<file_sep>var _ = require('lodash');

var DataActionCreators = require('../actions/DataActionCreators');
var DataStore = require('../stores/DataStore.js');

module.exports = {

  getFeatures: () => {
    var fetch = ['c_DueDate', 'Name', 'Owner', 'ScheduleState'];
    
    Ext.create('Rally.data.WsapiDataStore', {
      limit: Infinity,
      model: 'PortfolioItem/Feature',
      fetch: fetch
    }).load({
      callback: (features) => {
        _.each(features, (feature) => {
          feature.getCollection('UserStories').load({
            fetch: fetch,
            callback: (stories) => {
              var featureData = feature.data;
              featureData.UserStories = _.pluck(stories, 'data');
              DataActionCreators.receiveFeature(featureData);
            },
            scope: this
          })
        }, this);
      },
      scope: this
    });
  },

  updateField: (id, fieldName, value) => {
    console.log('Updating ' + id + ': [' + fieldName + '] ' + value);

    return new Promise((resolve, reject) => {
      Rally.data.ModelFactory.getModel({
        type: 'UserStory',
        success: (model) => {
          model.load(id, {
              fetch: [value],
              callback: (record, operation) => {
                // record.set(fieldName, value);
                record.save({
                  callback: (result, operation) => {
                    if (operation.wasSuccessful()) {
                      resolve();
                    }
                  }
                });
              }
          });
        }
      });
    });
  }

};<file_sep>/*** @jsx React.DOM */

var _ = require('lodash');

var React = require('react');
var ReactAddons = require('react-addons');
var ListItem = require('./ListItem');

var List = React.createClass({
  getInitialState: function() {
    return {
      expanded: true,
      newItem: false
    }
  },

  render: function() {
    var panelId = _.uniqueId('panel-');
    var uncompletedItems = _.filter(this.props.items, { complete: false });
    var completedItems = _.filter(this.props.items, { complete: true });

    return (
      <div className="list panel-group">
        <div className="panel">
          <div className="panel-heading" data-toggle="collapse" data-target={ '#' + panelId } onClick={ this.toggleState.bind(this, 'expanded') }>
            <h4 className="panel-title">
                <span>{ this.props.text }</span>
                <span className={ this.getChevronIconClasses() }></span>
            </h4>
          </div>
          <div id={ panelId } className={ this.getPanelClasses() }>
            <div className="panel-body">
              { _.map(uncompletedItems, this.createListItemElement) }
              { this.state.newItem ? this.createListItemElement() : this.getAddNewButton() }
            </div>
          </div>
        </div>
      </div>
    );
  },

  createListItemElement: function(item, onChangeFn) {
    return (
      <ListItem
        data={ item }
        hideAvatars={ this.props.hideAvatars }
        onStateChange={ this.props.onStateChange } 
        hideDates={ this.props.hideDates }
      />
    );
  },

  getChevronIconClasses: function() {
    return ReactAddons.classSet({
      'icon': true,
      'icon-chevron-up': this.state.expanded,
      'icon-chevron-down': !this.state.expanded,
      'pull-right': true
    });
  },

  getPanelClasses: function() {
    return ReactAddons.classSet({
      'panel-collapse': true,
      'collapse': true,
      'in': this.state.expanded
    })
  },

  getAddNewButton: function() {
    return (
      <button type="button" className="btn btn-default btn-sm add-new" onClick={ this.addNewItem }>
        <span className="icon icon-add"></span> Add New
      </button>
    );
  },

  addNewItem: function() {
    this.toggleState('newItem');
  },

  toggleState: function(key) {
    var newState = {};
    newState[key] = !this.state[key];
    this.setState(newState);
  }
});

module.exports = List;<file_sep>/*** @jsx React.DOM */

var moment = require('moment');

var React = require('react');
var ReactAddons = require('react-addons');
var WSAPI = require('../apis/WSAPI');

