<commit_before>'use strict';

//// CONSTRUCTORS

function Dog (name, options) {
   options = options || {};
   this.name = name;
   this.color = options.color;  // not used
   this.age = options.age;  // not used
   this.breeds = options.breeds || 'mutt';  // not used
   this.weight = options.weight;  // not used
   this.barkVol = options.barkVol || 5;  // not used
   this.avatar = options.avatar;
   this.lick = function(adopter) {
       return adopter.licked = true;
   }  // not used
   this.cuddle = function(adopter) {
       return adopter.cuddled = true;
   };  // not used
   this.bark = function(adopter) {
       return adopter.barkedAt = true;
   }  // not used
}

function Adopter (name, options) {
   options = options || {};
   this.name = name;
   this.licked = false;  // not used
   this.cuddled = false;  // not used
   this.tolLick = options.tolLick || 5;
   this.tolCuddle = options.tolCuddle || 5;
   this.tolBark = options.tolBark || 5;
   this.prefAge = options.prefAge;  // not used
   this.avatar = options.avatar;
}

//// GLOBAL VARIABLES

// DOGS

var Moksha = new Dog("Moksha",{
    color: ["brown","white"],
    age: 4,
    avatar: 'https://fbcdn-sphotos-g-a.akamaihd.net/hphotos-ak-xpa1/t31.0-8/1932629_10152418870378352_6293108892780372302_o.jpg'
});

var Emmitt = new Dog("Emmitt",{
    color: ["yellow"],
    age: 5,
    avatar: 'https://fbcdn-sphotos-h-a.akamaihd.net/hphotos-ak-xap1/t1.0-9/60479_432375844549_1903986_n.jpg'
});

var Bella = new Dog("Bella",{
    color: ["black"],
    age: 2,
    avatar: 'https://scontent-b-lga.xx.fbcdn.net/hphotos-xfp1/t1.0-9/4541_89487809549_852660_n.jpg'
});

var Herman = new Dog("Herman",{
    color: ["black","white"],
    age: 6,
    avatar: 'http://i.imgur.com/s7mpWGj.jpg'
});

var Dogs = ([Moksha,Emmitt,Bella,Herman]);

var dogNames = Dogs.map(function (dog) {
    return dog.name;
});

function populateDogChoices(doglist){
    var dogSelectView = $('#dog-select-template').html();
    for (var i = 0; i < (_.size(doglist)); i++)
        {
            $('.dog-choices').append(_.template(dogSelectView,({"imgURL": doglist[i].avatar, "dogname": doglist[i].name, "dogindex":i})));
        }
}

// ADOPTERS

var Dave = new Adopter("Dave",{
    tolLick: 7,
    tolCuddle: 4,
    tolBark: 5,
    avatar: 'https://dge9rmgqjs8m1.cloudfront.net/global/6e784a56292505372595b9023b9cdc970010/original.6e784a56292505372595b9023b9cdc970010.gif'
});

var Emily = new Adopter("Emily",{
    tolLick: 4,
    tolCuddle: 6,
    tolBark: 3,
    avatar: 'https://asset1.basecamp.com/1940253/people/8112581/photo/avatar.96.gif'
})

var Julia = new Adopter("Julia",{
    tolLick: 10,
    tolCuddle: 3,
    tolBark: 7,
    avatar: 'https://avatars0.githubusercontent.com/u/955558?s=460'
})

var Talal = new Adopter("Talal",{
    avatar: 'https://avatars1.githubusercontent.com/u/3066028?s=400'
})

var Adopters = ([Dave,Emily,Julia,Talal]);

var adopterNames = Adopters.map(function (adopter) {
    return adopter.name;
});

function populateAdopterChoices(adopterlist){
    var adopterSelectView = $('#adopter-select-template').html();
    for (var i = 0; i < (_.size(adopterlist)); i++)
        {
            $('.adopter-choices').append(_.template(adopterSelectView,({"imgURL": adopterlist[i].avatar, "adoptername": adopterlist[i].name})));
        }
}

// DEFAULT POINT VALUES FOR ACTIONS

var lickPts   =  5;
var cuddlePts = 10;
var barkPts   = -4;

// INITIALIZED STATE OF WOO DATA

var wooData = [{wooScore: 0, totalLicks: 0, totalCuddles: 0, totalBarks: 0}];

// DATA STRUCTURE
// wa = Woo Array
// created for use in multi-player/dog game with multiple active potential adopters

var wa =  cartesianProductOf(dogNames, adopterNames, wooData);

// MISCELLANEOUS

var won = false;

var dogname = '';
var dogPos = 0;   // position of the dog in the the Woo Array

var adoptername = '';
var adopterPos = 0;    // position of the adopter in the Woo Array

//// ACTIONS

// INITIALIZE GAME

$(document).ready(function() {
    $("h1").lettering();
    $("#main-game").css('opacity',.25);
    populateDogChoices(Dogs);
    populateAdopterChoices(Adopters);

    $('.dog-selection-entry').click(function() {
        event.preventDefault();
        dogname = $(this).children(".dog-selection-button").html();
        dogPos = $(this).children(".index-ignore").html();
        var dogView = $('#sidebar-template').html();
        $('#sidebar').append(_.template(dogView,({"imgURL": Dogs[dogPos].avatar, "dogname":dogname})));
        $('#select-player').fadeOut(750);
        $('#select-adopter').fadeIn(750);
      }
    );
});

$('.adopter-selection-entry').click(function() {
    event.preventDefault();
    adoptername = $(this).children("button").html();
    $('#select-adopter').fadeOut(750);
    $('') ///what happened here???
    $('#main-game').css('opacity',1);
    $('.adopter-avatar img').attr('src', eval(adoptername).avatar); // need better way to do this
});

$('#lick').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxLicks = Adopters[adopterPos].tolLick;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalLicks < maxLicks) {
               wa[index][2].wooScore += lickPts;
               wa[index][2].totalLicks++;
               moveProgress(lickPts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore >= 100) {
                   win(wa[index]);
               }
           }
           else {
               wooAlert('That\'s enough licks for now!');
               $("#lick").css('opacity',.15);
           }
        }
   });
});

$('#cuddle').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxCuddles = Adopters[adopterPos].tolCuddle;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalCuddles < maxCuddles) {
               wa[index][2].totalCuddles++;
               wa[index][2].wooScore += cuddlePts;
               moveProgress(cuddlePts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore >= 100) {
                   win(wa[index]);
               }
            }
            else {
                wooAlert('That\'s enough cuddles for now!');
                $("#cuddle").css('opacity',.15);
            }
        }
   });
});

$('#bark').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxBarks = Adopters[adopterPos].tolBark;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalBarks < maxBarks) {
               wa[index][2].totalBarks++;
               wa[index][2].wooScore += barkPts;
               moveProgress(barkPts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore < 0) {
                   wa[index][2].wooScore = 0;
                   $(".percent").html(0);
                   wooAlert('Maybe you should try something else!');
               }
               if (wa[index][2].totalBarks === 2 && maxBarks > 2 ) {
                   wooAlert('Don\'t give up!');
               }
            }
            else {
                wooAlert('OK, enough with the barking! Come over here!');
                $("#bark").css('opacity',.15);
                wa[index][2].totalCuddles = 0;
                wa[index][2].totalLicks = 0;
                $("#cuddle").css('opacity',1);
                $("#lick").css('opacity',1);
            }
        }
   });
});

$('.close').click(function(event) {
    event.preventDefault();
    $("#alert").fadeOut(700);
    if (won) {
        resetGame();
    }
  });

function win(dogAndAdopter){
    $(".percent").html(100);
    wooAlert(dogAndAdopter[1] + ' wants to adopt ' + dogAndAdopter[0] + '!');
    won = true;
}

function moveProgress(widthChange){
   var totalBarWidth = parseInt($(".woobar").css('width'));
   var progWidth = parseInt($(".woobarprog").css('width'));
   var newWidth = (progWidth + (totalBarWidth*(widthChange/100))) + 'px';
   $(".woobarprog").css('width', newWidth);
}

function resetGame(){
   $(".woobarprog").css('width',0);
   $(".percent").html(0);
   $("#sidebar").empty();
   $("#main-game").css('opacity',.25);
   $("#lick").css('opacity',1);
   $("#cuddle").css('opacity',1);
   $("#bark").css('opacity',1);
   wooData = [{wooScore: 0, totalLicks: 0, totalCuddles: 0, totalBarks: 0}];
   wa =  cartesianProductOf(dogNames,adopterNames, wooData);
   $('#select-player').fadeIn(750);
   won = false;
}

function wooAlert(alertMsg) {
    $('#alert').fadeIn(750);
    $('.alert-msg').html(alertMsg);
    };

// Cartesian Product function from ** http://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript **

function cartesianProductOf(a,b) {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
};
<commit_msg>Adopter template generating all adopters for use in game<commit_after>'use strict';

//// CONSTRUCTORS

function Dog (name, options) {
   options = options || {};
   this.name = name;
   this.color = options.color;  // not used
   this.age = options.age;  // not used
   this.breeds = options.breeds || 'mutt';  // not used
   this.weight = options.weight;  // not used
   this.barkVol = options.barkVol || 5;  // not used
   this.avatar = options.avatar;
   this.lick = function(adopter) {
       return adopter.licked = true;
   }  // not used
   this.cuddle = function(adopter) {
       return adopter.cuddled = true;
   };  // not used
   this.bark = function(adopter) {
       return adopter.barkedAt = true;
   }  // not used
}

function Adopter (name, options) {
   options = options || {};
   this.name = name;
   this.licked = false;  // not used
   this.cuddled = false;  // not used
   this.tolLick = options.tolLick || 5;
   this.tolCuddle = options.tolCuddle || 5;
   this.tolBark = options.tolBark || 5;
   this.prefAge = options.prefAge;  // not used
   this.avatar = options.avatar;
}

//// GLOBAL VARIABLES

// DOGS

var Moksha = new Dog("Moksha",{
    color: ["brown","white"],
    age: 4,
    avatar: 'https://fbcdn-sphotos-g-a.akamaihd.net/hphotos-ak-xpa1/t31.0-8/1932629_10152418870378352_6293108892780372302_o.jpg'
});

var Emmitt = new Dog("Emmitt",{
    color: ["yellow"],
    age: 5,
    avatar: 'https://fbcdn-sphotos-h-a.akamaihd.net/hphotos-ak-xap1/t1.0-9/60479_432375844549_1903986_n.jpg'
});

var Bella = new Dog("Bella",{
    color: ["black"],
    age: 2,
    avatar: 'https://scontent-b-lga.xx.fbcdn.net/hphotos-xfp1/t1.0-9/4541_89487809549_852660_n.jpg'
});

