Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="container"></div>
              
            
!

CSS

              
                body{
  margin: 0;
}

*{
  box-sizing: border-box;
}

#container{
  max-width: 480px;
  height: 100vh;
  background: #f5f5f5;
  margin: 0px auto;
}

.Number{
  transform: rotateZ(0deg);
  height: 120px;
  background: #fff;
  border-bottom: 1px solid #d8d8d8;
  font-family: Helvetica;
  line-height: 120px;
  font-size: 48px;
  text-align: center;
}

.ListView{
  height: 100%;
  overflow-y: auto;
}

.ListView--Inner{
  display: flex;
  flex-direction: column;
}

.Random{
  height: 540px;
  background: #fff;
  padding: 0px 10px;
}

.Random--Image{
  width: 443px;
  height: 300px;
}
.Random--Details{
  height: 240px;
}
.Random--Title{
  height: 40px;
  font-size: 18px;
  line-height: 40px;
  font-family: 'Helvetica';
}

.Random--Content{
  font-size: 16px;
  height: 200px;
  font-family: 'Helvetica';
  text-align: justify;
}
              
            
!

JS

              
                
let Component = React.Component;
let __unImpl  = methodName =>  {
  throw new Error('Unimplemented method' + methodName);
}

class DataSource{
  
  constructor(){
    if( this.constructor === DataSource.prototype.constructor ){
      throw new Error('Abstract class, don\'t create object of this');    
    }
  }
  
  getItemAtIndex(index){
    __unImpl('Promise getItemAtIndex(index)');
  }
}

class VirtualDataSource extends DataSource{
  constructor(){
    super();
    if( this.constructor === VirtualDataSource.prototype.constructor ){
      throw new Error('Abstract class', this.constructor.name ,' don\'t create object of this class directly');    
    }
    this.cache = new LRUCache();
  }
  
  getSetForIndex(){
    
  }
  getItemsAtIndex(){
    __unImpl('getItemsAtIndex()')
  }
  getItemAtIndex(index){
    let el = this.cache.get(index);
    if( el ){
      return el;
    }
    return getSetForIndex();
  }
  
  getLength(){
    
  }
  
  // Somehow re-use the original one ?
  // composition does sounds more promising
  // by creating 2 seperate elements 
  // but meh.
  get length(){
    return this.getLength();
  }
}

// This is most likely an overkill.
// and will make benjamin sad.
class DataSourceElementError{
  constructor(type, message){
    this._type = type;
  }
  
  get type(){
    return this._type;
  }
};

DataSource.Errors = {
  IndexOutOfBound : new DataSourceElementError('IndexOutOfBound'),
};

// A pseudo asynchronous loading dataSource
class LoremPizzaDataSource extends DataSource{
  constructor(length){
    super();
    this._max = length;
  }

  getItemAtIndex(index){
    return index < this._max ? {
      name: 'Sir Lemon sponsored image #' + index,
      photo: 'http://lorempizza.com/480/400/?t=' + (Math.random() * Date.now())
    } : DataSource.IndexOutOfBound;
  }
  
  get length(){
    return this._max;
  }
};

class Feed extends DataSource{
  constructor(length){
    super();
    this._max = length;
  }
  
  getItemAtIndex(index){
    if( index < 0 || index > this._max ){
       return DataSource.IndexOutOfBound;
    }
    return {
      title: 'Title #' + index,
      image: 'http://lorempixel.com/443/400/?t=' + index + '.' + Math.random(),
      descp: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
    }
  }
  
  get length(){
    return this._max;
  }
}
class NumberLine extends DataSource{
  constructor(length){
    super();
    this._max = length;
  }

  getItemAtIndex(index){
    return index < this._max ? {index} : DataSource.IndexOutOfBound;
  }
  
  get length(){
    return this._max;
  }
};

// @Internal
// @Example
// <ListViewElement renderer data height order/>
class ListViewElement extends Component{
  constructor(props){
    super(props);
    this.state = {};
    
    if( props.element instanceof Promise ){
      this.state.isPromised = true;
      props.data.then(data => this.setState({data}));
      return;
    }
    this.state.data = this.props.data;
  }
  
  render(){
    let styling = {
      height: this.props.height,
      order : this.props.order
    };
        
    return <div className="ListView--Element" style={styling}>
              <this.props.renderer data={this.props.data} />
           </div>;
  }
}