var ListItem = React.createClass({
  propTypes: {
    data: React.PropTypes.object.isRequired,
    hideDates: React.PropTypes.bool,
    hideAvatars: React.PropTypes.bool
  },

  getInitialState: function() {
    return {
      text: this.props.data.text
    };
  },

  getDefaultProps: function() {
    return {
      data: {}
    };
  },

  onFieldUpdated: function(field, value) {
    this.props.onStateChange(this.props.id, field, value);
  },

  handleChecked: function(e) {
    this.onFieldUpdated('complete', e.target.checked);
  },

  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },

  updateTaskName: function(e) {
    WSAPI.updateField(this.props.data.id, 'Name', this.state.text)
      .then(this.flairItem);
  },

  componentDidMount: function () {
    if (!this.props.data.text) {
      this.refs.input.getDOMNode().focus();
    }
  },

  flairItem: function() {
    debugger;
  },

  getAvatar: function(url) {
    var hideAvatarClass = this.props.hideAvatars ? ' hidden' : '';

    return (
      <div className="avatar">
        <span className="icon icon-user" />
      </div>
    );
  },

  getDateIcon: function(date) {
    return (
      <div className="date">
        <span className="icon icon-calendar" />
        <span className="text">{ date && this.formatDate(date) || 'assign due date' }</span>
      </div>
    );
  },

  formatDate: function(date) {
    var daysRemaining = moment(date).endOf('day').diff(moment(), 'days');

    if (daysRemaining < 0) {
      return 'Past Due (' + moment(date).format('l') + ')';
    } else if (daysRemaining === 0) {
      return 'due Today';
    } else if (daysRemaining === 1) {
      return 'due Tomorrow';
    } else {
      return 'due ' + moment(date).format('l');
    }
  },

  render: function() {
    var item = this.props.data;
    
    return (
      <div className="list-item">
        <div className="row">
          <label className="checkbox-label col-md-1">
            <input className="checkbox" type="checkbox" defaultChecked={ item.complete } onChange={ this.handleChecked } />
          </label>

          <div className="col-md-10">
            <input
              className={ "textbox complete-" + item.complete } 
              value={ this.state.text }
              placeholder="New Item" 
              onChange={ this.updateText } 
              onBlur={ this.updateTaskName }
              ref="input"
            />
          </div>

          <span className="icon icon-trash tool" />
          <span className="icon icon-pencil tool" />
        </div>


        <div className="row">
          <div className="col-md-1" />
          <div className="col-md-11">
            { this.getAvatar(item.avatarUrl) }
            { this.getDateIcon(item.date) }
          </div>
        </div>
      </div>
    );
  }
});

module.exports = ListItem;<file_sep>var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var AppConstants = require('../constants/AppConstants');
var merge = require('react/lib/merge');

var _data = {
  items: [{
    Name: 'Mock To-Do List',
    UserStories: [{
      Name: 'Task 0',
      c_DueDate: new Date('2015-1-3')
    },{
      Name: 'Task 1',
      c_DueDate: new Date('2015-1-6')
    },{
      Name: 'Task 2',
      c_DueDate: new Date('2015-1-7')
    },{
      Name: 'Task 3',
      c_DueDate: new Date('2020-1-1')
    }]
  }]
};

var DataStore = merge(EventEmitter.prototype, {

  addChangeListener: function(callback) {
    this.on(AppConstants.CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(AppConstants.CHANGE_EVENT, callback);
  },

  emitChange: function() {
    this.emit(AppConstants.CHANGE_EVENT);
  },

  _getListItemData: function(item) {
    return {
      id: item.ObjectID,
      text: item.Name,
      complete: item.ScheduleState === 'Accepted',
      date: item.c_DueDate
    };
  },

  getListItems: function() {
    return _.map(_data.items, function(item) {
      var parsedItem = this._getListItemData(item);
      parsedItem.items = _.map(item.UserStories, this._getListItemData, this);
      return parsedItem;
    }, this);
  },

  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;

    switch(action.type) {
      case AppConstants.ActionTypes.RECEIVE_ITEM:
        _data.items.push(action.item);
        DataStore.emitChange();
        break;
    }
  })

});

module.exports = DataStore;<file_sep>var gulp = require('gulp');
var sass = require('gulp-sass');
var traceur = require('gulp-traceur');
var browserify = require('gulp-browserify');
var concat = require('gulp-concat');

gulp.task('sass', function () {
  gulp.src('src/sass/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('dist/css'));
});

gulp.task('js', function() {
  gulp.src('src/js/index.js')
    .pipe(browserify({transform: 'reactify'}))
    .pipe(concat('index.js'))
    .pipe(traceur())
    .pipe(gulp.dest('dist/js'));
});

gulp.task('copy', function() {
  gulp.src('src/index.html')
    .pipe(gulp.dest('dist'));
});

gulp.task('build', ['sass', 'js', 'copy']);

gulp.task('watch', ['build'], function() {
  gulp.watch('src/**/*.*', ['build']);
});

gulp.task('default', ['build']);
<file_sep>/*** @jsx React.DOM */

var React = require('react');
var DataStore = require('../stores/DataStore');
var ActionCreator = require('../actions/DataActionCreators');
var WSAPI = require('../apis/WSAPI');
var List = require('./List');

var App = React.createClass({
  componentDidMount: function() {
    DataStore.addChangeListener(this._onChange);
    Rally.onReady(this._onRallyReady);
  },

  componentWillUnmount: function() {
    DataStore.removeChangeListener(this._onChange);
  },

  getInitialState: function() {
    return {
      listItems: []
    };
  },

  _onRallyReady: function() {
    WSAPI.getFeatures();
  },

  _onChange: function() {
    this.setState({
      listItems: DataStore.getListItems()
    });
  },

  render: function() {
    return (
      <div className="row">
        <div className="col-md-8"></div>
        <div className="col-md-4">
          { _.map(this.state.listItems, List) }
        </div>
      </div>
    );
  }
});

module.exports = App;