var Herman = new Dog("Herman",{
    color: ["black","white"],
    age: 6,
    avatar: 'http://i.imgur.com/s7mpWGj.jpg'
});

var Dogs = ([Moksha,Emmitt,Bella,Herman]);

var dogNames = Dogs.map(function (dog) {
    return dog.name;
});

function populateDogChoices(doglist){
    var dogSelectView = $('#dog-select-template').html();
    for (var i = 0; i < (_.size(doglist)); i++)
        {
            $('.dog-choices').append(_.template(dogSelectView,({"imgURL": doglist[i].avatar, "dogname": doglist[i].name, "dogindex":i})));
        }
}

// ADOPTERS

var Dave = new Adopter("Dave",{
    tolLick: 7,
    tolCuddle: 4,
    tolBark: 5,
    avatar: 'https://dge9rmgqjs8m1.cloudfront.net/global/6e784a56292505372595b9023b9cdc970010/original.6e784a56292505372595b9023b9cdc970010.gif'
});

var Emily = new Adopter("Emily",{
    tolLick: 4,
    tolCuddle: 6,
    tolBark: 3,
    avatar: 'https://asset1.basecamp.com/1940253/people/8112581/photo/avatar.96.gif'
})

var Julia = new Adopter("Julia",{
    tolLick: 10,
    tolCuddle: 3,
    tolBark: 7,
    avatar: 'https://avatars0.githubusercontent.com/u/955558?s=460'
})

var Talal = new Adopter("Talal",{
    avatar: 'https://avatars1.githubusercontent.com/u/3066028?s=400'
})

var Adopters = ([Dave,Emily,Julia,Talal]);

var adopterNames = Adopters.map(function (adopter) {
    return adopter.name;
});

function populateAdopterChoices(adopterlist){
    var adopterSelectView = $('#adopter-select-template').html();
    for (var i = 0; i < (_.size(adopterlist)); i++)
        {
            $('.adopter-choices').append(_.template(adopterSelectView,({"imgURL": adopterlist[i].avatar, "adoptername": adopterlist[i].name})));
        }
}

// DEFAULT POINT VALUES FOR ACTIONS

var lickPts   =  5;
var cuddlePts = 10;
var barkPts   = -4;

// INITIALIZED STATE OF WOO DATA

var wooData = [{wooScore: 0, totalLicks: 0, totalCuddles: 0, totalBarks: 0}];

// DATA STRUCTURE
// wa = Woo Array
// created for use in multi-player/dog game with multiple active potential adopters

var wa =  cartesianProductOf(dogNames, adopterNames, wooData);

// MISCELLANEOUS

var won = false;

var dogname = '';
var dogPos = 0;   // position of the dog in the the Woo Array

var adoptername = '';
var adopterPos = 0;    // position of the adopter in the Woo Array

//// ACTIONS

// INITIALIZE GAME

$(document).ready(function() {
    $("h1").lettering();
    $("#main-game").css('opacity',.25);
    populateDogChoices(Dogs);
    populateAdopterChoices(Adopters);

    $('.dog-selection-entry').click(function() {
        event.preventDefault();
        dogname = $(this).children(".dog-selection-button").html();
        dogPos = $(this).children(".index-ignore").html();
        var dogView = $('#sidebar-template').html();
        $('#sidebar').append(_.template(dogView,({"imgURL": Dogs[dogPos].avatar, "dogname":dogname})));
        $('#select-player').fadeOut(750);
        $('#select-adopter').fadeIn(750);
      }
    );

    $('.adopter-selection-entry').click(function() {
        event.preventDefault();
        adoptername = $(this).children("button").html();
        $('#select-adopter').fadeOut(750);
        $('') ///what happened here???
        $('#main-game').css('opacity',1);
        $('.adopter-avatar img').attr('src', eval(adoptername).avatar); // need better way to do this
    });

});

$('#lick').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxLicks = Adopters[adopterPos].tolLick;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalLicks < maxLicks) {
               wa[index][2].wooScore += lickPts;
               wa[index][2].totalLicks++;
               moveProgress(lickPts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore >= 100) {
                   win(wa[index]);
               }
           }
           else {
               wooAlert('That\'s enough licks for now!');
               $("#lick").css('opacity',.15);
           }
        }
   });
});

$('#cuddle').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxCuddles = Adopters[adopterPos].tolCuddle;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalCuddles < maxCuddles) {
               wa[index][2].totalCuddles++;
               wa[index][2].wooScore += cuddlePts;
               moveProgress(cuddlePts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore >= 100) {
                   win(wa[index]);
               }
            }
            else {
                wooAlert('That\'s enough cuddles for now!');
                $("#cuddle").css('opacity',.15);
            }
        }
   });
});

$('#bark').click(function() {
    event.preventDefault();
    dogPos = $.inArray(dogname,dogNames);
    adopterPos = $.inArray(adoptername,adopterNames);
    var maxBarks = Adopters[adopterPos].tolBark;
   $.each(wa, function(index,e)  {
       if ((e[0] === dogname) && (e[1] === adoptername)) {
           if (wa[index][2].totalBarks < maxBarks) {
               wa[index][2].totalBarks++;
               wa[index][2].wooScore += barkPts;
               moveProgress(barkPts);
               $(".percent").html(wa[index][2].wooScore);
               if (wa[index][2].wooScore < 0) {
                   wa[index][2].wooScore = 0;
                   $(".percent").html(0);
                   wooAlert('Maybe you should try something else!');
               }
               if (wa[index][2].totalBarks === 3 && maxBarks > 3 ) {
                   wooAlert('Don\'t give up!');
               }
            }
            else {
                wooAlert('OK, OK, come back over here!');
                $("#bark").css('opacity',.15);
                wa[index][2].totalCuddles = 0;
                wa[index][2].totalLicks = 0;
                $("#cuddle").css('opacity',1);
                $("#lick").css('opacity',1);
            }
        }
   });
});

$('.close').click(function(event) {
    event.preventDefault();
    $("#alert").fadeOut(700);
    if (won) {
        resetGame();
    }
  });

function win(dogAndAdopter){
    $(".percent").html(100);
    wooAlert(dogAndAdopter[1] + ' wants to adopt ' + dogAndAdopter[0] + '!');
    won = true;
}

function moveProgress(widthChange){
   var totalBarWidth = parseInt($(".woobar").css('width'));
   var progWidth = parseInt($(".woobarprog").css('width'));
   var newWidth = (progWidth + (totalBarWidth*(widthChange/100))) + 'px';
   $(".woobarprog").css('width', newWidth);
}

function resetGame(){
   $(".woobarprog").css('width',0);
   $(".percent").html(0);
   $("#sidebar").empty();
   $("#main-game").css('opacity',.25);
   $("#lick").css('opacity',1);
   $("#cuddle").css('opacity',1);
   $("#bark").css('opacity',1);
   wooData = [{wooScore: 0, totalLicks: 0, totalCuddles: 0, totalBarks: 0}];
   wa =  cartesianProductOf(dogNames,adopterNames, wooData);
   $('#select-player').fadeIn(750);
   won = false;
}

function wooAlert(alertMsg) {
    $('#alert').fadeIn(750);
    $('.alert-msg').html(alertMsg);
    };

// Cartesian Product function from ** http://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript **

function cartesianProductOf(a,b) {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import axios from 'axios';
import Search from './components/search/Search';
import Profile from './components/profile/Profile';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {
      searchString: '',
      userData: [],
    };
    this.getSearchResults = this.getSearchResults.bind(this);
    this.getSearchString = this.getSearchString.bind(this);
    this.searchUsers = this.searchUsers.bind(this);
  }

  searchUsers() {
    axios.get(`https://api.github.com/users/${this.state.searchString}`)
      .then((response) => {
        console.log(response.data);
        this.setState({userData: response.data});
      })
      .catch((error) => {
        console.log(error);
      });
  }

  getSearchString(event) {
    this.setState({searchString: event.target.value});
  }

  getSearchResults(event) {
    event.preventDefault();
    this.searchUsers();
  }

  render() {
    return (

        <Search getSearchResults={this.getSearchResults} getSearchString={this.getSearchString} />
        <Profile profile={this.state.userData} />

    );
  }
}

export default App;
<commit_msg>further abstraction of ajax request to be more DRY<commit_after>import React, { Component } from 'react';
import axios from 'axios';
import Search from './components/search/Search';
import Profile from './components/profile/Profile';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {
      searchString: '',
      userData: [],
      repoData: [],
    };
    this.ajaxRequest = this.ajaxRequest.bind(this);
    this.getSearchResults = this.getSearchResults.bind(this);
    this.getSearchString = this.getSearchString.bind(this);
    this.searchUsers = this.searchUsers.bind(this);
    this.searchRepos = this.searchRepos.bind(this);
  }

  ajaxRequest(options = {}) {
    const defaults = {
      string: '',
      state: '',
      ...options
    };

    axios.get(defaults.string)
      .then((response) => {
        console.log(response.data);
        this.setState({[defaults.state]: response.data});
      })
      .catch((error) => {
        console.log(error);
      });
  }

  searchUsers() {
    this.ajaxRequest({
      string: `https://api.github.com/users/${this.state.searchString}`,
      state: `userData`,
    });
  }

  searchRepos() {
    this.ajaxRequest({
      string: `https://api.github.com/search/repositories?q=${this.state.searchString}`,
      state: `repoData`,
    });
  }

  getSearchString(event) {
    this.setState({searchString: event.target.value});
  }

  getSearchResults(event) {
    event.preventDefault();
    this.searchUsers();
    this.searchRepos();
  }

  render() {
    return (

        <Search getSearchResults={this.getSearchResults} getSearchString={this.getSearchString} />
        <Profile profile={this.state.userData} />

    );
  }
}

export default App;
<|endoftext|>


<commit_before>/* global window */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import uniqueId from 'lodash/uniqueId';
import SprkIcon from '../icons/SprkIcon';
import SprkLink from '../../base/links/SprkLink';

class SprkDropdown extends Component {
  constructor(props) {
    super(props);
    this.state = {
      triggerText: props.defaultTriggerText,
      isOpen: false,
      choiceItems: props.choices.items.map(item => ({
        id: uniqueId(),
        ...item,
      })),
    };
    this.toggleDropdownOpen = this.toggleDropdownOpen.bind(this);
    this.closeOnEsc = this.closeOnEsc.bind(this);
    this.closeOnClickOutside = this.closeOnClickOutside.bind(this);
    this.closeDropdown = this.closeDropdown.bind(this);
    this.selectChoice = this.selectChoice.bind(this);
    this.setSelectedChoice = this.setSelectedChoice.bind(this);
    this.dropdownRef = React.createRef();
  }

