sproutcore 0.9.0 → 0.9.1
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.
- data/Manifest.txt +42 -29
- data/README.txt +1 -1
- data/Rakefile +5 -0
- data/app_generators/sproutcore/sproutcore_generator.rb +4 -4
- data/app_generators/sproutcore/templates/sc-config.rb +72 -0
- data/bin/sc-build +2 -0
- data/clients/sc_docs/controllers/docs.js +1 -0
- data/clients/sc_docs/english.lproj/body.rhtml +5 -0
- data/clients/sc_docs/views/doc_frame.js +1 -1
- data/clients/sc_test_runner/controllers/runner.js +2 -2
- data/clients/sc_test_runner/english.lproj/body.rhtml +5 -0
- data/clients/sc_test_runner/main.js +12 -12
- data/clients/sc_test_runner/models/test.js +3 -0
- data/clients/sc_test_runner/views/test_label.js +1 -1
- data/config/hoe.rb +2 -0
- data/frameworks/sproutcore/controllers/array.js +132 -125
- data/frameworks/sproutcore/drag/drag.js +5 -2
- data/frameworks/sproutcore/english.lproj/buttons.css +64 -64
- data/frameworks/sproutcore/english.lproj/collections.css +82 -0
- data/frameworks/sproutcore/english.lproj/images/buttons-sprite.png +0 -0
- data/frameworks/sproutcore/english.lproj/images/sproutcore-logo.png +0 -0
- data/frameworks/sproutcore/english.lproj/images/sticky-note.png +0 -0
- data/frameworks/sproutcore/english.lproj/menu.css +1 -1
- data/frameworks/sproutcore/english.lproj/theme.css +13 -4
- data/frameworks/sproutcore/foundation/array.js +24 -2
- data/frameworks/sproutcore/foundation/benchmark.js +12 -5
- data/frameworks/sproutcore/foundation/observable.js +62 -1
- data/frameworks/sproutcore/foundation/utils.js +16 -0
- data/frameworks/sproutcore/tests/views/label_item.rhtml +21 -0
- data/frameworks/sproutcore/tests/views/list.rhtml +21 -0
- data/frameworks/sproutcore/tests/views/scroll.rhtml +21 -0
- data/frameworks/sproutcore/views/collection.js +401 -73
- data/frameworks/sproutcore/views/collection/collection_item.js +36 -0
- data/frameworks/sproutcore/views/collection/grid.js +149 -0
- data/frameworks/sproutcore/views/collection/image_cell.js +154 -0
- data/frameworks/sproutcore/views/collection/list.js +115 -0
- data/frameworks/sproutcore/views/collection/text_cell.js +128 -0
- data/frameworks/sproutcore/views/image.js +1 -1
- data/frameworks/sproutcore/views/label.js +6 -2
- data/frameworks/sproutcore/views/scroll.js +34 -0
- data/frameworks/sproutcore/views/view.js +12 -4
- data/generators/client/client_generator.rb +3 -11
- data/generators/client/templates/english.lproj/body.css +75 -0
- data/generators/client/templates/english.lproj/body.rhtml +17 -2
- data/generators/model/templates/fixture.js +32 -0
- data/lib/sproutcore/build_tools/html_builder.rb +29 -11
- data/lib/sproutcore/build_tools/resource_builder.rb +1 -1
- data/lib/sproutcore/bundle.rb +19 -7
- data/lib/sproutcore/library.rb +39 -21
- data/lib/sproutcore/merb/bundle_controller.rb +3 -8
- data/lib/sproutcore/version.rb +1 -1
- data/lib/sproutcore/view_helpers.rb +7 -5
- data/lib/sproutcore/view_helpers/core_views.rb +11 -3
- data/sc-config.rb +7 -0
- data/tasks/deployment.rake +15 -2
- metadata +44 -31
- data/app_generators/sproutcore/templates/environment.yml +0 -4
- data/environment.yml +0 -9
| @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            /* @override http://localhost:4020/static/sproutcore/en/_cache/collections-1205614097.css */
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            /* @group Common */
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            body {
         | 
| 6 | 
            +
            	overflow: hidden;
         | 
| 7 | 
            +
            }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            .collection-item {
         | 
| 10 | 
            +
            	cursor: pointer ;
         | 
| 11 | 
            +
            }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            /* @end */
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            /* @group List View */
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            .sc-theme .sc-collection-view {
         | 
| 18 | 
            +
            	background-color: white ;
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            .sc-theme .list-insertion-point {
         | 
| 22 | 
            +
            	border: 1px #4e4977 solid;
         | 
| 23 | 
            +
            	position: absolute ;
         | 
| 24 | 
            +
            }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            .sc-theme .list-insertion-point .anchor {
         | 
| 27 | 
            +
            	position: absolute ;
         | 
| 28 | 
            +
            	width: 7px;
         | 
| 29 | 
            +
            	height: 7px;
         | 
| 30 | 
            +
            	left: -6px;
         | 
| 31 | 
            +
            	top: -4px;
         | 
| 32 | 
            +
            	background: static_url('images/buttons-sprite.png') no-repeat 0px -931px;
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            /* @end */
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            /* @group Grid View */
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            .sc-theme .grid-insertion-point {
         | 
| 40 | 
            +
            	border: 1px #4e4977 solid;
         | 
| 41 | 
            +
            	position: absolute ;
         | 
| 42 | 
            +
            }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            .sc-theme .grid-insertion-point .anchor {
         | 
| 45 | 
            +
            	position: absolute ;
         | 
| 46 | 
            +
            	width: 7px;
         | 
| 47 | 
            +
            	height: 7px;
         | 
| 48 | 
            +
            	left: -4px;
         | 
| 49 | 
            +
            	top: -6px;
         | 
| 50 | 
            +
            	background: static_url('images/buttons-sprite.png') no-repeat 0px -931px;
         | 
| 51 | 
            +
            }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            /* @end */
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            /* @group Text Cell */
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            .sc-theme .collection-item {
         | 
| 58 | 
            +
            	text-decoration: none ;
         | 
| 59 | 
            +
            	color: #000;
         | 
| 60 | 
            +
            	border-top: 1px white solid;
         | 
| 61 | 
            +
            	background-color: white ;
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            .sc-theme .collection-item.text-cell {
         | 
| 65 | 
            +
            	display: block ;
         | 
| 66 | 
            +
            	padding: 0 6px;
         | 
| 67 | 
            +
            	line-height: 22px;
         | 
| 68 | 
            +
            }
         | 
| 69 | 
            +
            .sc-theme .collection-item.sel {
         | 
| 70 | 
            +
            	background-color: #ddd;
         | 
| 71 | 
            +
            	border-top: 1px solid #eee;
         | 
| 72 | 
            +
            }
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            .sc-theme .sc-collection-view.focus .collection-item.sel {
         | 
| 75 | 
            +
            	background-color: #40007e;
         | 
| 76 | 
            +
            	color: white ;
         | 
| 77 | 
            +
            	border-top: 1px solid #84788f;
         | 
| 78 | 
            +
            }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            /* @end */
         | 
| 81 | 
            +
             | 
| 82 | 
            +
             | 
| Binary file | 
| Binary file | 
| Binary file | 
| @@ -1,3 +1,12 @@ | |
| 1 | 
            +
            /* @override http://localhost:4020/static/sproutcore/en/_cache/theme-1205438214.css */
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            /* @group Root Container */
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            .sc-scroll-view {
         | 
| 6 | 
            +
            	overflow: auto ;
         | 
| 7 | 
            +
            }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            /* @end */
         | 
| 1 10 |  | 
| 2 11 | 
             
            /* @group Root Form */
         | 
| 3 12 |  | 
| @@ -24,16 +33,16 @@ input.show-hint { | |
| 24 33 | 
             
            /* @group sc-theme */
         | 
| 25 34 |  | 
| 26 35 | 
             
            body.sc-theme {
         | 
| 27 | 
            -
            	font:  | 
| 36 | 
            +
            	font: 13px Helvetica, Verdana, sans-serif;
         | 
| 37 | 
            +
            	line-height: 18px;
         | 
| 28 38 | 
             
            	background-color: #f0f0f0 ;
         | 
| 29 | 
            -
            	padding:  | 
| 39 | 
            +
            	padding: 0px;
         | 
| 30 40 | 
             
            }
         | 
| 31 41 |  | 
| 32 42 | 
             
            .sc-theme h1 {
         | 
| 33 | 
            -
            	margin: 0 | 
| 43 | 
            +
            	margin: 0;
         | 
| 34 44 | 
             
            	padding: 0 ;
         | 
| 35 45 | 
             
            	margin-bottom: 10px;
         | 
| 36 | 
            -
            	border-bottom: 1px #888 dotted ;
         | 
| 37 46 | 
             
            }
         | 
| 38 47 |  | 
| 39 48 | 
             
            .sc-theme label {
         | 
| @@ -68,6 +68,7 @@ SC.Array = { | |
| 68 68 | 
             
            */
         | 
| 69 69 | 
             
              objectAt: function(idx)
         | 
| 70 70 | 
             
              {
         | 
| 71 | 
            +
                if (idx < 0) return undefined ;
         | 
| 71 72 | 
             
                if (idx >= this.get('length')) return undefined;
         | 
| 72 73 | 
             
                return this.get(idx);
         | 
| 73 74 | 
             
              },
         | 
| @@ -86,7 +87,8 @@ SC.Array = { | |
| 86 87 | 
             
                notified.
         | 
| 87 88 | 
             
              */
         | 
| 88 89 | 
             
              arrayContentDidChange: function() {
         | 
| 89 | 
            -
                this. | 
| 90 | 
            +
                var kvo = (this._kvo) ? this._kvo().changes : '(null)';
         | 
| 91 | 
            +
                this.notifyPropertyChange('[]') ;
         | 
| 90 92 | 
             
              },
         | 
| 91 93 |  | 
| 92 94 | 
             
              /**
         | 
| @@ -204,8 +206,27 @@ SC.Array = { | |
| 204 206 | 
             
            // enumerable methods since Arrays are already enumerable.
         | 
| 205 207 | 
             
            Object.extend(Array.prototype, SC.Array) ; 
         | 
| 206 208 |  | 
| 207 | 
            -
            // Now make SC.Array enumerable | 
| 209 | 
            +
            // Now make SC.Array enumerable and add other array method we did not want to
         | 
| 210 | 
            +
            // override in Array itself.
         | 
| 208 211 | 
             
            Object.extend(SC.Array, Enumerable) ;
         | 
| 212 | 
            +
            Object.extend(SC.Array, {
         | 
| 213 | 
            +
              /**
         | 
| 214 | 
            +
                Returns a new array that is a slice of the receiver.  This implementation
         | 
| 215 | 
            +
                uses the observable array methods to retrieve the objects for the new slice.
         | 
| 216 | 
            +
                
         | 
| 217 | 
            +
                @param beginIndex {Integer} (Optional) index to begin slicing from. Default: 0
         | 
| 218 | 
            +
                @param endIndex {Integer} (Optional) index to end the slice at. Default: 0
         | 
| 219 | 
            +
              */
         | 
| 220 | 
            +
              slice: function(beginIndex, endIndex) {
         | 
| 221 | 
            +
                var ret = []; 
         | 
| 222 | 
            +
                var length = this.get('length') ;
         | 
| 223 | 
            +
                if (beginIndex == null) beginIndex = 0 ;
         | 
| 224 | 
            +
                if ((endIndex == null) || (endIndex > length)) endIndex = length ;
         | 
| 225 | 
            +
                while(beginIndex < endIndex) ret[ret.length] = this.objectAt(beginIndex++) ;
         | 
| 226 | 
            +
                return ret ;
         | 
| 227 | 
            +
              }
         | 
| 228 | 
            +
              
         | 
| 229 | 
            +
            }) ;
         | 
| 209 230 |  | 
| 210 231 | 
             
            // ........................................................
         | 
| 211 232 | 
             
            // A few basic enhancements to the Array class.
         | 
| @@ -298,6 +319,7 @@ Object.extend(Array.prototype, { | |
| 298 319 | 
             
                if (value !== undefined) return null ;
         | 
| 299 320 | 
             
                return this.invoke('get', key) ;
         | 
| 300 321 | 
             
              }
         | 
| 322 | 
            +
                
         | 
| 301 323 | 
             
            }) ;
         | 
| 302 324 |  | 
| 303 325 | 
             
            Array.prototype.collect = Array.prototype.map ;
         | 
| @@ -58,23 +58,30 @@ SC.Benchmark = { | |
| 58 58 | 
             
                Call this method at the start of whatever you want to collect.
         | 
| 59 59 | 
             
                if topLevelOnly is passed, then recursive calls to the start will be 
         | 
| 60 60 | 
             
                ignored and only the top level call will be benchmarked.
         | 
| 61 | 
            +
                
         | 
| 62 | 
            +
                @param key {String} A unique key that identifies this benchmark.  All calls to start/end with the same key will be groups together.
         | 
| 63 | 
            +
                @param topLevelOnly {Boolean} If true then recursive calls to this method with the same key will be ignored.  
         | 
| 64 | 
            +
                @param time {Integer} Only pass if you want to explicitly set the start time.  Otherwise the start time is now.
         | 
| 61 65 | 
             
              */
         | 
| 62 | 
            -
              start: function(key, topLevelOnly) {
         | 
| 66 | 
            +
              start: function(key, topLevelOnly, time) {
         | 
| 63 67 | 
             
                if (!this.enabled) return ;
         | 
| 64 68 | 
             
                var stat = this._statFor(key) ;
         | 
| 65 69 |  | 
| 66 70 | 
             
                if (topLevelOnly && stat._starts.length > 0) {
         | 
| 67 71 | 
             
                  stat._starts.push('ignore') ;
         | 
| 68 72 | 
             
                } else {
         | 
| 69 | 
            -
                  stat._starts.push(Date.now()) ;
         | 
| 73 | 
            +
                  stat._starts.push(time || Date.now()) ;
         | 
| 70 74 | 
             
                }
         | 
| 71 75 | 
             
              },
         | 
| 72 76 |  | 
| 73 77 | 
             
              /**
         | 
| 74 78 | 
             
                Call this method at the end of whatever you want to collect.  This will
         | 
| 75 79 | 
             
                save the collected benchmark.
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                @param key {String} The benchmark key you used when you called start()
         | 
| 82 | 
            +
                @param time {Integer} Only pass if you want to explicitly set the end time.  Otherwise start time is now.
         | 
| 76 83 | 
             
              */
         | 
| 77 | 
            -
              end: function(key) {
         | 
| 84 | 
            +
              end: function(key, time) {
         | 
| 78 85 | 
             
                if (!this.enabled) return ;
         | 
| 79 86 | 
             
                var stat = this._statFor(key) ;
         | 
| 80 87 | 
             
                var start = stat._starts.pop() ;
         | 
| @@ -86,7 +93,7 @@ SC.Benchmark = { | |
| 86 93 | 
             
                // top level only.
         | 
| 87 94 | 
             
                if (start == 'ignore') return ; 
         | 
| 88 95 |  | 
| 89 | 
            -
                stat.amt += Date.now() - start ;
         | 
| 96 | 
            +
                stat.amt += (time || Date.now()) - start ;
         | 
| 90 97 | 
             
                stat.runs++ ;
         | 
| 91 98 |  | 
| 92 99 | 
             
                if (this.verbose) this.log(key) ;
         | 
| @@ -198,7 +205,7 @@ SC.Benchmark = { | |
| 198 205 | 
             
                return ret ;
         | 
| 199 206 | 
             
              },
         | 
| 200 207 |  | 
| 201 | 
            -
              reset: function() { this.stats = {} ; | 
| 208 | 
            +
              reset: function() { this.stats = {} ;  debugger;},
         | 
| 202 209 |  | 
| 203 210 | 
             
              // This is private, but it is used in some places, so we are keeping this for
         | 
| 204 211 | 
             
              // compatibility.
         | 
| @@ -174,6 +174,25 @@ SC.Observable = { | |
| 174 174 | 
             
                if (tuple[0] == null) return null ;
         | 
| 175 175 | 
             
                return tuple[0].set(tuple[1], value) ;
         | 
| 176 176 | 
             
              },
         | 
| 177 | 
            +
             | 
| 178 | 
            +
              
         | 
| 179 | 
            +
              /** 
         | 
| 180 | 
            +
                Convenience method to get an array of properties.
         | 
| 181 | 
            +
                
         | 
| 182 | 
            +
                Pass in multiple property keys or an array of property keys.  This
         | 
| 183 | 
            +
                method uses getPath() so you can also pass key paths.
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                @returns {Array} Values of property keys.
         | 
| 186 | 
            +
              */
         | 
| 187 | 
            +
              getEach: function() {
         | 
| 188 | 
            +
                var keys = $A(arguments).flatten() ;
         | 
| 189 | 
            +
                var ret = [];
         | 
| 190 | 
            +
                for(var idx=0; idx<keys.length;idx++) {
         | 
| 191 | 
            +
                  ret[ret.length] = this.getPath(keys[idx]);
         | 
| 192 | 
            +
                }
         | 
| 193 | 
            +
                return ret ;
         | 
| 194 | 
            +
              },
         | 
| 195 | 
            +
              
         | 
| 177 196 |  | 
| 178 197 | 
             
              /**  
         | 
| 179 198 | 
             
                Increments the value of a property.
         | 
| @@ -228,18 +247,60 @@ SC.Observable = { | |
| 228 247 | 
             
              },
         | 
| 229 248 |  | 
| 230 249 | 
             
              /**  
         | 
| 231 | 
            -
                 | 
| 250 | 
            +
                Notify the observer system that a property is about to change.
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                Sometimes you need to change a value directly or indirectly without actually
         | 
| 253 | 
            +
                calling get() or set() on it.  In this case, you can use this method and 
         | 
| 254 | 
            +
                propertyDidChange() instead.  Calling these two methods together will notify all
         | 
| 255 | 
            +
                observers that the property has potentially changed value.
         | 
| 256 | 
            +
                
         | 
| 257 | 
            +
                Note that you must always call propertyWillChange and propertyDidChange as a pair.
         | 
| 258 | 
            +
                If you do not, it may get the property change groups out of order and cause
         | 
| 259 | 
            +
                notifications to be delivered more often than you would like.
         | 
| 260 | 
            +
                
         | 
| 261 | 
            +
                @param key {String} The property key that is about to change.
         | 
| 232 262 | 
             
              */
         | 
| 233 263 | 
             
              propertyWillChange: function(key) {
         | 
| 234 264 | 
             
                this._kvo().changes++ ;
         | 
| 235 265 | 
             
              },
         | 
| 236 266 |  | 
| 267 | 
            +
              /**  
         | 
| 268 | 
            +
                Notify the observer system that a property has just changed.
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                Sometimes you need to change a value directly or indirectly without actually
         | 
| 271 | 
            +
                calling get() or set() on it.  In this case, you can use this method and 
         | 
| 272 | 
            +
                propertyWillChange() instead.  Calling these two methods together will notify all
         | 
| 273 | 
            +
                observers that the property has potentially changed value.
         | 
| 274 | 
            +
                
         | 
| 275 | 
            +
                Note that you must always call propertyWillChange and propertyDidChange as a pair.
         | 
| 276 | 
            +
                If you do not, it may get the property change groups out of order and cause
         | 
| 277 | 
            +
                notifications to be delivered more often than you would like.
         | 
| 278 | 
            +
                
         | 
| 279 | 
            +
                @param key {String} The property key that has just changed.
         | 
| 280 | 
            +
                @param value {Object} The new value of the key.  May be null.
         | 
| 281 | 
            +
              */
         | 
| 237 282 | 
             
              propertyDidChange: function(key,value) {
         | 
| 238 283 | 
             
                this._kvo().changed[key] = value ;
         | 
| 239 284 | 
             
                var kvo = this._kvo() ;  kvo.changes--; kvo.revision++ ;
         | 
| 240 285 | 
             
                if (kvo.changes <= 0) this._notifyPropertyObservers() ;
         | 
| 241 286 | 
             
              },
         | 
| 242 287 |  | 
| 288 | 
            +
              /**
         | 
| 289 | 
            +
                Convenience method to call propertyWillChange/propertyDidChange.
         | 
| 290 | 
            +
                
         | 
| 291 | 
            +
                Sometimes you need to notify observers that a property has changed value without
         | 
| 292 | 
            +
                actually changing this value.  In those cases, you can use this method as a 
         | 
| 293 | 
            +
                convenience instead of calling propertyWillChange() and propertyDidChange().
         | 
| 294 | 
            +
                
         | 
| 295 | 
            +
                @param key {String} The property key that has just changed.
         | 
| 296 | 
            +
                @param value {Object} The new value of the key.  May be null.
         | 
| 297 | 
            +
                @returns {void}
         | 
| 298 | 
            +
              */
         | 
| 299 | 
            +
              notifyPropertyChange: function(key, value) {
         | 
| 300 | 
            +
                this.propertyWillChange(key) ;
         | 
| 301 | 
            +
                this.propertyDidChange(key, value) ;
         | 
| 302 | 
            +
              },
         | 
| 303 | 
            +
              
         | 
| 243 304 | 
             
              /**  
         | 
| 244 305 | 
             
                This may be a simpler way to notify of changes if you are making a major
         | 
| 245 306 | 
             
                update or don't know exactly which properties have changed.  This ignores
         | 
| @@ -56,6 +56,22 @@ Object.extend(SC, | |
| 56 56 | 
             
                        (point.y >= SC.minY(f)) &&
         | 
| 57 57 | 
             
                        (point.x <= SC.maxX(f)) && 
         | 
| 58 58 | 
             
                        (point.y <= SC.maxY(f)) ;
         | 
| 59 | 
            +
              },
         | 
| 60 | 
            +
              
         | 
| 61 | 
            +
              /** Return true if the two frames match.
         | 
| 62 | 
            +
              
         | 
| 63 | 
            +
                @param r1 {Rect} the first rect
         | 
| 64 | 
            +
                @param r2 {Rect} the second rect
         | 
| 65 | 
            +
                @param delta {Float} an optional delta that allows for rects that do not match exactly. Defaults to 0.1
         | 
| 66 | 
            +
                @returns {Boolean} true if rects match
         | 
| 67 | 
            +
               */
         | 
| 68 | 
            +
              rectsEqual: function(r1, r2, delta) {
         | 
| 69 | 
            +
                if (delta == null) delta = 0.1;
         | 
| 70 | 
            +
                if (Math.abs(r1.y - r2.y) > delta) return false ; 
         | 
| 71 | 
            +
                if (Math.abs(r1.x - r2.x) > delta) return false ; 
         | 
| 72 | 
            +
                if (Math.abs(r1.width - r2.width) > delta) return false ; 
         | 
| 73 | 
            +
                if (Math.abs(r1.height - r2.height) > delta) return false ; 
         | 
| 74 | 
            +
                return true ;
         | 
| 59 75 | 
             
              }
         | 
| 60 76 |  | 
| 61 77 | 
             
            }) ;
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            <% # ========================================================================
         | 
| 2 | 
            +
               # Sproutcore.LabelItemView Unit Test
         | 
| 3 | 
            +
               # ========================================================================
         | 
| 4 | 
            +
            %>
         | 
| 5 | 
            +
            <% content_for('final') do %>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <script>
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Test.context("Sproutcore.LabelItemView",{
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              "TODO: Add your own tests here": function() {
         | 
| 12 | 
            +
                true.shouldEqual(true) ;
         | 
| 13 | 
            +
              }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            }) ;
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            appMain = main; main = null ; // Cancel main() so app does not start
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            </script>
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            <% end %>
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            <% # ========================================================================
         | 
| 2 | 
            +
               # Sproutcore.ListView Unit Test
         | 
| 3 | 
            +
               # ========================================================================
         | 
| 4 | 
            +
            %>
         | 
| 5 | 
            +
            <% content_for('final') do %>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <script>
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Test.context("Sproutcore.ListView",{
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              "TODO: Add your own tests here": function() {
         | 
| 12 | 
            +
                true.shouldEqual(true) ;
         | 
| 13 | 
            +
              }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            }) ;
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            appMain = main; main = null ; // Cancel main() so app does not start
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            </script>
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            <% end %>
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            <% # ========================================================================
         | 
| 2 | 
            +
               # Sproutcore.ScrollView Unit Test
         | 
| 3 | 
            +
               # ========================================================================
         | 
| 4 | 
            +
            %>
         | 
| 5 | 
            +
            <% content_for('final') do %>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <script>
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Test.context("Sproutcore.ScrollView",{
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              "TODO: Add your own tests here": function() {
         | 
| 12 | 
            +
                true.shouldEqual(true) ;
         | 
| 13 | 
            +
              }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            }) ;
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            appMain = main; main = null ; // Cancel main() so app does not start
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            </script>
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            <% end %>
         | 
| @@ -351,7 +351,8 @@ SC.CollectionView = SC.View.extend( | |
| 351 351 |  | 
| 352 352 | 
             
              /**  
         | 
| 353 353 | 
             
                Property returns all of the item views, regardless of group view.
         | 
| 354 | 
            -
             | 
| 354 | 
            +
             | 
| 355 | 
            +
                @property
         | 
| 355 356 | 
             
                @returns {Array} the item views.
         | 
| 356 357 | 
             
              */
         | 
| 357 358 | 
             
              itemViews: function() {
         | 
| @@ -363,6 +364,31 @@ SC.CollectionView = SC.View.extend( | |
| 363 364 | 
             
                return ret;
         | 
| 364 365 | 
             
              }.property(),
         | 
| 365 366 |  | 
| 367 | 
            +
              /** 
         | 
| 368 | 
            +
                The property on content objects item views should display.
         | 
| 369 | 
            +
                
         | 
| 370 | 
            +
                Most built-in item views will respect this property.  You can also use it when writing 
         | 
| 371 | 
            +
                you own item views.
         | 
| 372 | 
            +
              */
         | 
| 373 | 
            +
              displayProperty: null,
         | 
| 374 | 
            +
             | 
| 375 | 
            +
              /**
         | 
| 376 | 
            +
                Enables keyboard-based navigate if set to true.
         | 
| 377 | 
            +
              */
         | 
| 378 | 
            +
              acceptsFirstResponder: false,
         | 
| 379 | 
            +
             | 
| 380 | 
            +
              /**
         | 
| 381 | 
            +
                If your layout uses a grid or horizontal-based layout, then make sure this 
         | 
| 382 | 
            +
                property is always up to date with the current number of items per row.  
         | 
| 383 | 
            +
                
         | 
| 384 | 
            +
                The CollectionView will use this property to support keyboard navigation 
         | 
| 385 | 
            +
                using the arrow keys.
         | 
| 386 | 
            +
                
         | 
| 387 | 
            +
                If your collection view is simply a vertical list of items then you do not need
         | 
| 388 | 
            +
                to edit this property.
         | 
| 389 | 
            +
              */
         | 
| 390 | 
            +
              itemsPerRow: 1,
         | 
| 391 | 
            +
              
         | 
| 366 392 | 
             
              /**
         | 
| 367 393 | 
             
                Returns true if the passed view belongs to the collection.
         | 
| 368 394 |  | 
| @@ -379,6 +405,22 @@ SC.CollectionView = SC.View.extend( | |
| 379 405 | 
             
                return !!this._itemViews[SC.getGUID(view)];
         | 
| 380 406 | 
             
              },
         | 
| 381 407 |  | 
| 408 | 
            +
              // ......................................
         | 
| 409 | 
            +
              // FIRST RESPONDER
         | 
| 410 | 
            +
              //
         | 
| 411 | 
            +
             | 
| 412 | 
            +
              /**
         | 
| 413 | 
            +
                Called whenever the collection becomes first responder. 
         | 
| 414 | 
            +
                Adds the focused class to the element.
         | 
| 415 | 
            +
              */
         | 
| 416 | 
            +
              didBecomeFirstResponder: function() {
         | 
| 417 | 
            +
                this.addClassName('focus') ;
         | 
| 418 | 
            +
              },
         | 
| 419 | 
            +
              
         | 
| 420 | 
            +
              willLoseFirstResponder: function() {
         | 
| 421 | 
            +
                this.removeClassName('focus');
         | 
| 422 | 
            +
              },
         | 
| 423 | 
            +
              
         | 
| 382 424 | 
             
              // ......................................
         | 
| 383 425 | 
             
              // DRAG AND DROP SUPPORT
         | 
| 384 426 | 
             
              //
         | 
| @@ -425,7 +467,7 @@ SC.CollectionView = SC.View.extend( | |
| 425 467 | 
             
                  // This assumes you will flow LTR, but it should work if you flow
         | 
| 426 468 | 
             
                  // bottom to top or top to bottom.
         | 
| 427 469 | 
             
                  } else {
         | 
| 428 | 
            -
                    if (SC. | 
| 470 | 
            +
                    if (SC.minX(f) < loc.x) {
         | 
| 429 471 | 
             
                      curSide = (SC.maxY(f) < loc.y) ? -1 : 1 ;
         | 
| 430 472 | 
             
                    } else curSide = null ;
         | 
| 431 473 | 
             
                  } 
         | 
| @@ -488,26 +530,86 @@ SC.CollectionView = SC.View.extend( | |
| 488 530 | 
             
                @returns {void}
         | 
| 489 531 | 
             
              */
         | 
| 490 532 | 
             
              hideInsertionPoint: function() {},
         | 
| 533 | 
            +
             | 
| 534 | 
            +
              /**
         | 
| 535 | 
            +
                Override this method to provide your own ghost image for a drag.  
         | 
| 536 | 
            +
                
         | 
| 537 | 
            +
                Note that the only purpose of this view is to render a visible drag element.  It is
         | 
| 538 | 
            +
                not critical that you make this element bindable, etc.
         | 
| 539 | 
            +
                
         | 
| 540 | 
            +
                @param dragContent {Array} Array of content objects that will be used in the drag.
         | 
| 541 | 
            +
              */
         | 
| 542 | 
            +
              ghostViewFor: function(dragContent) {
         | 
| 543 | 
            +
                var view = SC.View.create() ;
         | 
| 544 | 
            +
                view.set('frame', this.get('frame')) ;
         | 
| 545 | 
            +
                view.set('isPositioned', true) ;
         | 
| 546 | 
            +
                var idx = dragContent.length ;
         | 
| 547 | 
            +
                var maxX = 0; var maxY = 0 ;
         | 
| 548 | 
            +
                
         | 
| 549 | 
            +
                while(--idx >= 0) {
         | 
| 550 | 
            +
                  var itemView = this.itemViewForContent(dragContent[idx]) ;
         | 
| 551 | 
            +
                  if (!itemView) continue ;
         | 
| 552 | 
            +
                  var f = itemView.get('frame') ;
         | 
| 553 | 
            +
                  var dom = itemView.rootElement ;
         | 
| 554 | 
            +
                  if (!dom) continue ;
         | 
| 555 | 
            +
                  
         | 
| 556 | 
            +
                  // save the maxX & maxY.  This will be used to trim the size 
         | 
| 557 | 
            +
                  // of the ghost view later.
         | 
| 558 | 
            +
                  if (SC.maxX(f) > maxX) maxX = SC.maxX(f) ;
         | 
| 559 | 
            +
                  if (SC.maxY(f) > maxY) maxY = SC.maxY(f) ;
         | 
| 560 | 
            +
             | 
| 561 | 
            +
                  // Clone the contents of this node.  We should probably apply the 
         | 
| 562 | 
            +
                  // computed style to the cloned nodes in order to make sure they match even if the 
         | 
| 563 | 
            +
                  // CSS styles do not match.  Make sure the items are properly 
         | 
| 564 | 
            +
                  // positioned.
         | 
| 565 | 
            +
                  dom = dom.cloneNode(true) ;
         | 
| 566 | 
            +
                  Element.setStyle(dom, { position: "absolute", left: "%@px".fmt(f.x), top: "%@px".fmt(f.y), width: "%@px".fmt(f.width), height: "%@px".fmt(f.height) }) ;
         | 
| 567 | 
            +
                  view.rootElement.appendChild(dom) ;
         | 
| 568 | 
            +
                }
         | 
| 569 | 
            +
                
         | 
| 570 | 
            +
                // Trim the size of the view to match the maxX & maxY as well as overflow
         | 
| 571 | 
            +
                view.setStyle({ overflow: 'hidden', width: "%@px.".fmt(maxX+1), height: "%@px".fmt(maxY+1) }) ;
         | 
| 572 | 
            +
             | 
| 573 | 
            +
                return view ;
         | 
| 574 | 
            +
              },
         | 
| 491 575 |  | 
| 492 | 
            -
              // handle mouse drags.  If the canReorderContent is enabled, allow the 
         | 
| 493 | 
            -
              // user to start a reorder.
         | 
| 494 576 | 
             
              mouseDragged: function(ev) {
         | 
| 495 577 | 
             
                // Don't do anything unless the user has been dragging for 123msec
         | 
| 496 578 | 
             
                if ((Date.now() - this._mouseDownAt) < 123) return true ;
         | 
| 497 579 |  | 
| 498 580 | 
             
                // OK, they must be serious, start a drag if possible. 
         | 
| 499 | 
            -
                // Also use this opportunity to clean up since mouseUp won't 
         | 
| 500 | 
            -
                // get called.
         | 
| 501 581 | 
             
                if (this.get('canReorderContent')) {
         | 
| 582 | 
            +
             | 
| 583 | 
            +
                  // we need to recalculate the frame at this point.
         | 
| 584 | 
            +
                  this.flushFrameCache();
         | 
| 585 | 
            +
                  
         | 
| 586 | 
            +
                  // First, get the selection to drag.  Drag an array of selected
         | 
| 587 | 
            +
                  // items appearing in this collection, in the order of the 
         | 
| 588 | 
            +
                  // collection.
         | 
| 589 | 
            +
                  var content = this.get('content') || [] ;
         | 
| 590 | 
            +
                  var dragContent = this.get('selection').sort(function(a,b) {
         | 
| 591 | 
            +
                    a = content.indexOf(a) ; b = content.indexOf(b) ;
         | 
| 592 | 
            +
                    return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
         | 
| 593 | 
            +
                  });
         | 
| 594 | 
            +
             | 
| 595 | 
            +
                  // Build the drag view to use for the ghost drag.  This 
         | 
| 596 | 
            +
                  // should essentially contain any visible drag items.
         | 
| 597 | 
            +
                  var view = this.ghostViewFor(dragContent) ;
         | 
| 598 | 
            +
                  
         | 
| 599 | 
            +
                  // Initiate the drag
         | 
| 502 600 | 
             
                  SC.Drag.start({
         | 
| 503 601 | 
             
                    event: this._mouseDownEvent,
         | 
| 504 602 | 
             
                    source: this,
         | 
| 505 | 
            -
                    dragView:  | 
| 603 | 
            +
                    dragView: view,
         | 
| 506 604 | 
             
                    ghost: NO,
         | 
| 507 605 | 
             
                    slideBack: YES,
         | 
| 508 | 
            -
                    data: { "_mouseDownContent":  | 
| 606 | 
            +
                    data: { "_mouseDownContent": dragContent }
         | 
| 509 607 | 
             
                  }) ; 
         | 
| 608 | 
            +
                  
         | 
| 609 | 
            +
                  // Also use this opportunity to clean up since mouseUp won't 
         | 
| 610 | 
            +
                  // get called.
         | 
| 510 611 | 
             
                  this._cleanupMouseDown() ;
         | 
| 612 | 
            +
                  this._lastInsertionIndex = null ;
         | 
| 511 613 | 
             
                }
         | 
| 512 614 | 
             
              },
         | 
| 513 615 |  | 
| @@ -525,9 +627,33 @@ SC.CollectionView = SC.View.extend( | |
| 525 627 | 
             
                if (this.get('canReorderContent')) {
         | 
| 526 628 | 
             
                  var loc = drag.get('location') ;
         | 
| 527 629 | 
             
                  loc = this.convertFrameFromView(loc, null) ;
         | 
| 630 | 
            +
                  
         | 
| 631 | 
            +
                  // get the insertion index for this location.  This can be computed
         | 
| 632 | 
            +
                  // by a subclass using whatever method.  This method is not expected to
         | 
| 633 | 
            +
                  // do any data valdidation, just to map the location to an insertion index.
         | 
| 528 634 | 
             
                  var ret = this.insertionIndexForLocation(loc) ;
         | 
| 635 | 
            +
             | 
| 636 | 
            +
                  // now that we have an index, find the nearest index that we can actually
         | 
| 637 | 
            +
                  // insert at, or do not allow.
         | 
| 638 | 
            +
                  var objects = (drag.source == this) ? (drag.dataForType('_mouseDownContent') || []) : [];
         | 
| 639 | 
            +
                  var content = this.get('content') || [] ;
         | 
| 640 | 
            +
             | 
| 641 | 
            +
                  // if the insertion index is in between two items in the drag itself, then this is
         | 
| 642 | 
            +
                  // not allowed.  Either use the last insertion index or find the first index that is not 
         | 
| 643 | 
            +
                  // in between selections.
         | 
| 644 | 
            +
                  var isPreviousInDrag = (ret > 0) ? objects.indexOf(content.objectAt(ret-1)) : -1 ;
         | 
| 645 | 
            +
                  var isNextInDrag = (ret < content.get('length')-1) ? objects.indexOf(content.objectAt(ret)) : -1 ;
         | 
| 646 | 
            +
                  if (isPreviousInDrag>=0 && isNextInDrag>=0) {
         | 
| 647 | 
            +
                    if (this._lastInsertionIndex == null) {
         | 
| 648 | 
            +
                      while((ret > 0) && (objects.indexOf(content.objectAt(ret)) >= 0)) ret-- ;
         | 
| 649 | 
            +
                    } else ret = this._lastInsertionIndex ;
         | 
| 650 | 
            +
                  }
         | 
| 651 | 
            +
                  
         | 
| 652 | 
            +
                  // Now that we have verified that, check to see if a drop is allowed in the 
         | 
| 653 | 
            +
                  // insertion index with the delegate.
         | 
| 654 | 
            +
                  // TODO
         | 
| 655 | 
            +
             | 
| 529 656 | 
             
                  if (this._lastInsertionIndex != ret) {
         | 
| 530 | 
            -
                    console.log("--itemView: %@".fmt(ret)) ;
         | 
| 531 657 | 
             
                    var itemView = this.itemViewForContent(this.get('content').objectAt(ret));
         | 
| 532 658 | 
             
                    this.showInsertionPointBefore(itemView) ;
         | 
| 533 659 | 
             
                  }
         | 
| @@ -553,29 +679,39 @@ SC.CollectionView = SC.View.extend( | |
| 553 679 |  | 
| 554 680 | 
             
              performDragOperation: function(op, drag) { 
         | 
| 555 681 |  | 
| 682 | 
            +
                SC.Benchmark.start('%@ performDragOperation'.fmt(this._guid)) ;
         | 
| 683 | 
            +
                
         | 
| 556 684 | 
             
                var loc = drag.get('location') ;
         | 
| 557 685 | 
             
                loc = this.convertFrameFromView(loc, null) ;
         | 
| 558 686 |  | 
| 559 687 | 
             
                // if op is MOVE or COPY, add item to view.
         | 
| 560 | 
            -
                var  | 
| 561 | 
            -
                if ( | 
| 688 | 
            +
                var objects = drag.dataForType('_mouseDownContent') ;
         | 
| 689 | 
            +
                if (objects && (op == SC.DRAG_MOVE)) {
         | 
| 562 690 |  | 
| 563 691 | 
             
                  // find the index to for the new insertion 
         | 
| 564 692 | 
             
                  var idx = this.insertionIndexForLocation(loc) ;
         | 
| 565 | 
            -
             | 
| 693 | 
            +
             | 
| 566 694 | 
             
                  var content = this.get('content') ;
         | 
| 567 695 | 
             
                  content.beginPropertyChanges(); // suspend notifications
         | 
| 568 | 
            -
             | 
| 696 | 
            +
             | 
| 697 | 
            +
                  // debugger ;
         | 
| 569 698 | 
             
                  // find the old index and remove it.
         | 
| 570 | 
            -
                  var  | 
| 571 | 
            -
                   | 
| 572 | 
            -
             | 
| 699 | 
            +
                  var objectsIdx = objects.get('length') ;
         | 
| 700 | 
            +
                  while(--objectsIdx >= 0) {
         | 
| 701 | 
            +
                    var obj = objects.objectAt(objectsIdx) ;
         | 
| 702 | 
            +
                    var old = content.indexOf(obj) ;
         | 
| 703 | 
            +
                    if (old >= 0) content.removeAt(old) ;
         | 
| 704 | 
            +
                    if ((old >= 0) && (old < idx)) idx--; //adjust idx
         | 
| 705 | 
            +
                  }
         | 
| 573 706 |  | 
| 574 | 
            -
                  // now insert  | 
| 575 | 
            -
                  content. | 
| 707 | 
            +
                  // now insert objects at new location
         | 
| 708 | 
            +
                  content.replace(idx, 0, objects) ;
         | 
| 576 709 | 
             
                  content.endPropertyChanges(); // restart notifications
         | 
| 577 710 | 
             
                }
         | 
| 578 711 |  | 
| 712 | 
            +
                SC.Benchmark.end('%@ performDragOperation'.fmt(this._guid)) ;
         | 
| 713 | 
            +
                console.log(SC.Benchmark.report()) ;
         | 
| 714 | 
            +
                
         | 
| 579 715 | 
             
                return SC.DRAG_MOVE; 
         | 
| 580 716 | 
             
              },
         | 
| 581 717 |  | 
| @@ -609,6 +745,7 @@ SC.CollectionView = SC.View.extend( | |
| 609 745 | 
             
              {
         | 
| 610 746 | 
             
                var el = this.containerElement || this.rootElement;
         | 
| 611 747 |  | 
| 748 | 
            +
                SC.Benchmark.start('%@: updateChildren'.fmt(this._guid)) ;
         | 
| 612 749 | 
             
                // initial setup
         | 
| 613 750 | 
             
                if (this._firstUpdate)
         | 
| 614 751 | 
             
                {
         | 
| @@ -713,6 +850,7 @@ SC.CollectionView = SC.View.extend( | |
| 713 850 | 
             
                this.updateSelectionStates() ;
         | 
| 714 851 | 
             
                this.flushFrameCache() ;
         | 
| 715 852 | 
             
                this.set('isDirty',false); 
         | 
| 853 | 
            +
                SC.Benchmark.end('%@: updateChildren'.fmt(this._guid)) ;
         | 
| 716 854 | 
             
              },
         | 
| 717 855 |  | 
| 718 856 | 
             
              /**
         | 
| @@ -746,6 +884,10 @@ SC.CollectionView = SC.View.extend( | |
| 746 884 |  | 
| 747 885 | 
             
                var firstChild = null ;
         | 
| 748 886 |  | 
| 887 | 
            +
                // save the first child to be modified.  This will be
         | 
| 888 | 
            +
                // passed to the layout method.
         | 
| 889 | 
            +
                var firstModifiedChild = null;
         | 
| 890 | 
            +
                
         | 
| 749 891 | 
             
                while (child || (inGroup && (loc < contentCount) && !expired)) {
         | 
| 750 892 |  | 
| 751 893 | 
             
                  // get the content object.
         | 
| @@ -796,6 +938,7 @@ SC.CollectionView = SC.View.extend( | |
| 796 938 | 
             
                    parent.insertBefore(newChild,child);
         | 
| 797 939 | 
             
                    this._itemViews[SC.getGUID(newChild)] = newChild;
         | 
| 798 940 | 
             
                    itemViewsDidChange = true;
         | 
| 941 | 
            +
                    if (!firstModifiedChild) firstModifiedChild = newChild ;
         | 
| 799 942 | 
             
                    child = newChild;
         | 
| 800 943 | 
             
                  }
         | 
| 801 944 |  | 
| @@ -813,26 +956,22 @@ SC.CollectionView = SC.View.extend( | |
| 813 956 |  | 
| 814 957 |  | 
| 815 958 | 
             
                // maybe save the current render loc and reschedule.
         | 
| 816 | 
            -
                if (expired && (loc < contentCount))
         | 
| 817 | 
            -
                {
         | 
| 959 | 
            +
                if (expired && (loc < contentCount)) {
         | 
| 818 960 | 
             
                  this._lastRenderLoc = loc ;
         | 
| 819 961 | 
             
                  this._lastRenderChild = child ;
         | 
| 820 962 | 
             
                  setTimeout(this.updateChildren.bind(this),1) ; // do more later.
         | 
| 821 | 
            -
                } 
         | 
| 822 | 
            -
                else 
         | 
| 823 | 
            -
                {
         | 
| 963 | 
            +
                } else {
         | 
| 824 964 | 
             
                  this._resetExpiredRender();
         | 
| 825 965 | 
             
                }
         | 
| 826 966 |  | 
| 827 967 | 
             
                // now let the collection view layout the views that changed (if 
         | 
| 828 968 | 
             
                // it is implemented.)
         | 
| 829 | 
            -
                if (this.layoutChildViewsFor)
         | 
| 830 | 
            -
                {
         | 
| 969 | 
            +
                if (firstModifiedChild && this.layoutChildViewsFor) {
         | 
| 831 970 | 
             
                  var el = this.containerElement || this.rootElement;
         | 
| 832 971 | 
             
                  if (this._cachedParent) {
         | 
| 833 972 | 
             
                    this._cachedParent.insertBefore(el,this._cachedSibling);
         | 
| 834 973 | 
             
                  }   
         | 
| 835 | 
            -
                  this.layoutChildViewsFor(parent,  | 
| 974 | 
            +
                  this.layoutChildViewsFor(parent, firstModifiedChild);
         | 
| 836 975 | 
             
                  if (this._cachedParent) {
         | 
| 837 976 | 
             
                    this._cachedParent.removeChild(el);
         | 
| 838 977 | 
             
                  }
         | 
| @@ -981,24 +1120,11 @@ SC.CollectionView = SC.View.extend( | |
| 981 1120 | 
             
              // This will set the collection height.
         | 
| 982 1121 | 
             
              updateComputedViewHeight: function(groupView) {
         | 
| 983 1122 | 
             
                var height = this.computedViewHeight(groupView) ;
         | 
| 984 | 
            -
                if (height  | 
| 985 | 
            -
                   | 
| 986 | 
            -
             | 
| 987 | 
            -
                     | 
| 988 | 
            -
             | 
| 989 | 
            -
                } else {
         | 
| 990 | 
            -
                  if (!groupView._heightView) {
         | 
| 991 | 
            -
                    groupView._heightView = document.createElement('div') ;
         | 
| 992 | 
            -
                    groupView.rootElement.appendChild(groupView._heightView) ;
         | 
| 993 | 
            -
                    Element.setStyle(groupView._heightView,{
         | 
| 994 | 
            -
                      position: 'absolute', left: '0px', display: 'block',
         | 
| 995 | 
            -
                      width: '1px', height: '1px'
         | 
| 996 | 
            -
                    }) ;
         | 
| 997 | 
            -
                  }
         | 
| 998 | 
            -
                  
         | 
| 999 | 
            -
                  if (height != groupView._lastComputedHeight) {
         | 
| 1000 | 
            -
                    Element.setStyle(groupView._heightView,{ top: height + 'px' }) ;
         | 
| 1001 | 
            -
                    groupView._lastComputedHeight = height ;
         | 
| 1123 | 
            +
                if (height >= 0) {
         | 
| 1124 | 
            +
                  var f = this.get('frame') ;
         | 
| 1125 | 
            +
                  if (Math.abs(f.height - height) > 0.1) {
         | 
| 1126 | 
            +
                    f.height = height ;
         | 
| 1127 | 
            +
                    this.set('frame', { height: height }) ;
         | 
| 1002 1128 | 
             
                  }
         | 
| 1003 1129 | 
             
                }
         | 
| 1004 1130 | 
             
              },
         | 
| @@ -1007,36 +1133,158 @@ SC.CollectionView = SC.View.extend( | |
| 1007 1133 | 
             
              // SELECTION
         | 
| 1008 1134 | 
             
              //
         | 
| 1009 1135 |  | 
| 1010 | 
            -
               | 
| 1136 | 
            +
              _indexOfSelectionTop: function() {
         | 
| 1137 | 
            +
                var content = this.get('content');
         | 
| 1138 | 
            +
                var sel = this.get('selection');
         | 
| 1139 | 
            +
                if (!content || !sel) return - 1;
         | 
| 1140 | 
            +
             | 
| 1141 | 
            +
                // find the first item in the selection
         | 
| 1142 | 
            +
                var contentLength = content.get('length') ;
         | 
| 1143 | 
            +
                var indexOfSelected = contentLength ; var idx = sel.length ;
         | 
| 1144 | 
            +
                while(--idx >= 0) {
         | 
| 1145 | 
            +
                  var curIndex = content.indexOf(sel[idx]) ;
         | 
| 1146 | 
            +
                  if ((curIndex >= 0) && (curIndex < indexOfSelected)) indexOfSelected = curIndex ;
         | 
| 1147 | 
            +
                }
         | 
| 1148 | 
            +
                
         | 
| 1149 | 
            +
                return (indexOfSelected >= contentLength) ? -1 : indexOfSelected ;
         | 
| 1150 | 
            +
              },
         | 
| 1151 | 
            +
              
         | 
| 1152 | 
            +
              _indexOfSelectionBottom: function() {
         | 
| 1153 | 
            +
                var content = this.get('content');
         | 
| 1154 | 
            +
                var sel = this.get('selection');
         | 
| 1155 | 
            +
                if (!content || !sel) return - 1;
         | 
| 1156 | 
            +
             | 
| 1157 | 
            +
                var indexOfSelected = -1 ; var idx = sel.length ;
         | 
| 1158 | 
            +
                while(--idx >= 0) {
         | 
| 1159 | 
            +
                  var curIndex = content.indexOf(sel[idx]) ;
         | 
| 1160 | 
            +
                  if (curIndex > indexOfSelected) indexOfSelected = curIndex ;
         | 
| 1161 | 
            +
                }
         | 
| 1162 | 
            +
                
         | 
| 1163 | 
            +
                return (indexOfSelected < 0) ? -1 : indexOfSelected ;
         | 
| 1164 | 
            +
              },
         | 
| 1165 | 
            +
              
         | 
| 1166 | 
            +
              /**
         | 
| 1167 | 
            +
                Select one or more items before the current selection, optionally
         | 
| 1168 | 
            +
                extending the current selection.  Also scrolls the selected item into view.
         | 
| 1169 | 
            +
                
         | 
| 1170 | 
            +
                Selection does not wrap around.
         | 
| 1171 | 
            +
                
         | 
| 1172 | 
            +
                @param extend {Boolean} (Optional) If true, the selection will be extended instead of replaced.  Defaults to false.
         | 
| 1173 | 
            +
                @param numberOfItems {Integer} (Optional) The number of previous to be selected.  Defaults to 1
         | 
| 1174 | 
            +
                @returns {void}
         | 
| 1175 | 
            +
              */
         | 
| 1176 | 
            +
              selectPreviousItem: function(extend, numberOfItems)
         | 
| 1011 1177 | 
             
              {
         | 
| 1012 | 
            -
                 | 
| 1178 | 
            +
                if (numberOfItems == null) numberOfItems = 1 ;
         | 
| 1179 | 
            +
                if (extend == null) extend = false ;
         | 
| 1180 | 
            +
             | 
| 1013 1181 | 
             
                var content  = this.get('content');
         | 
| 1014 | 
            -
                var  | 
| 1015 | 
            -
             | 
| 1016 | 
            -
                 | 
| 1017 | 
            -
                var  | 
| 1018 | 
            -
                 | 
| 1019 | 
            -
             | 
| 1020 | 
            -
             | 
| 1021 | 
            -
             | 
| 1022 | 
            -
             | 
| 1023 | 
            -
             | 
| 1182 | 
            +
                var contentLength = content.get('length') ;
         | 
| 1183 | 
            +
             | 
| 1184 | 
            +
                // if extending, then we need to do some fun stuff to build the array
         | 
| 1185 | 
            +
                var selTop, selBottom, anchor ;
         | 
| 1186 | 
            +
                if (extend) {
         | 
| 1187 | 
            +
                  selTop = this._indexOfSelectionTop() ;
         | 
| 1188 | 
            +
                  selBottom = this._indexOfSelectionBottom() ;
         | 
| 1189 | 
            +
                  anchor = (this._selectionAnchor == null) ? selTop : this._selectionAnchor ;
         | 
| 1190 | 
            +
                  this._selectionAnchor = anchor ;
         | 
| 1191 | 
            +
             | 
| 1192 | 
            +
                  // If the selBottom is after the anchor, then reduce the selection
         | 
| 1193 | 
            +
                  if (selBottom > anchor) {
         | 
| 1194 | 
            +
                    selBottom-- ;
         | 
| 1195 | 
            +
                    
         | 
| 1196 | 
            +
                  // otherwise, select the previous item from the top 
         | 
| 1197 | 
            +
                  } else {
         | 
| 1198 | 
            +
                    selTop-- ;
         | 
| 1199 | 
            +
                  }
         | 
| 1200 | 
            +
                  
         | 
| 1201 | 
            +
                  // Ensure we are not out of bounds
         | 
| 1202 | 
            +
                  if (selTop < 0) selTop = 0 ;
         | 
| 1203 | 
            +
                  if (selBottom < selTop) selBottom = selTop ;
         | 
| 1204 | 
            +
                  
         | 
| 1205 | 
            +
                // if not extending, just select the item previous to the selTop
         | 
| 1206 | 
            +
                } else {
         | 
| 1207 | 
            +
                  selTop = this._indexOfSelectionTop() - 1;
         | 
| 1208 | 
            +
                  if (selTop < 0) selTop = 0 ;
         | 
| 1209 | 
            +
                  selBottom = selTop ;
         | 
| 1210 | 
            +
                  anchor = null ;
         | 
| 1211 | 
            +
                }
         | 
| 1212 | 
            +
                
         | 
| 1213 | 
            +
                // now build array of new items to select
         | 
| 1214 | 
            +
                var items = [] ;
         | 
| 1215 | 
            +
                while(selTop <= selBottom) {
         | 
| 1216 | 
            +
                  items[items.length] = content.objectAt(selTop++) ;
         | 
| 1217 | 
            +
                }
         | 
| 1218 | 
            +
             | 
| 1219 | 
            +
                // ensure that the item is visible and set the selection
         | 
| 1220 | 
            +
                if (items.length > 0) {
         | 
| 1221 | 
            +
                  this.scrollToItemRecord(items.first());
         | 
| 1222 | 
            +
                  this.selectItems(items);
         | 
| 1223 | 
            +
                }
         | 
| 1224 | 
            +
                
         | 
| 1225 | 
            +
                this._selectionAnchor = anchor ;
         | 
| 1024 1226 | 
             
              },
         | 
| 1025 1227 |  | 
| 1026 | 
            -
               | 
| 1228 | 
            +
              /**
         | 
| 1229 | 
            +
                Select one or more items folling the current selection, optionally
         | 
| 1230 | 
            +
                extending the current selection.  Also scrolls to selected item.
         | 
| 1231 | 
            +
             | 
| 1232 | 
            +
                Selection does not wrap around.
         | 
| 1233 | 
            +
                
         | 
| 1234 | 
            +
                @param extend {Boolean} (Optional) If true, the selection will be extended instead of replaced.  Defaults to false.
         | 
| 1235 | 
            +
                @param numberOfItems {Integer} (Optional) The number of items to be selected.  Defaults to 1.
         | 
| 1236 | 
            +
                @returns {void}
         | 
| 1237 | 
            +
              */
         | 
| 1238 | 
            +
              selectNextItem: function(extend, numberOfItems)
         | 
| 1027 1239 | 
             
              {
         | 
| 1028 | 
            -
                 | 
| 1240 | 
            +
                if (numberOfItems == null) numberOfItems = 1 ;
         | 
| 1241 | 
            +
                if (extend == null) extend = false ;
         | 
| 1242 | 
            +
             | 
| 1029 1243 | 
             
                var content  = this.get('content');
         | 
| 1030 | 
            -
                var  | 
| 1031 | 
            -
             | 
| 1032 | 
            -
                 | 
| 1033 | 
            -
                var  | 
| 1034 | 
            -
                 | 
| 1035 | 
            -
             | 
| 1036 | 
            -
             | 
| 1037 | 
            -
             | 
| 1038 | 
            -
             | 
| 1039 | 
            -
             | 
| 1244 | 
            +
                var contentLength = content.get('length') ;
         | 
| 1245 | 
            +
             | 
| 1246 | 
            +
                // if extending, then we need to do some fun stuff to build the array
         | 
| 1247 | 
            +
                var selTop, selBottom, anchor ;
         | 
| 1248 | 
            +
                if (extend) {
         | 
| 1249 | 
            +
                  selTop = this._indexOfSelectionTop() ;
         | 
| 1250 | 
            +
                  selBottom = this._indexOfSelectionBottom() ;
         | 
| 1251 | 
            +
                  anchor = (this._selectionAnchor == null) ? selTop : this._selectionAnchor ;
         | 
| 1252 | 
            +
                  this._selectionAnchor = anchor ;
         | 
| 1253 | 
            +
             | 
| 1254 | 
            +
                  // If the selTop is before the anchor, then reduce the selection
         | 
| 1255 | 
            +
                  if (selTop < anchor) {
         | 
| 1256 | 
            +
                    selTop++ ;
         | 
| 1257 | 
            +
                    
         | 
| 1258 | 
            +
                  // otherwise, select the next item after the top 
         | 
| 1259 | 
            +
                  } else {
         | 
| 1260 | 
            +
                    selBottom++ ;
         | 
| 1261 | 
            +
                  }
         | 
| 1262 | 
            +
                  
         | 
| 1263 | 
            +
                  // Ensure we are not out of bounds
         | 
| 1264 | 
            +
                  if (selBottom >= contentLength) selBottom = contentLength-1;
         | 
| 1265 | 
            +
                  if (selTop > selBottom) selTop = selBottom ;
         | 
| 1266 | 
            +
                  
         | 
| 1267 | 
            +
                // if not extending, just select the item next to the selBottom
         | 
| 1268 | 
            +
                } else {
         | 
| 1269 | 
            +
                  selBottom = this._indexOfSelectionBottom() + 1;
         | 
| 1270 | 
            +
                  if (selBottom >= contentLength) selBottom = contentLength-1;
         | 
| 1271 | 
            +
                  selTop = selBottom ;
         | 
| 1272 | 
            +
                  anchor = null ;
         | 
| 1273 | 
            +
                }
         | 
| 1274 | 
            +
                
         | 
| 1275 | 
            +
                // now build array of new items to select
         | 
| 1276 | 
            +
                var items = [] ;
         | 
| 1277 | 
            +
                while(selTop <= selBottom) {
         | 
| 1278 | 
            +
                  items[items.length] = content.objectAt(selTop++) ;
         | 
| 1279 | 
            +
                }
         | 
| 1280 | 
            +
             | 
| 1281 | 
            +
                // ensure that the item is visible and set the selection
         | 
| 1282 | 
            +
                if (items.length > 0) {
         | 
| 1283 | 
            +
                  this.scrollToItemRecord(items.first());
         | 
| 1284 | 
            +
                  this.selectItems(items);
         | 
| 1285 | 
            +
                }
         | 
| 1286 | 
            +
                
         | 
| 1287 | 
            +
                this._selectionAnchor = anchor ;
         | 
| 1040 1288 | 
             
              },
         | 
| 1041 1289 |  | 
| 1042 1290 | 
             
              /**
         | 
| @@ -1075,12 +1323,25 @@ SC.CollectionView = SC.View.extend( | |
| 1075 1323 | 
             
                }
         | 
| 1076 1324 | 
             
              },
         | 
| 1077 1325 |  | 
| 1326 | 
            +
              /** 
         | 
| 1327 | 
            +
                Selects the passed array of items, optionally extending the
         | 
| 1328 | 
            +
                current selection.
         | 
| 1329 | 
            +
                
         | 
| 1330 | 
            +
                @param items {Array} The item or items to select.
         | 
| 1331 | 
            +
                @param extendSelection {Boolean} If true, extends the selection instead of replacing it.
         | 
| 1332 | 
            +
              */
         | 
| 1078 1333 | 
             
              selectItems: function(items, extendSelection) {
         | 
| 1079 1334 | 
             
                var base = (extendSelection) ? this.get('selection') : [] ;
         | 
| 1080 1335 | 
             
                var sel = [items].concat(base).flatten().uniq() ;
         | 
| 1336 | 
            +
                
         | 
| 1337 | 
            +
                // if you are not extending the selection, then clear the selection anchor.
         | 
| 1338 | 
            +
                this._selectionAnchor = null ;
         | 
| 1081 1339 | 
             
                this.set('selection',sel) ;  
         | 
| 1082 1340 | 
             
              },
         | 
| 1083 | 
            -
             | 
| 1341 | 
            +
             | 
| 1342 | 
            +
              /** 
         | 
| 1343 | 
            +
                Removes the items from the selection.
         | 
| 1344 | 
            +
              */
         | 
| 1084 1345 | 
             
              deselectItems: function(items) {
         | 
| 1085 1346 | 
             
                items = [items].flatten() ;
         | 
| 1086 1347 | 
             
                var base = this.get('selection') || [] ; 
         | 
| @@ -1093,6 +1354,73 @@ SC.CollectionView = SC.View.extend( | |
| 1093 1354 | 
             
              // EVENT HANDLING
         | 
| 1094 1355 | 
             
              //
         | 
| 1095 1356 |  | 
| 1357 | 
            +
              keyDown: function(evt) {
         | 
| 1358 | 
            +
                return this.interpretKeyEvents(evt) ;
         | 
| 1359 | 
            +
              },
         | 
| 1360 | 
            +
              
         | 
| 1361 | 
            +
              keyUp: function() { return true; },
         | 
| 1362 | 
            +
             | 
| 1363 | 
            +
              /** @private
         | 
| 1364 | 
            +
                Selects the same item on the next row.  Or moves down one if 
         | 
| 1365 | 
            +
                itemsPerRow = 1
         | 
| 1366 | 
            +
              */
         | 
| 1367 | 
            +
              moveDown: function(sender, evt) {
         | 
| 1368 | 
            +
                this.selectNextItem(false, this.get('itemsPerRow') || 1) ;
         | 
| 1369 | 
            +
                return true ;
         | 
| 1370 | 
            +
              },
         | 
| 1371 | 
            +
              
         | 
| 1372 | 
            +
              /** @private
         | 
| 1373 | 
            +
                Selects the same item on the next row.  Or moves up one if 
         | 
| 1374 | 
            +
                itemsPerRow = 1
         | 
| 1375 | 
            +
              */
         | 
| 1376 | 
            +
              moveUp: function(sender, evt) {
         | 
| 1377 | 
            +
                this.selectPreviousItem(false, this.get('itemsPerRow') || 1) ;
         | 
| 1378 | 
            +
                return true ;
         | 
| 1379 | 
            +
              },
         | 
| 1380 | 
            +
             | 
| 1381 | 
            +
              /** @private
         | 
| 1382 | 
            +
                Selects the previous item if itemsPerRow > 1.  Otherwise does nothing.
         | 
| 1383 | 
            +
              */
         | 
| 1384 | 
            +
              moveLeft: function(sender, evt) {
         | 
| 1385 | 
            +
                if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(false, 1) ;
         | 
| 1386 | 
            +
                return true ;
         | 
| 1387 | 
            +
              },
         | 
| 1388 | 
            +
             | 
| 1389 | 
            +
              /** @private
         | 
| 1390 | 
            +
                Selects the next item if itemsPerRow > 1.  Otherwise does nothing.
         | 
| 1391 | 
            +
              */
         | 
| 1392 | 
            +
              moveRight: function(sender, evt) {
         | 
| 1393 | 
            +
                if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(false, 1) ;
         | 
| 1394 | 
            +
                return true ;
         | 
| 1395 | 
            +
              },
         | 
| 1396 | 
            +
             | 
| 1397 | 
            +
              moveDownAndModifySelection: function(sender, evt) {
         | 
| 1398 | 
            +
                this.selectNextItem(true, this.get('itemsPerRow') || 1) ;
         | 
| 1399 | 
            +
                return true ;
         | 
| 1400 | 
            +
              },
         | 
| 1401 | 
            +
              
         | 
| 1402 | 
            +
              moveUpAndModifySelection: function(sender, evt) {
         | 
| 1403 | 
            +
                this.selectPreviousItem(true, this.get('itemsPerRow') || 1) ;
         | 
| 1404 | 
            +
                return true ;
         | 
| 1405 | 
            +
              },
         | 
| 1406 | 
            +
             | 
| 1407 | 
            +
              /** @private
         | 
| 1408 | 
            +
                Selects the previous item if itemsPerRow > 1.  Otherwise does nothing.
         | 
| 1409 | 
            +
              */
         | 
| 1410 | 
            +
              moveLeftAndModifySelection: function(sender, evt) {
         | 
| 1411 | 
            +
                if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(true, 1) ;
         | 
| 1412 | 
            +
                return true ;
         | 
| 1413 | 
            +
              },
         | 
| 1414 | 
            +
             | 
| 1415 | 
            +
              /** @private
         | 
| 1416 | 
            +
                Selects the next item if itemsPerRow > 1.  Otherwise does nothing.
         | 
| 1417 | 
            +
              */
         | 
| 1418 | 
            +
              moveRightAndModifySelection: function(sender, evt) {
         | 
| 1419 | 
            +
                if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(true, 1) ;
         | 
| 1420 | 
            +
                return true ;
         | 
| 1421 | 
            +
              },
         | 
| 1422 | 
            +
             | 
| 1423 | 
            +
              
         | 
| 1096 1424 | 
             
              /** 
         | 
| 1097 1425 | 
             
                Find the item view underneath the passed mouse location.
         | 
| 1098 1426 |  | 
| @@ -1177,6 +1505,9 @@ SC.CollectionView = SC.View.extend( | |
| 1177 1505 | 
             
                var mouseDownView    = this._mouseDownView = this.itemViewForEvent(ev);
         | 
| 1178 1506 | 
             
                var mouseDownContent = this._mouseDownContent = (mouseDownView) ? mouseDownView.get('content') : null;
         | 
| 1179 1507 |  | 
| 1508 | 
            +
                // become first responder if possible.
         | 
| 1509 | 
            +
                this.becomeFirstResponder() ;
         | 
| 1510 | 
            +
                
         | 
| 1180 1511 | 
             
                // recieved a mouseDown on the collection element, but not on one of the childItems... bail
         | 
| 1181 1512 | 
             
                if (!mouseDownView) {
         | 
| 1182 1513 | 
             
                  if (this.get('allowDeselectAll')) this.selectItems([], false);
         | 
| @@ -1235,8 +1566,6 @@ SC.CollectionView = SC.View.extend( | |
| 1235 1566 |  | 
| 1236 1567 | 
             
              _mouseUp: function(ev) {
         | 
| 1237 1568 |  | 
| 1238 | 
            -
                console.info('_mouseUp!');
         | 
| 1239 | 
            -
                
         | 
| 1240 1569 | 
             
                var canAct = this.get('actOnSelect') ;
         | 
| 1241 1570 | 
             
                var view = this.itemViewForEvent(ev) ;
         | 
| 1242 1571 |  | 
| @@ -1482,8 +1811,7 @@ SC.CollectionView = SC.View.extend( | |
| 1482 1811 | 
             
              // called on content change *and* content.[] change...
         | 
| 1483 1812 | 
             
              _contentPropertyObserver: function(target,key,value)
         | 
| 1484 1813 | 
             
              {
         | 
| 1485 | 
            -
                if (!this._updating)
         | 
| 1486 | 
            -
                {
         | 
| 1814 | 
            +
                if (!this._updating) {
         | 
| 1487 1815 | 
             
                  this._updating = true;
         | 
| 1488 1816 | 
             
                  this.set('isDirty',true);
         | 
| 1489 1817 | 
             
                  this._resetExpiredRender();
         |