<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Ember Virtual List Component</title> <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css"> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="http://builds.emberjs.com/tags/v1.13.5/ember-template-compiler.js"></script> <script src="http://builds.emberjs.com/tags/v1.13.5/ember.debug.js"></script> <style id="jsbin-css"> /* Put your CSS here */ html, body { margin: 20px; box-sizing: border-box; } h1, h2 { font-weight: normal; } .x-virtual-list__container { width: 300px; height: 500px; outline: 1px solid black; overflow: scroll; position: relative; } .x-virtual-list__scroller { position: absolute; background-color: blue; width: 100%; } .x-virtual_list__item { position: absolute; outline: 1px solid white; height: 40px; width: calc(100% - 10px); line-height: 40px; padding-left: 10px; color: white; background-color: green; } .x-virtual_list__item2 { outline: 1px solid white; height: 40px; width: calc(100% - 10px); line-height: 40px; padding-left: 10px; color: white; background-color: green; pointer-events: none; } .x-virtual-list__debug { list-style-type: none; padding-left: 0; } </style> </head> <body> <script type="text/x-handlebars"> <h1>Scroll Scroll</h1> {{outlet}} </script> <script type="text/x-handlebars" id="components/x-virtual-list-item"> {{item.name}} </script> <script type="text/x-handlebars" id="components/x-virtual-list"> <div class="x-virtual-list__container"> <div class="x-virtual-list__scroller" style={{scrollerStyle}}> <div class="x-virtual-list__positioner" style={{positionerStyle}}> {{#each visibleRows key='rowKey' as |row|}} <div class="x-virtual_list__item2" {{action 'log'}}>{{component itemComponent item=row.item}}</div> {{/each}} </div> </div> </div> <ul class="x-virtual-list__debug"> <li>scroll offset: {{scrollOffset}}</li> <li>visible elements: {{visibleRows.length}}</li> <li>start offset: {{startOffset}}</li> <li>end offset: {{endOffset}}</li> <li>start index: {{startIndex}}</li> <li>end index: {{endIndex}}</li> </ul> </script> <script type="text/x-handlebars" data-template-name="index"> {{x-virtual-list items=model}} <div class="x-virtual-list__container"> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> </div> </script> <script id="jsbin-javascript"> App = Ember.Application.create(); App.Router.map(function() { // put your routes here }); App.IndexRoute = Ember.Route.extend({ model: function() { var people = [ 'Bill', 'Zach', 'Seung', 'Kapil', 'Tom', 'Ren', 'George', 'Ed', 'Ben', 'Curtis', 'Allie', 'Tao', 'David', 'Gian', 'Clifton', 'Richard', 'Max', 'Tony', 'Kiran', 'Mike', 'Brian', 'Joe', 'Matt', 'Alex', 'Jeremy', 'Travis' ]; var items = []; for (var i = 0; i < 1000; i++) { var person = people[Math.floor(Math.random() * people.length)]; items.push({ name: (i+1) + ". " + person }); } return items; } }); App.XVirtualListComponent = Ember.Component.extend({ rowHeight: 40, scrollOffset: 0, containerHeight: 0, itemComponent: 'x-virtual-list-item', visibleRows: function() { var rowHeight = this.get('rowHeight'); var startIndex = this.get('startIndex'); var endIndex = this.get('endIndex'); return this.get('items').slice(startIndex, endIndex).map(function(item, i) { var index = i + startIndex; var top = (rowHeight * index); return { rowKey: index, item: item, }; }); }.property('items', 'rowHeight', 'startIndex', 'endIndex'), positionerStyle: function() { var top = this.get('rowHeight') * this.get('startIndex'); return new Ember.Handlebars.SafeString("transform: translateY(" + top + "px);"); }.property('startIndex', 'rowHeight'), startOffset: function() { return Math.abs(this.get('scrollOffset')) / this.get('rowHeight'); }.property('scrollOffset', 'containerHeight', 'rowHeight'), endOffset: function() { return (Math.abs(this.get('scrollOffset')) + this.getWithDefault('containerHeight', 0)) / this.get('rowHeight'); }.property('scrollOffset', 'containerHeight', 'rowHeight'), startIndex: function() { return Math.floor(this.get('startOffset')); }.property('startOffset'), endIndex: function() { return Math.ceil(this.get('endOffset')); }.property('endOffset'), scrollerStyle: function() { var height = this.get('items.length') * this.get('rowHeight'); var style = "height: " + height + "px"; return new Ember.Handlebars.SafeString(style); }.property('items.length', 'rowHeight'), hookupScroll: function() { var component = this; this.outer().bind('scroll', function() { Ember.run.once(component, function() { var containerOffset = this.outer().offset().top; var scrollerOffset = this.inner().offset().top; var scrollOffset = scrollerOffset - containerOffset; this.set('scrollOffset', scrollOffset); }); }); }.on('didInsertElement'), readySetGo: function() { Ember.run.later(this, function() { this.set('containerHeight', this.outer().height()); }); }.on('didInsertElement'), outer: function() { return this.$().find('.x-virtual-list__container'); }, inner: function() { return this.$().find('.x-virtual-list__scroller'); }, actions: { log: function() { alert("hello"); } }, }); </script> <script id="jsbin-source-html" type="text/html"><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Ember Virtual List Component</title> <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css"> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"><\/script> <script src="http://builds.emberjs.com/tags/v1.13.5/ember-template-compiler.js"><\/script> <script src="http://builds.emberjs.com/tags/v1.13.5/ember.debug.js"><\/script> </head> <body> <script type="text/x-handlebars"> <h1>Scroll Scroll</h1> {{outlet}} <\/script> <script type="text/x-handlebars" id="components/x-virtual-list-item"> {{item.name}} <\/script> <script type="text/x-handlebars" id="components/x-virtual-list"> <div class="x-virtual-list__container"> <div class="x-virtual-list__scroller" style={{scrollerStyle}}> <div class="x-virtual-list__positioner" style={{positionerStyle}}> {{#each visibleRows key='rowKey' as |row|}} <div class="x-virtual_list__item2" {{action 'log'}}>{{component itemComponent item=row.item}}</div> {{/each}} </div> </div> </div> <ul class="x-virtual-list__debug"> <li>scroll offset: {{scrollOffset}}</li> <li>visible elements: {{visibleRows.length}}</li> <li>start offset: {{startOffset}}</li> <li>end offset: {{endOffset}}</li> <li>start index: {{startIndex}}</li> <li>end index: {{endIndex}}</li> </ul> <\/script> <script type="text/x-handlebars" data-template-name="index"> {{x-virtual-list items=model}} <div class="x-virtual-list__container"> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> <div class="x-virtual_list__item2"> Hello </div> </div> <\/script> </body> </html> </script> <script id="jsbin-source-css" type="text/css">/* Put your CSS here */ html, body { margin: 20px; box-sizing: border-box; } h1, h2 { font-weight: normal; } .x-virtual-list__container { width: 300px; height: 500px; outline: 1px solid black; overflow: scroll; position: relative; } .x-virtual-list__scroller { position: absolute; background-color: blue; width: 100%; } .x-virtual_list__item { position: absolute; outline: 1px solid white; height: 40px; width: calc(100% - 10px); line-height: 40px; padding-left: 10px; color: white; background-color: green; } .x-virtual_list__item2 { outline: 1px solid white; height: 40px; width: calc(100% - 10px); line-height: 40px; padding-left: 10px; color: white; background-color: green; pointer-events: none; } .x-virtual-list__debug { list-style-type: none; padding-left: 0; }</script> <script id="jsbin-source-javascript" type="text/javascript">App = Ember.Application.create(); App.Router.map(function() { // put your routes here }); App.IndexRoute = Ember.Route.extend({ model: function() { var people = [ 'Bill', 'Zach', 'Seung', 'Kapil', 'Tom', 'Ren', 'George', 'Ed', 'Ben', 'Curtis', 'Allie', 'Tao', 'David', 'Gian', 'Clifton', 'Richard', 'Max', 'Tony', 'Kiran', 'Mike', 'Brian', 'Joe', 'Matt', 'Alex', 'Jeremy', 'Travis' ]; var items = []; for (var i = 0; i < 1000; i++) { var person = people[Math.floor(Math.random() * people.length)]; items.push({ name: (i+1) + ". " + person }); } return items; } }); App.XVirtualListComponent = Ember.Component.extend({ rowHeight: 40, scrollOffset: 0, containerHeight: 0, itemComponent: 'x-virtual-list-item', visibleRows: function() { var rowHeight = this.get('rowHeight'); var startIndex = this.get('startIndex'); var endIndex = this.get('endIndex'); return this.get('items').slice(startIndex, endIndex).map(function(item, i) { var index = i + startIndex; var top = (rowHeight * index); return { rowKey: index, item: item, }; }); }.property('items', 'rowHeight', 'startIndex', 'endIndex'), positionerStyle: function() { var top = this.get('rowHeight') * this.get('startIndex'); return new Ember.Handlebars.SafeString("transform: translateY(" + top + "px);"); }.property('startIndex', 'rowHeight'), startOffset: function() { return Math.abs(this.get('scrollOffset')) / this.get('rowHeight'); }.property('scrollOffset', 'containerHeight', 'rowHeight'), endOffset: function() { return (Math.abs(this.get('scrollOffset')) + this.getWithDefault('containerHeight', 0)) / this.get('rowHeight'); }.property('scrollOffset', 'containerHeight', 'rowHeight'), startIndex: function() { return Math.floor(this.get('startOffset')); }.property('startOffset'), endIndex: function() { return Math.ceil(this.get('endOffset')); }.property('endOffset'), scrollerStyle: function() { var height = this.get('items.length') * this.get('rowHeight'); var style = "height: " + height + "px"; return new Ember.Handlebars.SafeString(style); }.property('items.length', 'rowHeight'), hookupScroll: function() { var component = this; this.outer().bind('scroll', function() { Ember.run.once(component, function() { var containerOffset = this.outer().offset().top; var scrollerOffset = this.inner().offset().top; var scrollOffset = scrollerOffset - containerOffset; this.set('scrollOffset', scrollOffset); }); }); }.on('didInsertElement'), readySetGo: function() { Ember.run.later(this, function() { this.set('containerHeight', this.outer().height()); }); }.on('didInsertElement'), outer: function() { return this.$().find('.x-virtual-list__container'); }, inner: function() { return this.$().find('.x-virtual-list__scroller'); }, actions: { log: function() { alert("hello"); } }, });</script></body> </html>