  componentDidMount() {
    window.addEventListener('keydown', this.closeOnEsc);
    window.addEventListener('focusin', this.closeOnClickOutside);
    window.addEventListener('click', this.closeOnClickOutside);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.closeOnEsc);
    window.removeEventListener('focusin', this.closeOnClickOutside);
    window.removeEventListener('click', this.closeOnClickOutside);
  }

  setSelectedChoice(componentToUpdateId) {
    const { choiceItems } = this.state;
    return choiceItems.map(({ id, isActive, ...rest }) => {
      if (id === componentToUpdateId) {
        return { id, isActive: true, ...rest };
      }
      return { id, isActive: false, ...rest };
    });
  }

  selectChoice(id, text) {
    this.setState({
      triggerText: text,
      choiceItems: this.setSelectedChoice(id),
    });
  }

  toggleDropdownOpen(e) {
    e.preventDefault();
    this.setState(prevState => ({
      isOpen: !prevState.isOpen,
    }));
  }

  closeOnEsc(e) {
    if (e.key === 'Escape') {
      this.closeDropdown();
    }
  }

  closeOnClickOutside(e) {
    if (!this.dropdownRef.current.contains(e.target)) {
      this.closeDropdown();
    }
  }

  closeDropdown() {
    this.setState({
      isOpen: false,
    });
  }

  render() {
    const {
      additionalClasses,
      additionalIconClasses,
      additionalTriggerClasses,
      additionalTriggerTextClasses,
      analyticsString,
      choices,
      iconName,
      idString,
      screenReaderText,
      title,
      variant,
    } = this.props;
    const { choiceFunction, footer } = choices;
    const { choiceItems, isOpen, triggerText } = this.state;
    return (

        <SprkLink
          element="a"
          variant="plain"
          additionalClasses={classNames(
            { 'sprk-u-mrs': variant === 'informational' },
            additionalTriggerClasses,
          )}
          aria-expanded={isOpen}
          aria-haspopup="listbox"
          aria-label={screenReaderText ? screenReaderText : triggerText}
          data-analytics={analyticsString || 'undefined'}
          data-id={idString || 'undefined'}
          onClick={this.toggleDropdownOpen}
        >
          {variant === 'informational' && (
            <React.Fragment>

                {triggerText}

              <SprkIcon
                additionalClasses="sprk-c-Icon--stroke-current-color sprk-u-mls"
                iconName="chevron-down"
              />
            </React.Fragment>
          )}
          {variant === 'base' && (
            <React.Fragment>

                {screenReaderText}

              <SprkIcon
                iconName={iconName}
                additionalClasses={additionalIconClasses}
              />
            </React.Fragment>
          )}
        </SprkLink>
        {isOpen && (

            {title !== '' && (

{title}

            )}

              {choiceItems.map((choice) => {
                const {
                  content,
                  element,
                  href,
                  isActive,
                  text,
                  value,
                  idString,
                  ...rest
                } = choice;
                const TagName = element || 'a';
                return (

                    {variant === 'base' && (
                      <TagName
                        className="sprk-c-Dropdown__link"
                        href={TagName === 'a' ? href || '#' : undefined}
                        aria-label={text}
                        onClick={() => {
                          this.selectChoice(choice.id, text);
                          this.closeDropdown();
                          if (choiceFunction) {
                            choiceFunction(value);
                          }
                        }}
                        data-id={idString}
                        {...rest}
                      >
                        {text}
                      </TagName>
                    )}
                    {variant === 'informational' && (
                      <React.Fragment>
                        <TagName
                          className={classNames('sprk-c-Dropdown__link', {
                            'sprk-c-Dropdown__link--active': isActive,
                          })}
                          href={TagName === 'a' ? href || '#' : undefined}
                          aria-label={content.title}
                          onClick={() => {
                            this.selectChoice(choice.id, content.title);
                            this.closeDropdown();
                            if (choiceFunction) {
                              choiceFunction(value);
                            }
                          }}
                          {...rest}
                        >

{content.title}

                            {content.infoLine1}

                            {content.infoLine2}

                        </TagName>
                      </React.Fragment>
                    )}

                );
              })}

            {footer && (

                {footer}

            )}

        )}

    );
  }
}

SprkDropdown.propTypes = {
  /**
   * Expects a space separated string
   * of classes to be added to the
   * component.
   */
  additionalClasses: PropTypes.string,
  /**
   * Expects a space separated string
   * of classes to be added to the
   * icon.
   */
  additionalIconClasses: PropTypes.string,
  /**
   * Expects a space separated string of
   * classes to be added to the trigger link element.
   */
  additionalTriggerClasses: PropTypes.string,
  /**
   * Expects a space separated string of
   * classes to be added to the trigger text.
   */
  additionalTriggerTextClasses: PropTypes.string,
  /**
   * The value supplied will be assigned to the
   * `data-analytics` attribute on the component.
   * Intended for an outside
   * library to capture data.
   */
  analyticsString: PropTypes.string,
  /** Incoming children. */
  children: PropTypes.node,
  /** Choices object that builds the dropdown contents. */
  choices: PropTypes.shape({
    /** An array of objects that describe the items in the menu. */
    items: PropTypes.arrayOf(
      PropTypes.shape({
        /** The element to render for each menu item. */
        element: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
        /**
         * Assigned to `href` of the element is `a`.
         */
        href: PropTypes.string,
        /** The text inside the choice item. */
        text: PropTypes.string,
      }),
    ),
  }),
  /** The text set as the default of the trigger link. */
  defaultTriggerText: PropTypes.string,
  /** The icon type of the trigger icon. */
  iconName: PropTypes.string,
  /**
   * The value supplied will be assigned
   * to the `data-id` attribute on the
   * component. This is intended to be
   * used as a selector for automated
   * tools. This value should be unique
   * per page.
   */
  idString: PropTypes.string,
  /**
  * The value supplied will be visually hidden
  * inside the trigger. Userful
  * for when the `title` is empty.
  */
  screenReaderText: PropTypes.string,
  /**
   * The text of the optional header above the
   * choices in the dropdown.
   */
  title: PropTypes.string,
  /**
   * The variant of the Dropdown to render.
   */
  variant: PropTypes.oneOf(['base', 'informational']),
};

SprkDropdown.defaultProps = {
  additionalClasses: '',
  additionalIconClasses: '',
  additionalTriggerClasses: '',
  additionalTriggerTextClasses: '',
  analyticsString: '',
  children: [],
  choices: {
    items: [],
  },
  defaultTriggerText: 'Choose One...',
  iconName: 'chevron-down',
  idString: '',
  screenReaderText: '',
  title: '',
  variant: 'base',
};

export default SprkDropdown;
<commit_msg>Improve SprkDropdown comments<commit_after>/* global window */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import uniqueId from 'lodash/uniqueId';
import SprkIcon from '../icons/SprkIcon';
import SprkLink from '../../base/links/SprkLink';

class SprkDropdown extends Component {
  constructor(props) {
    super(props);
    this.state = {
      triggerText: props.defaultTriggerText,
      isOpen: false,
      choiceItems: props.choices.items.map(item => ({
        id: uniqueId(),
        ...item,
      })),
    };
    this.toggleDropdownOpen = this.toggleDropdownOpen.bind(this);
    this.closeOnEsc = this.closeOnEsc.bind(this);
    this.closeOnClickOutside = this.closeOnClickOutside.bind(this);
    this.closeDropdown = this.closeDropdown.bind(this);
    this.selectChoice = this.selectChoice.bind(this);
    this.setSelectedChoice = this.setSelectedChoice.bind(this);
    this.dropdownRef = React.createRef();
  }

  componentDidMount() {
    window.addEventListener('keydown', this.closeOnEsc);
    window.addEventListener('focusin', this.closeOnClickOutside);
    window.addEventListener('click', this.closeOnClickOutside);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.closeOnEsc);
    window.removeEventListener('focusin', this.closeOnClickOutside);
    window.removeEventListener('click', this.closeOnClickOutside);
  }

  setSelectedChoice(componentToUpdateId) {
    const { choiceItems } = this.state;
    return choiceItems.map(({ id, isActive, ...rest }) => {
      if (id === componentToUpdateId) {
        return { id, isActive: true, ...rest };
      }
      return { id, isActive: false, ...rest };
    });
  }

  selectChoice(id, text) {
    this.setState({
      triggerText: text,
      choiceItems: this.setSelectedChoice(id),
    });
  }

  toggleDropdownOpen(e) {
    e.preventDefault();
    this.setState(prevState => ({
      isOpen: !prevState.isOpen,
    }));
  }

  closeOnEsc(e) {
    if (e.key === 'Escape') {
      this.closeDropdown();
    }
  }

  closeOnClickOutside(e) {
    if (!this.dropdownRef.current.contains(e.target)) {
      this.closeDropdown();
    }
  }

  closeDropdown() {
    this.setState({
      isOpen: false,
    });
  }

  render() {
    const {
      additionalClasses,
      additionalIconClasses,
      additionalTriggerClasses,
      additionalTriggerTextClasses,
      analyticsString,
      choices,
      iconName,
      idString,
      screenReaderText,
      title,
      variant,
    } = this.props;
    const { choiceFunction, footer } = choices;
    const { choiceItems, isOpen, triggerText } = this.state;
    return (

        <SprkLink
          element="a"
          variant="plain"
          additionalClasses={classNames(
            { 'sprk-u-mrs': variant === 'informational' },
            additionalTriggerClasses,
          )}
          aria-expanded={isOpen}
          aria-haspopup="listbox"
          aria-label={screenReaderText ? screenReaderText : triggerText}
          data-analytics={analyticsString || 'undefined'}
          data-id={idString || 'undefined'}
          onClick={this.toggleDropdownOpen}
        >
          {variant === 'informational' && (
            <React.Fragment>

                {triggerText}

              <SprkIcon
                additionalClasses="sprk-c-Icon--stroke-current-color sprk-u-mls"
                iconName="chevron-down"
              />
            </React.Fragment>
          )}
          {variant === 'base' && (
            <React.Fragment>

                {screenReaderText}

              <SprkIcon
                iconName={iconName}
                additionalClasses={additionalIconClasses}
              />
            </React.Fragment>
          )}
        </SprkLink>
        {isOpen && (

            {title !== '' && (

{title}

            )}

              {choiceItems.map((choice) => {
                const {
                  content,
                  element,
                  href,
                  isActive,
                  text,
                  value,
                  idString,
                  ...rest
                } = choice;
                const TagName = element || 'a';
                return (

                    {variant === 'base' && (
                      <TagName
                        className="sprk-c-Dropdown__link"
                        href={TagName === 'a' ? href || '#' : undefined}
                        aria-label={text}
                        onClick={() => {
                          this.selectChoice(choice.id, text);
                          this.closeDropdown();
                          if (choiceFunction) {
                            choiceFunction(value);
                          }
                        }}
                        data-id={idString}
                        {...rest}
                      >
                        {text}
                      </TagName>
                    )}
                    {variant === 'informational' && (
                      <React.Fragment>
                        <TagName
                          className={classNames('sprk-c-Dropdown__link', {
                            'sprk-c-Dropdown__link--active': isActive,
                          })}
                          href={TagName === 'a' ? href || '#' : undefined}
                          aria-label={content.title}
                          onClick={() => {
                            this.selectChoice(choice.id, content.title);
                            this.closeDropdown();
                            if (choiceFunction) {
                              choiceFunction(value);
                            }
                          }}
                          {...rest}
                        >

{content.title}

                            {content.infoLine1}

                            {content.infoLine2}

                        </TagName>
                      </React.Fragment>
                    )}

                );
              })}

            {footer && (

                {footer}

            )}

        )}

    );
  }
}