/* 
  @class : Ring
   @desc  : A circular linked list like data structure
            only implemented as per our needs no need to
            implement all methods. you must not play 
            with this._elements treat it as pure immutable 
            datastructure.
*/
class Ring{
  constructor(length){
    // For link list
    // now the problem is how to make the 
    // constructor work faster better stronger
    this._start = null;
    this._originalStart = null;
    this._originalEnd   = null;
    this._end   = null;
  }

  _wrap(data, next, prev){
    return {data, next, prev};
  }
  
  push(data){
    let el = this._wrap(data, this._start, this._end);
    
    if( this._end ){
      this._end.next = el;
    }
    this._originalEnd = this._end = el;  
    
    if( ! this._start ){
      this._originalStart = this._start = this._end;
    }
    this._adjust();
  }  
  
  _adjust(){
    this._end.next = this._start;
    this._start.prev = this._end;
  }
  
  pop(){
    this._end = this._end.prev;
    this._adjust();
  }
  
  shift(){
    this._start = this._start.next;
    this._adjust();
  }

  // just for the lulz
  unshift(data){
    let el = this._wrap(data, this._start, this._end);
    if( this._start ){
      this._start.prev = el;
    }
    this._start = el;
    if( ! this._end ){
      this._end = this._start;
    }
    this._adjust();
  }
  
  turnAntiClockwise(times, visitFn){
    var el = null;
    for( let i = 0; i < times; i++ ){
      el = this._end;
      this._start = el;
      this._end    = el.prev;
      visitFn && visitFn(el.data, i);
    }
  }
  
  turnClockwise(times, visitFn){
    var el = null;
    for( let i = 0; i < times; i++ ){
      el = this._start;
      this._end = el;
      this._start = el.next;
      visitFn && visitFn(el.data, i);
    }
  }

  // travellers work irrespective
  // of teh alignment
  travelClockwise(visitor){
    let el = this._originalStart;
    let i  = 0;
    do{
      visitor(el, i++);
      el = el.next;
    }while(el !== this._originalStart);
  }
  
  travelAnticlockwise(visitor){
    let el = this._originalEnd;
    let i = 0;
    do{
      visitor(el, i);
      el = el.prev;
    }while(el !== this._originalEnd);
  }
  
  mapClockwise(visitor){
    let el = this._originalStart,i = 0, arr = [];
    do{
      arr.push(visitor(el.data, i++));
      el = el.next;
    }while(el !== this._originalStart);
    return arr;
  }
  
  mapAntiClockwise(visitor){
    let el = this._originalEnd, i = 0, arr = [];
    do{
      arr.push(visitor(el.data, i++));
      el = el.prev;
    }while(el !== this._originalEnd);
    return arr;
  }
};
/* @Component
   @name: ListView
   
   @attributes
     
     @attribute 
      @name: elementRenderer
      @type: React.Component
      @desc: Reference to the class to render components.
     
     @attribute
      @name: backDrop
      @optional: true
      @type: React.Component
      @desc: In case the data is to be queried online this will
             render a loading state, defaults to DefaultBackProp
      
     @attribute
      @name: dataSource
      @type: ListView.DataSource
      @desc: A datasource from which items will be generated
             in case the data is to be queried a promise may 
             be returned, the listview will wait this can be
             a use case for querying items dynamically like
             UITableView
             
     @attribute: 
      @name: elementHeight
      @type: integer
      @desc: Height of individual element to be rendered,
             this will be passed on to the containers.
    
     @attribute: 
      @NOT_IMPLEMENTED
      @name: layout
      @type: ItemRendererClass
 */