SprkDropdown.propTypes = {
  /**
   * Expects a space separated string
   * of classes to be added to the
   * SprkDropdown component.
   */
  additionalClasses: PropTypes.string,
  /**
   * Expects a space separated string
   * of classes to be added to the
   * icon.
   */
  additionalIconClasses: PropTypes.string,
  /**
   * Expects a space separated string of
   * classes to be added to the trigger link element.
   *
   */
  additionalTriggerClasses: PropTypes.string,
  /**
   * Expects a space separated string of
   * classes to be added to the trigger text.
   */
  additionalTriggerTextClasses: PropTypes.string,
  /**
   * Value assigned to the
   * `data-analytics` attribute on the component.
   * Intended for an outside
   * library to capture data.
   */
  analyticsString: PropTypes.string,
  /** Content to render inside of the SprkDropdown */
  children: PropTypes.node,
  /**
   * The choices object represents
   * data that is shown inside the open dropdown
   */
  choices: PropTypes.shape({
    /** An array of objects that describe the items in the menu. */
    items: PropTypes.arrayOf(
      PropTypes.shape({
        /** The element to render for each menu item. */
        element: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
        /**
         * Determines the href of the choice item.
         */
        href: PropTypes.string,
        /** The text inside the choice item. */
        text: PropTypes.string,
      }),
    ),
  }),
  /** The text set as the default of the trigger link. */
  defaultTriggerText: PropTypes.string,
  /**
   * Determines what icon `SprkIcon` renders
   * as the trigger icon.
   */
  iconName: PropTypes.string,
  /**
   * Value assigned
   * to the `data-id` attribute on the
   * component. This is intended to be
   * used as a selector for automated
   * tools. This value should be unique
   * per page.
   */
  idString: PropTypes.string,
  /**
  * A value for screen readers when there isn't
  * discernable text on the dropdown.
  * Useful for when the `title` prop is empty
  * and the Dropdown trigger is only an icon.
  */
  screenReaderText: PropTypes.string,
  /**
   * The text of the optional header above the
   * choices in the dropdown.
   */
  title: PropTypes.string,
  /**
   * The variant of the Dropdown to render.
   */
  variant: PropTypes.oneOf(['base', 'informational']),
};

SprkDropdown.defaultProps = {
  additionalClasses: '',
  additionalIconClasses: '',
  additionalTriggerClasses: '',
  additionalTriggerTextClasses: '',
  analyticsString: '',
  children: [],
  choices: {
    items: [],
  },
  defaultTriggerText: 'Choose One...',
  iconName: 'chevron-down',
  idString: '',
  screenReaderText: '',
  title: '',
  variant: 'base',
};

export default SprkDropdown;
<|endoftext|>


<commit_before>// Computable
// ==========

// The **computable** is one of the two key components of the upward library,
// along with the **upwardable**.
// A **computable** is an enhanced function which recomputes itself
// when its inputs or dependencies change.
// Inovking a computable results in a **computed**, which holds the value.
// A computed is always an object; if primitive, it is wrapped.

// Convenience.
var {getNotifier, observe, unobserve, defineProperty} = Object;

import {Observer} from './Obs';
import {copyOnto, isObject} from './Obj';

// Keep track of computables, computeds, and computifieds.
var computables  = new WeakSet();
var computeds    = new WeakSet();
var computifieds = new WeakMap();

function isComputable (c)    { return isObject(c) && computables .has(c); }
function isComputed   (c)    { return isObject(c) && computeds   .has(c); }
function getComputable(f)    { return isObject(f) && computifieds.get(f); }
function addComputable(f, c) { computables.add(c); computifieds.set(f, addComputable); }
function addComputed  (c)    { computeds.add(c); }

var computedId = 0;

// Constructor for computable.
function C(f, options) {
  var computable = getComputable(f);
  if (!computable) {
    computable = createComputable(f, options);
    addComputable(f, computable);
  }
  return computable;
}

var computablePrototype = Object.create(Function, {
});

function createComputable(f, options = {}) {

  function computable(...args) {

    var computed;
    var observable = {};
    var notifier = getNotifier(observable);

    // Deal with properties accessed during execution of this function.
    // Maintain an `accesses` map indexed by object,
    // containing "access" entries with values of `{names: [], observer}`.
    // `names` of null means to watch properties of any name.
    // It is built by calls to `notifyAccess`, invoked through `accessNotifier`.
    var accesses = new Map();

    function notifyAccess({object, name}) {

      // Create an observer for changes in properties accessed during execution of this function.
      function makeAccessedObserver() {
        return Observer(object, function(changes) {
          changes.forEach(({type, name}) => {
            var {names} = accessEntry;
            if (!names || type === 'update' && names.indexOf(name) !== -1)
              notifier.notify({type: 'recompute'});
          });
        });
      }

      // Make a new entry in the access table, containing initial property name if any
      // and observer for properties accessed on the object.
      function makeAccessEntry() {
        return {
          names:    name ? [name] : null,
          observer: makeAccessedObserver()
        };
      }

      // If properties on this object are already being watched, there is already an entry
      // in the access table for it. Add a new property name to the existing entry.
      function setAccessEntry() {
        if (name && accessEntry.names) accessEntry.names.push(name);
        else accessEntry.names = null;
      }

      var accessEntry = accesses.get(object);
      if (accessEntry) setAccessEntry();
      else accessEntry = makeAccessEntry();
      accesses.set(object, accessEntry);
    }

    // Run the function.
    // Stop observing dependencies, and set up capture of new dependencies.
    // After computation, start observing newly captured dependencies.
    // If computed is scalar and changes, then alert upwardables who may be watching.
    function run() {

      // If this function is running in the context of another computed function,
      // let it know that there has been an access.
      if (computed) {
        accessNotifier.notify({type: 'update',  object: computed});
      }

      // Stop observing changes to accesses. Prepare to capture new accesses.
      accesses.forEach(({observer}) => observer.unobserve());
      accesses.clear();
      accessNotifier.push(notifyAccess);

      var newComputed = Object(f.call({computed, accessNotifier, run}, ...args));

      accessNotifier.pop();

      // Objects remain themselves, but primitives become new objects.
      // Upwardables who are watching this computed need to know.
      if (newComputed !== computed) {
        const type = 'compute';
//        notifier.notify({object: computed, newValue: newComputed, type});
        if (computed) {
          Object.getNotifier(computed).notify({object: computed, newValue: newComputed, type});
        }
        computed = newComputed;
        defineProperty(computed, 'id', { value: computedId++ });
      }

      addComputed(computed);

      // Observe changes to newly-captured accesses.
      // @TODO: apparently 'configure' change types are reported--why?
      accesses.forEach(({observer}) => observer.observe(['update', 'add', 'delete']));
    }

    // Make stack traces friendlier.
    run.displayName = `[run '${f.displayName || f.name}']`;

    // Set up an observer for changes to an argument.
    // This will handle 'compute' changes, and eventually cause the fn to recompute.
    // If the argument changes, the new argument is reobserved.
    function observeArg(arg, i, args) {
      var observer = Observer(
        arg,
        function argObserver(changes) {
          changes.forEach(({type, newValue}) => {
            if (type === 'compute') {
              args[i] = newValue;
              observer.reobserve(newValue);
            }
          });
          notifier.notify({type: 'recompute'});
        },
//        ['compute', 'delete', 'update', 'add'] // @TODO: check all these are necessary
        ['compute'] // @TODO: check all these are necessary
      );
      observer.observe();
    }

    args.forEach(observeArg);

    observe(observable, run, ['recompute']);
    run();

    return computed;
  }

  return computable;
}

// The ur-computable is to get a property from an object.
var getComputedProperty = C(
  function getProperty(object, name) {
    observe(object, changes => changes.forEach(change => {
      if (change.name === name) this.run();
    }));
    return object[name];
  }
);

// `accessNotifier` allows upwardables to report property accesses.
// It is a stack to handle nested invocations of computables.
var _accessNotifier = [];

var accessNotifier = {
  pop:  function()               { _accessNotifier.shift(); },
  push: function(notifier)       { _accessNotifier.unshift(notifier); },
  notify: function(notification) {
    if (_accessNotifier.length) _accessNotifier[0](notification) ;
  }
};

// Convenience routine to allow functions to request an entire object be observed.
// This would typically be called from within a computified function.
function objectNotifier(o) {
  accessNotifier.notify({object: o});
}

export default C;

export {
  isComputable,
  accessNotifier,
  isComputed,
  getComputedProperty
};
<commit_msg>Broken WIP on Com.js.<commit_after>// Computable
// ==========

// The **computable** is one of the two key components of the upward library,
// along with the **upwardable**.
// A **computable** is an enhanced function which recomputes itself
// when its inputs or dependencies change.
// Inovking a computable results in a **computed**, which holds the value.
// A computed is always an object; if primitive, it is wrapped.

// Convenience.
var {getNotifier, observe, unobserve, defineProperty} = Object;

import {Observer} from './Obs';
import {copyOnto, isObject} from './Obj';

// Keep track of computables, computeds, and computifieds.
var computables  = new WeakSet();
var computeds    = new WeakSet();
var computifieds = new WeakMap();

function isComputable (c)    { return isObject(c) && computables .has(c); }
function isComputed   (c)    { return isObject(c) && computeds   .has(c); }
function getComputable(f)    { return isObject(f) && computifieds.get(f); }
function addComputable(f, c) { computables.add(c); computifieds.set(f, addComputable); }
function addComputed  (c)    { computeds.add(c); }

var computedId = 0;

// Constructor for computable.
function C(f, options) {
  return constructComputable(generateCallForever(f), options);
}

function constructComputable(f, options) {
  var computable = getComputable(f);
  if (!computable) {
    computable = createComputable(f, options);
    addComputable(f, computable);
  }
  return computable;
}

// Create a computable function based on a generator.
function createComputable(generator, options = {}) {

  function computable(...args) {

    // Resolve the promise which will trigger recomputation.
    function rerunner() { rerun(); }

    function iterate() {
      var changed = new Promise(resolve => rerun = resolve);

      stopWatching();
      startCapturing(accesses);

      Promise.resolve(iterator.next().value)
        .then(function(value) {
          // handle value
          stopCapturing();
          startWatching(accesses, rerunner);
          changed.then(iterate);
        })
      ;
    }

    var computed;

    var iterator = generator(rerunner);
    var rerun;

    // `accesses` is a map indexed by object,
    // containing "access" entries with values of `{names: [], observer}`.
    // `names` of null means to watch properties of any name.
    // It is built by calls to `notifyAccess`, invoked through `accessNotifier`.
    var accesses = new Map();

    observeArgs(args, rerunner);
    iterate();
  }

  return computable;
)

function notifyAccess({object, name}) {

  // Create an observer for changes in properties accessed during execution of this function.
  function makeAccessedObserver() {
    return Observer(object, function(changes) {
      changes.forEach(({type, name}) => {
        var {names} = accessEntry;
        if (!names || type === 'update' && names.indexOf(name) !== -1)
          notifier.notify({type: 'recompute'});
      });
    });
  }

  // Make a new entry in the access table, containing initial property name if any
  // and observer for properties accessed on the object.
  function makeAccessEntry() {
    return {
      names:    name ? [name] : null,
      observer: makeAccessedObserver()
    };
  }

  // If properties on this object are already being watched, there is already an entry
  // in the access table for it. Add a new property name to the existing entry.
  function setAccessEntry() {
    if (name && accessEntry.names) accessEntry.names.push(name);
    else accessEntry.names = null;
  }

  var accessEntry = accesses.get(object);
  if (accessEntry) setAccessEntry();
  else accessEntry = makeAccessEntry();
  accesses.set(object, accessEntry);
}

// Run the function.
// Stop observing dependencies, and set up capture of new dependencies.
// After computation, start observing newly captured dependencies.
// If computed is scalar and changes, then alert upwardables who may be watching.
function run() {

  // If this function is running in the context of another computed function,
  // let it know that there has been an access.
      if (computed) {
        accessNotifier.notify({type: 'update',  object: computed});
      }
}

function startWatching() {
  accesses.forEach(({observer}) => observer.observe(['update', 'add', 'delete']));
}

function stopWatching(accesses) {
  // Stop observing changes to accesses. Prepare to capture new accesses.
  accesses.forEach(({observer}) => observer.unobserve());
}

function startCapturing(accesses) {
  accesses.clear();
  accessNotifier.push(notifyAccess);
}

function stopCapturing() {
  accessNotifier.pop();
}

function setValue(value) {
  // Objects remain themselves, but primitives become new objects.
  // Upwardables who are watching this computed need to know.
  if (newComputed !== computed) {
    const type = 'compute';
    //        notifier.notify({object: computed, newValue: newComputed, type});
    if (computed) {
      Object.getNotifier(computed).notify({object: computed, newValue: newComputed, type});
    }
    computed = newComputed;
    defineProperty(computed, 'id', { value: computedId++ });
  }

  addComputed(computed);
}

// Observe changes to arguments.
// This will handle 'compute' changes, and trigger recomputation of function.
// If the argument changes, the new argument is reobserved.
function observeArgs(args, changed) {

  function observeArg(arg, i, args) {
    var observer = Observer(
      arg,
      function argObserver(changes) {
        changes.forEach(({type, newValue}) => {
          if (type === 'compute') {
            args[i] = newValue;
            observer.reobserve(newValue);
          }
        });
        changed();
      },
      //        ['compute', 'delete', 'update', 'add'] // @TODO: check all these are necessary
      ['compute'] // @TODO: check all these are necessary
    );
    observer.observe();
  }
  args.forEach(observeArg);
}

// The ur-computable is to get a property from an object.
var getComputedProperty = C(
  function getProperty(object, name) {
    observe(object, changes => changes.forEach(change => {
      if (change.name === name) this.run();
    }));
    return object[name];
  }
);

// `accessNotifier` allows upwardables to report property accesses.
// It is a stack to handle nested invocations of computables.
var _accessNotifier = [];

var accessNotifier = {
  pop:  function()               { _accessNotifier.shift(); },
  push: function(notifier)       { _accessNotifier.unshift(notifier); },
  notify: function(notification) {
    if (_accessNotifier.length) _accessNotifier[0](notification) ;
  }
};

// Convenience routine to allow functions to request an entire object be observed.
// This would typically be called from within a computified function.
function objectNotifier(o) {
  accessNotifier.notify({object: o});
}

export default C;

export {
  isComputable,
  accessNotifier,
  isComputed,
  getComputedProperty
};
<|endoftext|>


<commit_before>app.controller('schoolkindsController', function ($scope,  $location, states) {
    var relevantYears = ['2007', '2015'];

    $scope.init = function(state){
        $scope.state = state;
        states.get($scope.state, function (err, statedata) {
            var data = relevantYears.map(function (year) {
                return statedata.schularten[year]
            });
            var keys = Object.keys(data[0]);
            var nvd3data = keys.map(function (key) {
                var result = {key: key};
                for (var i = 0; i < data.length; i++) {
                    result[relevantYears[i]] = data[i][key];
                }
                return result;
            });

            $scope.nvd3data = _.filter(nvd3data, function (elem) {
                var delta = Math.abs(elem[relevantYears[0]] - elem[relevantYears[1]]);
                return (delta / elem[relevantYears[0]] > 0.08 && delta > 10) &&
                    elem.key !== "Keine Zuordung zu einer Schulart möglich";
            });
            var keys = _.map($scope.nvd3data, "key");
            var firstYear = _.map($scope.nvd3data, "2007");
            var secondYear = _.map($scope.nvd3data, "2015");
            $scope.teacher_data = {labels: keys, series: [{name: "2007", "data": firstYear},
                                                          {name: "2015", "data":  secondYear}]};

            $scope.teacher_options = {
                seriesBarDistance: 30,
                height: '200px',
                chartPadding: {
                    top: 0,
                    right: 0,
                    bottom: 16,
                    left: 0
                },
                plugins: [
                    Chartist.plugins.legend({
                        clickable: false
                    }),
                    Chartist.plugins.ctBarLabels({
                        position: {
                            y: function(data) {
                                return data.y2 - 5
                            }
                        }
                    })
                ]
            };
        });
    }
});<commit_msg>bar series space too large on tiny screens<commit_after>app.controller('schoolkindsController', function ($scope,  $location, states) {
    var relevantYears = ['2007', '2015'];

    $scope.init = function(state){
        $scope.state = state;
        states.get($scope.state, function (err, statedata) {
            var data = relevantYears.map(function (year) {
                return statedata.schularten[year]
            });
            var keys = Object.keys(data[0]);
            var nvd3data = keys.map(function (key) {
                var result = {key: key};
                for (var i = 0; i < data.length; i++) {
                    result[relevantYears[i]] = data[i][key];
                }
                return result;
            });

            $scope.nvd3data = _.filter(nvd3data, function (elem) {
                var delta = Math.abs(elem[relevantYears[0]] - elem[relevantYears[1]]);
                return (delta / elem[relevantYears[0]] > 0.08 && delta > 10) &&
                    elem.key !== "Keine Zuordung zu einer Schulart möglich";
            });
            var keys = _.map($scope.nvd3data, "key");
            var firstYear = _.map($scope.nvd3data, "2007");
            var secondYear = _.map($scope.nvd3data, "2015");
            $scope.teacher_data = {labels: keys, series: [{name: "2007", "data": firstYear},
                                                          {name: "2015", "data":  secondYear}]};

            $scope.teacher_options = {
                seriesBarDistance: 20,
                height: '200px',
                chartPadding: {
                    top: 0,
                    right: 8,
                    bottom: 16,
                    left: 0
                },
                plugins: [
                    Chartist.plugins.legend({
                        clickable: false
                    }),
                    Chartist.plugins.ctBarLabels({
                        position: {
                            y: function(data) {
                                return data.y2 - 5
                            }
                        }
                    })
                ]
            };
        });
    }
});<|endoftext|>