class ListView extends Component{
  constructor(props){
    super(props);
    this.state = { 
      range: {
         startPoint: -1,
         endPoint  : -1,
         inViewPort:  0,
         totalElements: 0,
         elements  : []
      }
    };
  }
  shouldSkipRender(r1,r2){
    return r1.totalElements === r2.totalElements && r1.startPoint === r2.startPoint && r1.endPoint === r2.endPoint; 
  }
  isDiffLen(range1, range2){
    return range1.totalElements != range2.totalElements;
  }
  isReusable(range1, range2){
    //why ? because fuck redability thats why
    return  range1.startPoint > range2.startPoint ? range1.startPoint < range2.endPoint : range1.endPoint > range2.startPoint;
  }
  getRangeOfElements(scrollTop, viewPortHeight, elementHeight){
    let inViewPort    = Math.ceil(viewPortHeight/elementHeight);
    let outOfViewPort = 1;
    let startPoint    = Math.max(Math.floor(scrollTop / elementHeight) - outOfViewPort, 0);
    let totalElements = inViewPort + outOfViewPort + outOfViewPort;
    let endPoint      = startPoint + totalElements;
    let length  = this.props.dataSource.length;
    if( endPoint >= length ){
      startPoint  -= outOfViewPort;
      endPoint = length - 1;
    }
    return {startPoint, endPoint, inViewPort, totalElements};
  }
  computeBoundsAndUpdateRange(scrollWrapperNode){
    // maybe get this from .detail ?
    let scrollTop      = scrollWrapperNode.scrollTop;
    // maybe cache this ?
    let viewPortHeight = scrollWrapperNode.offsetHeight;    
    let {elementHeight, elementRenderer, dataSource} = this.props;
    let newRange = this.getRangeOfElements(scrollTop, viewPortHeight, elementHeight);
    let currentRange = this.state.range;
    if( this.shouldSkipRender(newRange, currentRange)){
      return;
    }
    // first render in view, in this case lets just drop
    // the view and buildFromScratch
    
    let elements;
    if( !this.isDiffLen(newRange, currentRange) && this.isReusable(newRange, currentRange) ){
      elements = currentRange.elements;
      let offset   = newRange.startPoint - currentRange.startPoint;
      let goingUp  = offset < 0;
      let startPoint = goingUp ? newRange.startPoint : newRange.endPoint;
      let length = dataSource.length;
      let noop = (s) => {};
      
      let mapData = (element, index) => {
          let itemIndex = goingUp ? startPoint + index : startPoint - index;
          if( length < itemIndex ){ return element.node = '';};
          element.node = <ListViewElement key={element.i} order={itemIndex} data={dataSource.getItemAtIndex(itemIndex)} height={elementHeight} renderer={elementRenderer} />;
      }
      
      if( goingUp ){
        elements.turnAntiClockwise( -offset, mapData );
      }else{
        elements.turnClockwise(offset, mapData);
      }            
      // how many blocks
    }else{
      // create a fucking new list react will save us anyway
      elements = new Ring();
      let node = null;
      for( let i = newRange.startPoint ; i <= newRange.endPoint ; i++ ){
        node = <ListViewElement key={i} order={i} data={dataSource.getItemAtIndex(i)} height={elementHeight} renderer={elementRenderer} />;
        elements.push({node, i});
      }
    }

    newRange.elements = elements;
    this.setState({
      isReady: true,
      range: newRange
    });
 }
  componentDidMount(){
    // give browser time to sync ui
    setTimeout( t => {
      let scrollWrapper = React.findDOMNode(this);
      this.computeBoundsAndUpdateRange(scrollWrapper);
    }, 100);
  }
  prepareRenderedArray(){
    let range = this.state.range;

    return range.elements.mapClockwise( (item) => {
      return item.node;
    });
  }           
  handleScroll(e){
    let scrollWrapperNode = e.target;
    this.computeBoundsAndUpdateRange(scrollWrapperNode)
  }
  
  render(){
    // @todo: find better method.
    if( !this.state.isReady ){
      return <div className="ListView"><div className="ListView--Inner"></div></div>;
    }
    
    let totalHeight = this.props.dataSource.length * this.props.elementHeight;
    let paddingTop  = this.props.elementHeight * this.state.range.startPoint;
    let innerStyle = {
       minHeight: totalHeight,
       paddingTop: paddingTop
    };

    return <div className="ListView" onScroll={e => this.handleScroll(e)}>
              <div className="ListView--Inner" style={innerStyle}>
                {this.prepareRenderedArray()}
              </div>
           </div>;
  }
};


class SNumber{
  render(){
    return <div className="Number">
              
              {this.props.data.index}
           </div>;
  }
}

class Random{
  render(){
    let random = this.props.data;
    return <div className="Random">
              <img className="Random--Image" src={random.image} />
              <div className="Random--Details">
                 <h2 className="Random--Title">{random.title}</h2>
                 <p className="Random--Content">{random.descp}</p>
              </div>
           </div>
  }
}

class Test extends Component{
  constructor(props){
    super(props);
    this.dataSource = new Feed(1000);
  }
  render(){
    return <ListView 
              dataSource={this.dataSource} 
              elementRenderer={Random}
              elementHeight={540}
           />;
  }
}


React.render(<Test />, document.getElementById("container"));


              
            
!
999px

Console