<commit_before>/*
 * apps.js: Commands related to app resources
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var eyes = require('eyes'),
    winston = require('winston'),
    analyzer = require('require-analyzer'),
    jitsu = require('jitsu');

var apps = exports;

apps.usage = [
  '`jitsu apps *` commands allow you to work with your',
  'Applications on Nodejitsu. Valid commands are:',
  '',
  'jitsu apps create',
  'jitsu apps list',
  'jitsu apps deploy',
  'jitsu apps view    [<name>]',
  'jitsu apps update  [<name>]',
  'jitsu apps destroy [<name>]',
  'jitsu apps start   [<name>]',
  'jitsu apps restart [<name>]',
  'jitsu apps stop    [<name>]',
  '',
  'For commands that take a <name> parameter, if no parameter',
  'is supplied, jitsu will attempt to read the package.json',
  'from the current directory.'
];

//
// ### function deploy (callback)
// #### @callback {function} Continuation to respond to when complete.
// Deploys an application through the following steps:
// 1. Creates the application (if necessary)
// 2. Creates or validates the package.json
// 3. Packages and creates a new snapshot
// 4. Stops the application (if neccessary)
// 5. Starts the application
//
apps.deploy = function (callback) {
  var dir = process.cwd(), pkg;

  function startApp (err, existing) {
    if (err) {
      winston.error('Error creating snapshot for app ' + pkg.name.magenta);
      winston.error(err.message);
      return callback(new Error(), true);
    }

//    if (existing.state === 'started') {
//existing.state is thrown away in check app.
//these closures would need to be rearanged so that this closure can see the result of view..
      apps.stop(existing.name, function (err) {
        return err ? callback(err) : apps.start(existing.name, callback);
      });
//    }
//    else {
//      apps.start(existing.name, callback);
//    }
  }

  function activateSnapshot (existing, snapshot) {
    winston.info('Activating snapshot ' + snapshot.grey + ' for ' + existing.name.magenta);
    jitsu.snapshots.activate(existing.name, snapshot, function (err) {
      winston.silly('Done activating snapshot ' + snapshot.grey);
      startApp(err, existing);
    });
  }

  function updateApp (existing, snapshot) {
    winston.info('Updating application ' + existing.name.magenta);
    jitsu.apps.update(existing.name, pkg, function (err) {
      if (err) {
        return callback(err);
      }

      activateSnapshot(existing, snapshot);
    });
  }

  function updateSnapshot (err, existing, firstSnapshot) {
    if (err) {
      return callback(err, true, true);
    }

    jitsu.package.updateTarball(null, pkg, existing, firstSnapshot, function (err, snapshot) {
      if (err) {
        return callback(err, snapshot);
      }

      updateApp(existing, snapshot);
    });
  }

  function checkApp (err, local) {
    if (err) {
      winston.error(err);
      return callback(new Error(), true);
    }

    pkg = local;
    winston.silly('Checking if application ' + local.name.magenta + ' exists.');
    jitsu.apps.view(local.name, function (err, app) {
      if (err) {
        winston.warn('No application exists for ' + local.name.magenta);
        //
        // If there is a before filter we should run before creating the app do so,
        // then create the application.
        //
        return apps.create(local, function (err, existing) {
          updateSnapshot(err, existing, true);
        });
      }

      app = !app ? {} : jitsu.utils.formatApp(app, ['snapshots', 'active', 'user', 'state']);
      var pkg = analyzer.merge({}, local, app);
      jitsu.package.validate(pkg, dir, updateSnapshot);
    });
  }

  jitsu.package.read(dir, function (err, local) {
    return err ? jitsu.package.get(dir, checkApp) : jitsu.package.validate(local, dir, {}, checkApp);
  });
};

apps.deploy.usage = [
  '',
  'Deploys an application using the following steps:'.cyan.underline,
  '',
  '  1. Creates the application (if necessary)',
  '  2. Creates or validates the package.json',
  '  3. Packages and creates a new snapshot',
  '  4. Stops the application (if neccessary)',
  '  5. Starts the application',
  '',
  'jitsu deploy',
  'jitsu apps deploy'
];

//
// ### function list (callback)
// #### @target {string|Object} **optional** Name of the application to create
// #### @callback {function} Continuation to pass control to when complete.
// Creates an application for the package.json in the current directory
// using `name` if supplied and falling back to `package.name`.
//
apps.create = function (target, callback) {
  if (!callback) {
    callback = target;
    target = null;
  }

  var name;

  function executeCreate (err, pkg) {
    if (err) {
      winston.error(err);
      return callback(new Error(), true);
    }

    pkg.name = name || pkg.name;
    jitsu.apps.list(function (err, apps) {
      var username = jitsu.config.get('username');
      var existing = apps.filter(function (a) {
        return a.name === pkg.name && a.user === username;
      });

      if (existing.length > 0) {
        return callback(new Error('Cannot create duplicate application ' + pkg.name.magenta), true, true);
      }

      //
      // TODO (indexzero): Configure this default value in nodejitsu APIs
      //
      pkg.state = 'stopped';

      winston.info('Checking app availability ' + pkg.name.magenta);
      jitsu.apps.available(pkg, function (err, result) {
        if (!result.available) {
          winston.error('Error creating ' + pkg.name.magenta);
          winston.error(result.message);
          return callback(new Error());
        }

        winston.info('Creating app ' + pkg.name.magenta);
        jitsu.apps.create(pkg, function (err, res, result) {
          winston.silly('Done creating app ' + pkg.name.magenta);
          return err ? callback(err) : callback(null, pkg);
        });
      });
    });
  }

  if (typeof target === 'object') {
    name = target.name;
    return executeCreate(null, target);
  }

  jitsu.package.get(process.cwd(), executeCreate);
};

//
// Usage for `jitsu apps create [<name>]`
//
apps.create.usage = [
  'Creates an application using the package.json file in the',
  'current directory, if <name> is supplied then this is used',
  'instead of the `name` property in the package.json file.',
  '',
  'jitsu create',
  'jitsu apps create',
  'jitsu apps create <name>'
];

//
// ### function list (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Lists the applications for the authenticated user.
//
apps.list = function (callback) {
  winston.info('Listing apps');
  jitsu.apps.list(function (err, apps) {
    if (err) {
      return callback(err);
    }

    if (!apps || apps.length === 0) {
      winston.warn('No applications exist.');
      winston.help('Try creating one with ' + 'jitsu apps create'.magenta);
      return callback();
    }

    var rows = [['name', 'state', 'subdomain', 'start', 'latest']],
        colors = ['underline', 'underline', 'underline', 'underline', 'underline'];

    apps.forEach(function (app) {
      rows.push([
        app.name,
        app.state || 'stopped',
        app.subdomain,
        app.scripts.start || '---',
        app.snapshots && app.snapshots.length > 0 ? app.snapshots[app.snapshots.length - 1].filename : '---'
      ]);
    });

    jitsu.log.putRows('data', rows, colors);
    callback();
  });
};

//
// Usage for `jitsu apps list`
//
apps.list.usage = [
  'Lists all of the applications for the current user',
  '',
  'jitsu list',
  'jitsu apps list'
];

//
// ### function view (name, callback)
// #### @name {string} **optional** Name of the application to view
// #### @callback {function} Continuation to pass control to when complete.
// Views the application with the specfied `name` for the authenticated user.
// If no name is supplied this will view the application in the current directory.
//
apps.view = function (name, callback) {
  function executeView() {
    jitsu.apps.view(name, function (err, app) {
      if (err) {
        return callback(err);
      }

      app = jitsu.utils.formatApp(app);
      jitsu.log.putObject(app);
      callback(null, app);
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeView();
    });
  }

  executeView();
};

//
// Usage for `jitsu apps view [<name>]`
//
apps.view.usage = [
  'Lists the information for the application in the current',
  'directory. If <name> is supplied then that application',
  'is listed instead.',
  '',
  'jitsu apps view',
  'jitsu apps view <name>'
];

//
// ### function list (callback)
// #### @name {string} **optional** Name of the application to create
// #### @callback {function} Continuation to pass control to when complete.
// Lists the applications for the authenticated user.
//
apps.update = function (name, callback) {
  if (!callback) {
    callback = name;
    name = null;
  }

  winston.silly('Reading package.json in ' + process.cwd());
  jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
    name = name || pkg.name;
    jitsu.apps.view(name, function (err, app) {
      if (err) {
        return callback(err);
      }

      var diff = jitsu.utils.objectDiff(app, pkg);
      if (!diff) {
        winston.warn('No changes found to your package.json for ' + name.magenta);
        return callback();
      }

      winston.info('Updating application ' + name.magenta + ' with:');
      jitsu.log.putObject(diff);

      jitsu.apps.update(name, diff, callback);
    });
  });
};

//
// Usage for `jitsu apps update [<name>]`
//
apps.update.usage = [
  'Updates the application in the current directory',
  'with the information in the package.json file. If',
  '<name> is supplied the application with <name> is updated.',
  '',
  'jitsu apps update',
  'jitsu apps update <name>'
];

//
// ### function destroy (callback)
// #### @name {string} **optional** Name of the application to destroy.
// #### @callback {function} Continuation to pass control to when complete.
// Destroys the application specified by `name`. If no name is supplied
// this will destroy the application in the current directory.
//
apps.destroy = function (name, callback) {
  function executeDestroy() {
    winston.info('Destroying app ' + name.magenta);
    jitsu.apps.destroy(name, function (err) {
      winston.silly('Done destroying app ' + name.magenta);
      return err ? callback(err) : callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeDestroy();
    });
  }

  executeDestroy();
};

//
// Usage for `jitsu apps destroy [<name>]`
//
apps.destroy.usage = [
  'Destroys the application in the current directory. If',
  '<name> is supplied then that application is destroyed instead',
  '',
  'jitsu apps destroy',
  'jitsu apps destroy <name>'
];

//
// ### function start (callback)
// #### @name {string} **optional** Name of the application to start.
// #### @callback {function} Continuation to pass control to when complete.
// Starts the application specified by `name`. If no name is supplied
// this will start the application in the current directory.
//
apps.start = function (name, callback) {
  function executeStart() {
    winston.info('Starting app ' + name.magenta);
    jitsu.apps.start(name, function (err) {
      if (err) {
        return callback(err, true);
      }

      jitsu.apps.view(name, function (err, app) {
        if (err) {
          return callback(err);
        }

        winston.info('App ' + name.magenta + ' is now started')
        winston.info(('http://' + app.subdomain + '.nodejitsu.com').magenta + ' on Port ' + '80'.magenta);
        callback();
      });
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeStart();
    });
  }

  executeStart();
};

//
// Usage for `jitsu apps start [<name>]`
//
apps.start.usage = [
  'Starts the application in the current directory. If <name>',
  'is supplied then that application is started instead.',
  '',
  'jitsu apps start',
  'jitsu apps start <name>'
];

//
// ### function restart (name, callback)
// #### @name {string} **optional** Name of the application to restart.
// #### @callback {function} Continuation to pass control to when complete.
// Restarts the application specified by `name`. If no name is supplied
// this will restart the application in the current directory.
//
apps.restart = function (name, callback) {
  function executeRestart() {
    winston.info('Restarting app ' + name.magenta);
    jitsu.apps.restart(name, function (err) {
      if (err) {
        return callback(err);
      }

      winston.info('App ' + name.magenta + ' has been restarted');
      callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeRestart();
    });
  }

  executeRestart();
};

//
// Usage for `jitsu apps restart [<name>]`
//
apps.restart.usage = [
  'Restarts the application in the current directory. If <name>',
  'is supplied then that application is restarted instead.',
  '',
  'jitsu apps restart',
  'jitsu apps restart <name>'
];

//
// ### function stop (callback)
// #### @name {string} **optional** Name of the application to stop.
// #### @callback {function} Continuation to pass control to when complete.
// Stops the application specified by `name`. If no name is supplied
// this will stop the application in the current directory.
//
apps.stop = function (name, callback) {
  function executeStop() {
    winston.info('Stopping app ' + name.magenta);
    jitsu.apps.stop(name, function (err) {
      if (err) {
        return callback(err);
      }

      winston.info('App ' + name.magenta + ' is now stopped');
      callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeStop();
    });
  }

  executeStop();
};

//
// Usage for `jitsu apps stop [<name>]`
//
apps.stop.usage = [
  'Stops the application in the current directory. If <name>',
  'is supplied then that application is stopped instead.',
  '',
  'jitsu apps stop',
  'jitsu apps stop <name>'
];
<commit_msg>[minor] Respond to error in commands/apps.js<commit_after>/*
 * apps.js: Commands related to app resources
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var eyes = require('eyes'),
    winston = require('winston'),
    analyzer = require('require-analyzer'),
    jitsu = require('jitsu');

var apps = exports;

apps.usage = [
  '`jitsu apps *` commands allow you to work with your',
  'Applications on Nodejitsu. Valid commands are:',
  '',
  'jitsu apps create',
  'jitsu apps list',
  'jitsu apps deploy',
  'jitsu apps view    [<name>]',
  'jitsu apps update  [<name>]',
  'jitsu apps destroy [<name>]',
  'jitsu apps start   [<name>]',
  'jitsu apps restart [<name>]',
  'jitsu apps stop    [<name>]',
  '',
  'For commands that take a <name> parameter, if no parameter',
  'is supplied, jitsu will attempt to read the package.json',
  'from the current directory.'
];

//
// ### function deploy (callback)
// #### @callback {function} Continuation to respond to when complete.
// Deploys an application through the following steps:
// 1. Creates the application (if necessary)
// 2. Creates or validates the package.json
// 3. Packages and creates a new snapshot
// 4. Stops the application (if neccessary)
// 5. Starts the application
//
apps.deploy = function (callback) {
  var dir = process.cwd(), pkg;

  function startApp (err, existing) {
    if (err) {
      winston.error('Error creating snapshot for app ' + pkg.name.magenta);
      winston.error(err.message);
      return callback(new Error(), true);
    }

//    if (existing.state === 'started') {
//existing.state is thrown away in check app.
//these closures would need to be rearanged so that this closure can see the result of view..
      apps.stop(existing.name, function (err) {
        return err ? callback(err) : apps.start(existing.name, callback);
      });
//    }
//    else {
//      apps.start(existing.name, callback);
//    }
  }

  function activateSnapshot (existing, snapshot) {
    winston.info('Activating snapshot ' + snapshot.grey + ' for ' + existing.name.magenta);
    jitsu.snapshots.activate(existing.name, snapshot, function (err) {
      winston.silly('Done activating snapshot ' + snapshot.grey);
      startApp(err, existing);
    });
  }

  function updateApp (existing, snapshot) {
    winston.info('Updating application ' + existing.name.magenta);
    jitsu.apps.update(existing.name, pkg, function (err) {
      if (err) {
        return callback(err);
      }

      activateSnapshot(existing, snapshot);
    });
  }

  function updateSnapshot (err, existing, firstSnapshot) {
    if (err) {
      return callback(err, true, true);
    }

    jitsu.package.updateTarball(null, pkg, existing, firstSnapshot, function (err, snapshot) {
      if (err) {
        return callback(err, snapshot);
      }

      updateApp(existing, snapshot);
    });
  }

  function checkApp (err, local) {
    if (err) {
      winston.error(err);
      return callback(new Error(), true);
    }

    pkg = local;
    winston.silly('Checking if application ' + local.name.magenta + ' exists.');
    jitsu.apps.view(local.name, function (err, app) {
      if (err) {
        winston.warn('No application exists for ' + local.name.magenta);
        //
        // If there is a before filter we should run before creating the app do so,
        // then create the application.
        //
        return apps.create(local, function (err, existing) {
          updateSnapshot(err, existing, true);
        });
      }

      app = !app ? {} : jitsu.utils.formatApp(app, ['snapshots', 'active', 'user', 'state']);
      var pkg = analyzer.merge({}, local, app);
      jitsu.package.validate(pkg, dir, updateSnapshot);
    });
  }

  jitsu.package.read(dir, function (err, local) {
    return err ? jitsu.package.get(dir, checkApp) : jitsu.package.validate(local, dir, {}, checkApp);
  });
};

apps.deploy.usage = [
  '',
  'Deploys an application using the following steps:'.cyan.underline,
  '',
  '  1. Creates the application (if necessary)',
  '  2. Creates or validates the package.json',
  '  3. Packages and creates a new snapshot',
  '  4. Stops the application (if neccessary)',
  '  5. Starts the application',
  '',
  'jitsu deploy',
  'jitsu apps deploy'
];

//
// ### function list (callback)
// #### @target {string|Object} **optional** Name of the application to create
// #### @callback {function} Continuation to pass control to when complete.
// Creates an application for the package.json in the current directory
// using `name` if supplied and falling back to `package.name`.
//
apps.create = function (target, callback) {
  if (!callback) {
    callback = target;
    target = null;
  }

  var name;

  function executeCreate (err, pkg) {
    if (err) {
      winston.error(err);
      return callback(new Error(), true);
    }

    pkg.name = name || pkg.name;
    jitsu.apps.list(function (err, apps) {
      if (err) {
        return callback(err);
      }

      var username = jitsu.config.get('username'),
          existing;

      existing = apps.filter(function (a) {
        return a.name === pkg.name && a.user === username;
      });

      if (existing.length > 0) {
        return callback(new Error('Cannot create duplicate application ' + pkg.name.magenta), true, true);
      }

      //
      // TODO (indexzero): Configure this default value in nodejitsu APIs
      //
      pkg.state = 'stopped';

      winston.info('Checking app availability ' + pkg.name.magenta);
      jitsu.apps.available(pkg, function (err, result) {
        if (!result.available) {
          winston.error('Error creating ' + pkg.name.magenta);
          winston.error(result.message);
          return callback(new Error());
        }

        winston.info('Creating app ' + pkg.name.magenta);
        jitsu.apps.create(pkg, function (err, res, result) {
          winston.silly('Done creating app ' + pkg.name.magenta);
          return err ? callback(err) : callback(null, pkg);
        });
      });
    });
  }

  if (typeof target === 'object') {
    name = target.name;
    return executeCreate(null, target);
  }

  jitsu.package.get(process.cwd(), executeCreate);
};

//
// Usage for `jitsu apps create [<name>]`
//
apps.create.usage = [
  'Creates an application using the package.json file in the',
  'current directory, if <name> is supplied then this is used',
  'instead of the `name` property in the package.json file.',
  '',
  'jitsu create',
  'jitsu apps create',
  'jitsu apps create <name>'
];

//
// ### function list (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Lists the applications for the authenticated user.
//
apps.list = function (callback) {
  winston.info('Listing apps');
  jitsu.apps.list(function (err, apps) {
    if (err) {
      return callback(err);
    }

    if (!apps || apps.length === 0) {
      winston.warn('No applications exist.');
      winston.help('Try creating one with ' + 'jitsu apps create'.magenta);
      return callback();
    }

    var rows = [['name', 'state', 'subdomain', 'start', 'latest']],
        colors = ['underline', 'underline', 'underline', 'underline', 'underline'];

    apps.forEach(function (app) {
      rows.push([
        app.name,
        app.state || 'stopped',
        app.subdomain,
        app.scripts.start || '---',
        app.snapshots && app.snapshots.length > 0 ? app.snapshots[app.snapshots.length - 1].filename : '---'
      ]);
    });

    jitsu.log.putRows('data', rows, colors);
    callback();
  });
};

//
// Usage for `jitsu apps list`
//
apps.list.usage = [
  'Lists all of the applications for the current user',
  '',
  'jitsu list',
  'jitsu apps list'
];

//
// ### function view (name, callback)
// #### @name {string} **optional** Name of the application to view
// #### @callback {function} Continuation to pass control to when complete.
// Views the application with the specfied `name` for the authenticated user.
// If no name is supplied this will view the application in the current directory.
//
apps.view = function (name, callback) {
  function executeView() {
    jitsu.apps.view(name, function (err, app) {
      if (err) {
        return callback(err);
      }

      app = jitsu.utils.formatApp(app);
      jitsu.log.putObject(app);
      callback(null, app);
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeView();
    });
  }

  executeView();
};

//
// Usage for `jitsu apps view [<name>]`
//
apps.view.usage = [
  'Lists the information for the application in the current',
  'directory. If <name> is supplied then that application',
  'is listed instead.',
  '',
  'jitsu apps view',
  'jitsu apps view <name>'
];

//
// ### function list (callback)
// #### @name {string} **optional** Name of the application to create
// #### @callback {function} Continuation to pass control to when complete.
// Lists the applications for the authenticated user.
//
apps.update = function (name, callback) {
  if (!callback) {
    callback = name;
    name = null;
  }

  winston.silly('Reading package.json in ' + process.cwd());
  jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
    name = name || pkg.name;
    jitsu.apps.view(name, function (err, app) {
      if (err) {
        return callback(err);
      }

      var diff = jitsu.utils.objectDiff(app, pkg);
      if (!diff) {
        winston.warn('No changes found to your package.json for ' + name.magenta);
        return callback();
      }

      winston.info('Updating application ' + name.magenta + ' with:');
      jitsu.log.putObject(diff);

      jitsu.apps.update(name, diff, callback);
    });
  });
};

//
// Usage for `jitsu apps update [<name>]`
//
apps.update.usage = [
  'Updates the application in the current directory',
  'with the information in the package.json file. If',
  '<name> is supplied the application with <name> is updated.',
  '',
  'jitsu apps update',
  'jitsu apps update <name>'
];

//
// ### function destroy (callback)
// #### @name {string} **optional** Name of the application to destroy.
// #### @callback {function} Continuation to pass control to when complete.
// Destroys the application specified by `name`. If no name is supplied
// this will destroy the application in the current directory.
//
apps.destroy = function (name, callback) {
  function executeDestroy() {
    winston.info('Destroying app ' + name.magenta);
    jitsu.apps.destroy(name, function (err) {
      winston.silly('Done destroying app ' + name.magenta);
      return err ? callback(err) : callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeDestroy();
    });
  }

  executeDestroy();
};

//
// Usage for `jitsu apps destroy [<name>]`
//
apps.destroy.usage = [
  'Destroys the application in the current directory. If',
  '<name> is supplied then that application is destroyed instead',
  '',
  'jitsu apps destroy',
  'jitsu apps destroy <name>'
];

//
// ### function start (callback)
// #### @name {string} **optional** Name of the application to start.
// #### @callback {function} Continuation to pass control to when complete.
// Starts the application specified by `name`. If no name is supplied
// this will start the application in the current directory.
//
apps.start = function (name, callback) {
  function executeStart() {
    winston.info('Starting app ' + name.magenta);
    jitsu.apps.start(name, function (err) {
      if (err) {
        return callback(err, true);
      }

      jitsu.apps.view(name, function (err, app) {
        if (err) {
          return callback(err);
        }

        winston.info('App ' + name.magenta + ' is now started')
        winston.info(('http://' + app.subdomain + '.nodejitsu.com').magenta + ' on Port ' + '80'.magenta);
        callback();
      });
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeStart();
    });
  }

  executeStart();
};

//
// Usage for `jitsu apps start [<name>]`
//
apps.start.usage = [
  'Starts the application in the current directory. If <name>',
  'is supplied then that application is started instead.',
  '',
  'jitsu apps start',
  'jitsu apps start <name>'
];

//
// ### function restart (name, callback)
// #### @name {string} **optional** Name of the application to restart.
// #### @callback {function} Continuation to pass control to when complete.
// Restarts the application specified by `name`. If no name is supplied
// this will restart the application in the current directory.
//
apps.restart = function (name, callback) {
  function executeRestart() {
    winston.info('Restarting app ' + name.magenta);
    jitsu.apps.restart(name, function (err) {
      if (err) {
        return callback(err);
      }

      winston.info('App ' + name.magenta + ' has been restarted');
      callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeRestart();
    });
  }

  executeRestart();
};

//
// Usage for `jitsu apps restart [<name>]`
//
apps.restart.usage = [
  'Restarts the application in the current directory. If <name>',
  'is supplied then that application is restarted instead.',
  '',
  'jitsu apps restart',
  'jitsu apps restart <name>'
];

//
// ### function stop (callback)
// #### @name {string} **optional** Name of the application to stop.
// #### @callback {function} Continuation to pass control to when complete.
// Stops the application specified by `name`. If no name is supplied
// this will stop the application in the current directory.
//
apps.stop = function (name, callback) {
  function executeStop() {
    winston.info('Stopping app ' + name.magenta);
    jitsu.apps.stop(name, function (err) {
      if (err) {
        return callback(err);
      }

      winston.info('App ' + name.magenta + ' is now stopped');
      callback();
    });
  }

  if (!callback) {
    callback = name;
    return jitsu.package.tryRead(process.cwd(), callback, function (pkg) {
      name = pkg.name;
      executeStop();
    });
  }

  executeStop();
};

//
// Usage for `jitsu apps stop [<name>]`
//
apps.stop.usage = [
  'Stops the application in the current directory. If <name>',
  'is supplied then that application is stopped instead.',
  '',
  'jitsu apps stop',
  'jitsu apps stop <name>'
];
<|endoftext|>


<commit_before>// Josh de Leeuw
// Updated October 2013
//
// This plugin is for presenting a single image and collecting a key response.
// It can be used for categorizing images (without feedback), collecting yes/no responses, etc...
//
//  parameters
//      stimuli: array of stimuli to present. elements of the array can be either paths to images
//                  or HTML strings
//      choices: array of key codes that represent valid responses. other key codes will be ignored
//      continue_after_response: when true, the trial will end as soon as the user gives a response.
//                  if false, then the trial will continue until timing_response is reached
//      timing_stim: how long to show the stimulus for. -1 will show indefinitely.
//      timing_response: how long to wait for a response. this timer starts at the same time as the
//                  timer for the stimulus presentation. if the timer is reached without a response
//                  given, then the user's response will be recorded as a "-1" for the trial, and the
//                  trial will end.
//      timing_post_trial: how long to show a blank screen after the trial ends.
//      is_html: must set to true when using HTML strings as the stimuli.
//      prompt: optional HTML string to display with the stimulus.
//      data: the optional data object

(function($) {
    jsPsych["single-stim"] = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = new Array(params.stimuli.length);
            for (var i = 0; i < trials.length; i++) {
                trials[i] = {};
                trials[i].type = "single-stim";
                trials[i].a_path = params.stimuli[i];
                trials[i].choices = params.choices;
                // option to show image for fixed time interval, ignoring key responses
                //      true = image will keep displaying after response
                //      false = trial will immediately advance when response is recorded
                trials[i].continue_after_response = params.continue_after_response || true;
                // timing parameters
                trials[i].timing_stim = params.timing_stim || -1; // if -1, then show indefinitely
                trials[i].timing_response = params.timing_response || -1; // if -1, then wait for response forever
                trials[i].timing_post_trial = params.timing_post_trial || 1000;
                // optional parameters
                trials[i].is_html = (typeof params.is_html === 'undefined') ? false : true;
                trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
                trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
            }
            return trials;
        };

        plugin.trial = function(display_element, block, trial, part) {

            var trial_complete = false;

            var startTime = (new Date()).getTime();

            var key_press = -1;

            if (!trial.is_html) {
                display_element.append($('<img>', {
                    src: trial.a_path,
                    id: 'ss'
                }));
            }
            else {
                display_element.append($('<div>', {
                    html: trial.a_path,
                    id: 'ss'
                }));
            }

            //show prompt here
            if (trial.prompt !== "") {
                display_element.append(trial.prompt);
            }

            var cont_function = function() {
                var endTime = (new Date()).getTime();
                var rt = (endTime - startTime);
                trial_complete = true;

                var trial_data = {
                    "trial_type": "single-stim",
                    "trial_index": block.trial_idx,
                    "rt": rt,
                    "a_path": trial.a_path,
                    "key_press": key_press
                };

                block.writeData($.extend({}, trial_data, trial.data));
                $(document).unbind('keyup', resp_func);
                display_element.html('');
                setTimeout(function() {
                    block.next();
                }, trial.timing_post_trial);
            };

            var resp_func = function(e) {
                var flag = false;
                // check if the key is any of the options, or if it is an accidental keystroke
                for (var i = 0; i < trial.choices.length; i++) {
                    if (e.which == trial.choices[i]) {
                        flag = true;
                    }
                }
                if (flag) {
                    key_press = e.which;

                    // after a valid response, the stimulus will have the CSS class 'responded'
                    // which can be used to provide visual feedback that a response was recorded
                    $("#ss").addClass('responded');

                    if (trial.continue_after_response) {
                        // response triggers the next trial in this case.
                        // if hide_image_after_response is true, then next
                        // trial should be triggered by timeout function below.
                        cont_function();
                    }
                }
            };

            $(document).keyup(resp_func);

            // hide image if timing is set
            if (trial.timing_stim > 0) {
                setTimeout(function() {
                    if (!trial_complete) {
                        $('#ss').css('visibility', 'hidden');
                    }
                }, trial.timing_stim);
            }

            // end trial if time limit is set
            if (trial.timing_response > 0) {
                setTimeout(function() {
                    if (!trial_complete) {
                        cont_function();
                    }
                }, trial.timing_response);
            }

        };

        return plugin;
    })();
})(jQuery);<commit_msg>renaming a_path b_path, etc..<commit_after>// Josh de Leeuw
// Updated October 2013
//
// This plugin is for presenting a single image and collecting a key response.
// It can be used for categorizing images (without feedback), collecting yes/no responses, etc...
//
//  parameters
//      stimuli: array of stimuli to present. elements of the array can be either paths to images
//                  or HTML strings
//      choices: array of key codes that represent valid responses. other key codes will be ignored
//      continue_after_response: when true, the trial will end as soon as the user gives a response.
//                  if false, then the trial will continue until timing_response is reached
//      timing_stim: how long to show the stimulus for. -1 will show indefinitely.
//      timing_response: how long to wait for a response. this timer starts at the same time as the
//                  timer for the stimulus presentation. if the timer is reached without a response
//                  given, then the user's response will be recorded as a "-1" for the trial, and the
//                  trial will end.
//      timing_post_trial: how long to show a blank screen after the trial ends.
//      is_html: must set to true when using HTML strings as the stimuli.
//      prompt: optional HTML string to display with the stimulus.
//      data: the optional data object

(function($) {
    jsPsych["single-stim"] = (function() {

        var plugin = {};

        plugin.create = function(params) {
            var trials = new Array(params.stimuli.length);
            for (var i = 0; i < trials.length; i++) {
                trials[i] = {};
                trials[i].type = "single-stim";
                trials[i].a_path = params.stimuli[i];
                trials[i].choices = params.choices;
                // option to show image for fixed time interval, ignoring key responses
                //      true = image will keep displaying after response
                //      false = trial will immediately advance when response is recorded
                trials[i].continue_after_response = params.continue_after_response || true;
                // timing parameters
                trials[i].timing_stim = params.timing_stim || -1; // if -1, then show indefinitely
                trials[i].timing_response = params.timing_response || -1; // if -1, then wait for response forever
                trials[i].timing_post_trial = params.timing_post_trial || 1000;
                // optional parameters
                trials[i].is_html = (typeof params.is_html === 'undefined') ? false : true;
                trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
                trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
            }
            return trials;
        };

        plugin.trial = function(display_element, block, trial, part) {

            var trial_complete = false;

            var startTime = (new Date()).getTime();

            var key_press = -1;

            if (!trial.is_html) {
                display_element.append($('<img>', {
                    src: trial.a_path,
                    id: 'ss'
                }));
            }
            else {
                display_element.append($('<div>', {
                    html: trial.a_path,
                    id: 'ss'
                }));
            }

            //show prompt here
            if (trial.prompt !== "") {
                display_element.append(trial.prompt);
            }

            var cont_function = function() {
                var endTime = (new Date()).getTime();
                var rt = (endTime - startTime);
                trial_complete = true;

                var trial_data = {
                    "trial_type": "single-stim",
                    "trial_index": block.trial_idx,
                    "rt": rt,
                    "stimulus": trial.a_path,
                    "key_press": key_press
                };

                block.writeData($.extend({}, trial_data, trial.data));
                $(document).unbind('keyup', resp_func);
                display_element.html('');
                setTimeout(function() {
                    block.next();
                }, trial.timing_post_trial);
            };

            var resp_func = function(e) {
                var flag = false;
                // check if the key is any of the options, or if it is an accidental keystroke
                for (var i = 0; i < trial.choices.length; i++) {
                    if (e.which == trial.choices[i]) {
                        flag = true;
                    }
                }
                if (flag) {
                    key_press = e.which;

                    // after a valid response, the stimulus will have the CSS class 'responded'
                    // which can be used to provide visual feedback that a response was recorded
                    $("#ss").addClass('responded');

                    if (trial.continue_after_response) {
                        // response triggers the next trial in this case.
                        // if hide_image_after_response is true, then next
                        // trial should be triggered by timeout function below.
                        cont_function();
                    }
                }
            };

            $(document).keyup(resp_func);

            // hide image if timing is set
            if (trial.timing_stim > 0) {
                setTimeout(function() {
                    if (!trial_complete) {
                        $('#ss').css('visibility', 'hidden');
                    }
                }, trial.timing_stim);
            }

            // end trial if time limit is set
            if (trial.timing_response > 0) {
                setTimeout(function() {
                    if (!trial_complete) {
                        cont_function();
                    }
                }, trial.timing_response);
            }

        };

        return plugin;
    })();
})(jQuery);<|endoftext|>