<commit_before>(function(){
    // Book

    // Throughout the book, a method method is used to define new methods.
    // It will be explained in Chapter 4.
    Function.prototype.method = function (name, func) {
       this.prototype[name] = func;
        return this;
    };

    if (typeof Object.create !== 'function') {
        Object.create = function (o) {
            var F = function () {};
            F.prototype = o;
            return new F();
        };
    }

    var empty_object = {};
    var stooge = {
        "first-name": "Jerome",
        "last-name": "Howard"
    };
    var another_stooge = Object.create(stooge);

    jQuery(function($){
        $('input[name="throw-ex"]').click(function(){
            throw {name: 'textEx', message: 'test-ex-message'};
            //throw 'test-ex-message';
        });
    });
})();<commit_msg>play with js<commit_after>(function(){
    // ==========================================================
    // Book

    // Throughout the book, a method method is used to define new methods.
    // It will be explained in Chapter 4.
    Function.prototype.method = function (name, func) {
       this.prototype[name] = func;
        return this;
    };

    // When youmake a new object, youcan select the object that should be its prototype.
    // The mechanism that JavaScript provides to do this is messy and complex, but it can
    // be significantly simplified. We will add a create method to the Object function. The
    // create method creates a new object that uses an old object as its prototype. There
    // will be much more about functions in the next chapter.
    if (typeof Object.create !== 'function') {
        Object.create = function (o) {
            var F = function () {};
            F.prototype = o;
            return new F();
        };
    }

    var stooge = {
        "first-name": "Jerome",
        "last-name": "Howard"
    };
    var another_stooge = Object.create(stooge);
    stooge.dao = {cs: 'localhost'};

    // The prototype link has no effect on updating. When we make changes to an object,
    // the object’s prototype is not touched:
    another_stooge['first-name'] = 'Harry';
    another_stooge['middle-name'] = 'Moses';
    another_stooge.nickname = 'Moe';

    console.log('This shows how change of "stooge" object also changes "another_stooge" - prototyped from "stooge" (memo field)' );
    stooge.memo = 'our memo';
    console.log('stooge: ');
    console.log(stooge);
    console.log('another_stooge: ');
    console.log(another_stooge);

    delete another_stooge.nickname;
    //console.log(another_stooge);

    // Features
    var status = stooge.status || "unknown"; // default value
    var nex = stooge.deo && stooge.deo.cs; // guard against 'type error'
    var ex = stooge.dao && stooge.dao.cs;

    console.log('nex: ' + nex + ' ex: ' + ex);

    var flight = {
        airline: "Oceanic",
        number: 815,
        departure: {
            IATA: "SYD",
            time: "2004-09-22 14:55",
            city: "Sydney"
        },
        arrival: {
            IATA: "LAX",
            time: "2004-09-23 10:42",
            city: "Los Angeles"
        }
    };
    // =================================
    // My stuff
    function MyObjecto(data) {
        this.status = 'active';
        this.data = data;
        this[''] = 'empty string value';
    }

    var myObjecto1 = new MyObjecto();
    //console.log(myObjecto1);
    delete myObjecto1;

    jQuery(function($){
        $('input[name="throw-ex"]').click(function(){
            throw {name: 'textEx', message: 'test-ex-message'};
            //throw 'test-ex-message';
        });
    });
})();<|endoftext|>


<commit_before>import Effect, { ConfigUI, fract } from './effect';
import { parseHtml } from '../ui/util';
import Flickr, { ApiKey } from '../polyfills/flickr';

const EffectName = 'Flickr Image';
const Attribution = 'This product uses the Flickr API but is not endorsed or certified by Flickr.';

class FlickrImageConfigUI extends ConfigUI {
  constructor() {
    super();
    const searchInputClass = 'effect-flickr-img-search-term';
    this.element = parseHtml(`
      <fieldset>
        <legend>${EffectName}</legend>
        <label>
          Search term:
          <input type="text" class="${searchInputClass}"/>
        </label>
      </fieldset>
    `);
    const ui = this.element;
    this.searchTermInput = this.element.querySelector(`.${searchInputClass}`);
    this.searchTermInput.addEventListener('change', () => {
      this.notifyChange();
    });
  }

  getElement() {
    return this.element;
  }

  getConfig() {
    const config = {};

    config.searchTerm = this.searchTermInput.value;

    return config;
  }

  applyConfig(config) {
    this.searchTermInput.value = config.searchTerm;
  }
}

const flickr = new Flickr({ api_key: ApiKey });

export default class FlickrImageEffect extends Effect {
  static registerAsync(instance, props, uniforms, vertexShader) {
    const prefetchCount = 5;

    // The logic for getting a continuous stream of images from flickr
    let page = 1;
    let loadsInProgress = 0;
    const initialQueryTime = Math.floor(Date.now() / 1000);
    const runFlickrQuery = (processResponse) => {
      // Two different flickr apis, depending on search string content
      let query = null;
      if (instance.config.searchTerm === '') {
        query = flickr
        .photos
        .getRecent({
          per_page: prefetchCount,
          page,
          max_upload_date: initialQueryTime
        }).then(processResponse);
      } else {
        query = flickr
        .photos
        .search({
          text: instance.config.searchTerm,
          per_page: prefetchCount,
          page,
          max_upload_date: initialQueryTime
        }).then(processResponse);
      }
      loadsInProgress += prefetchCount;
      page = page + 1;
      return query;
    };

    // This method is called the first time we have a list of potential images
    const onInitialFlickrResponse = (photos) => new Promise((finishRegister, rej) => {
      const loadedImgs = [];
      const particleDataQueue = [];
      const loadQueue = [];

      const processPhoto = (photo) => flickr.photos.getSizes({
        photo_id: photo.id
      }).then((sizes) => new Promise((res, rej) => {
        const original = sizes.sizes.size.find((size) => {
          return size.width >= props.config.xParticlesCount;
        }) || sizes.sizes.size[sizes.sizes.size.length - 1];
        const url = original.source;
        const loader = document.createElement('img');
        loader.crossOrigin = 'Anonymous';
        loader.src = url;
        loader.onload = () => {
          loadedImgs.push(loader);
          loadsInProgress = loadsInProgress - 1;
          res();
        };
      }));

      // kick off loading process for each image
      for (let i = 0; i < photos.photos.photo.length; i++) {
        loadQueue.push(processPhoto(photos.photos.photo[i]));
      }
      // As soon as we have a fully-loaded image, use it!
      Promise.race(loadQueue).then(() => {
        let alive = true;
        let prevWasChange = false;
        let displayed = -1;
        // Run this in a loop to check if we need to update the image
        const checkTime = () => {
          if (!alive) {
            return;
          }
          // synchronize the contents of loadedImgs with particleDataQueue
          while(particleDataQueue.length < loadedImgs.length) {
            particleDataQueue.push(
              props.state.createParticleDataFromDomImg(
                loadedImgs[particleDataQueue.length],
                props.config.xParticlesCount, props.config.yParticlesCount
              )
            );
            // intentional break. Avoid too much work per RAF iteration
            break;
          }
          const tDist = instance.timeBegin - props.clock.getTime();
          if (tDist >= 0 && tDist <= props.clock.getDelta()) {
            // free unneeded resources when we proceed to the next image
            let freed = -1;
            if (displayed !== -1 && particleDataQueue.length > 1) {
              loadedImgs.shift();
              freed = particleDataQueue.shift();
            }
            displayed = particleDataQueue[0];
            props.state.setParticleData(displayed);
            // do the free AFTER we setParticleData to a new one
            if (freed !== -1) {
              props.state.destroyParticleData(freed);
              if (loadsInProgress < prefetchCount && loadedImgs.length < prefetchCount) {
                runFlickrQuery((photos) => {
                  for (let i = 0; i < photos.photos.photo.length; i++) {
                    processPhoto(photos.photos.photo[i]);
                  }
                });
              }
            }
          }
          window.requestAnimationFrame(checkTime);
        };
        checkTime();
        props.state.addHook(() => {
          alive = false;
        });

        finishRegister();
      });
    });

    return runFlickrQuery(onInitialFlickrResponse);
  }

  static getDisplayName() {
    return EffectName;
  }

  static getConfigUI() {
    if (!this._configUI) {
      this._configUI = new FlickrImageConfigUI();
    }

    return this._configUI;
  }

  static getDefaultConfig() {
    return {
      searchTerm: ''
    };
  }

  static getRandomConfig() {
    return this.getDefaultConfig();
  }
}
<commit_msg>Wrap page number in FlickrImageEffect<commit_after>import Effect, { ConfigUI, fract } from './effect';
import { parseHtml } from '../ui/util';
import Flickr, { ApiKey } from '../polyfills/flickr';

const EffectName = 'Flickr Image';
const Attribution = 'This product uses the Flickr API but is not endorsed or certified by Flickr.';

class FlickrImageConfigUI extends ConfigUI {
  constructor() {
    super();
    const searchInputClass = 'effect-flickr-img-search-term';
    this.element = parseHtml(`
      <fieldset>
        <legend>${EffectName}</legend>
        <label>
          Search term:
          <input type="text" class="${searchInputClass}"/>
        </label>
      </fieldset>
    `);
    const ui = this.element;
    this.searchTermInput = this.element.querySelector(`.${searchInputClass}`);
    this.searchTermInput.addEventListener('change', () => {
      this.notifyChange();
    });
  }

  getElement() {
    return this.element;
  }

  getConfig() {
    const config = {};

    config.searchTerm = this.searchTermInput.value;

    return config;
  }

  applyConfig(config) {
    this.searchTermInput.value = config.searchTerm;
  }
}

const flickr = new Flickr({ api_key: ApiKey });

export default class FlickrImageEffect extends Effect {
  static registerAsync(instance, props, uniforms, vertexShader) {
    const prefetchCount = 5;

    // The logic for getting a continuous stream of images from flickr
    let page = 1;
    let loadsInProgress = 0;
    const initialQueryTime = Math.floor(Date.now() / 1000);
    const runFlickrQuery = (processResponse) => {
      const onResponse = (response) => {
        // since page is 1-indexed, a real greater is necessary
        if (page > response.photos.pages) {
          page = 1;
        }
        processResponse(response);
      };
      // Two different flickr apis, depending on search string content
      let query = null;
      if (instance.config.searchTerm === '') {
        query = flickr
        .photos
        .getRecent({
          per_page: prefetchCount,
          page,
          max_upload_date: initialQueryTime
        }).then(onResponse);
      } else {
        query = flickr
        .photos
        .search({
          text: instance.config.searchTerm,
          per_page: prefetchCount,
          page,
          max_upload_date: initialQueryTime
        }).then(onResponse);
      }
      loadsInProgress += prefetchCount;
      page = page + 1;
      return query;
    };

    // This method is called the first time we have a list of potential images
    const onInitialFlickrResponse = (photos) => new Promise((finishRegister, rej) => {
      const loadedImgs = [];
      const particleDataQueue = [];
      const loadQueue = [];

      const processPhoto = (photo) => flickr.photos.getSizes({
        photo_id: photo.id
      }).then((sizes) => new Promise((res, rej) => {
        const original = sizes.sizes.size.find((size) => {
          return size.width >= props.config.xParticlesCount;
        }) || sizes.sizes.size[sizes.sizes.size.length - 1];
        const url = original.source;
        const loader = document.createElement('img');
        loader.crossOrigin = 'Anonymous';
        loader.src = url;
        loader.onload = () => {
          loadedImgs.push(loader);
          loadsInProgress = loadsInProgress - 1;
          res();
        };
      }));

      // kick off loading process for each image
      for (let i = 0; i < photos.photos.photo.length; i++) {
        loadQueue.push(processPhoto(photos.photos.photo[i]));
      }
      // As soon as we have a fully-loaded image, use it!
      Promise.race(loadQueue).then(() => {
        let alive = true;
        let prevWasChange = false;
        let displayed = -1;
        // Run this in a loop to check if we need to update the image
        const checkTime = () => {
          if (!alive) {
            return;
          }
          // synchronize the contents of loadedImgs with particleDataQueue
          while(particleDataQueue.length < loadedImgs.length) {
            particleDataQueue.push(
              props.state.createParticleDataFromDomImg(
                loadedImgs[particleDataQueue.length],
                props.config.xParticlesCount, props.config.yParticlesCount
              )
            );
            // intentional break. Avoid too much work per RAF iteration
            break;
          }
          const tDist = instance.timeBegin - props.clock.getTime();
          if (tDist >= 0 && tDist <= props.clock.getDelta()) {
            // free unneeded resources when we proceed to the next image
            let freed = -1;
            if (displayed !== -1 && particleDataQueue.length > 1) {
              loadedImgs.shift();
              freed = particleDataQueue.shift();
            }
            displayed = particleDataQueue[0];
            props.state.setParticleData(displayed);
            // do the free AFTER we setParticleData to a new one
            if (freed !== -1) {
              props.state.destroyParticleData(freed);
              if (loadsInProgress < prefetchCount && loadedImgs.length < prefetchCount) {
                runFlickrQuery((photos) => {
                  for (let i = 0; i < photos.photos.photo.length; i++) {
                    processPhoto(photos.photos.photo[i]);
                  }
                });
              }
            }
          }
          window.requestAnimationFrame(checkTime);
        };
        checkTime();
        props.state.addHook(() => {
          alive = false;
        });

        finishRegister();
      });
    });

    return runFlickrQuery(onInitialFlickrResponse);
  }

  static getDisplayName() {
    return EffectName;
  }

  static getConfigUI() {
    if (!this._configUI) {
      this._configUI = new FlickrImageConfigUI();
    }

    return this._configUI;
  }

  static getDefaultConfig() {
    return {
      searchTerm: ''
    };
  }

  static getRandomConfig() {
    return this.getDefaultConfig();
  }
}
<|endoftext|>


<commit_before>define(["elaiJS/widget", "elaiJS/binder", "elaiJS/helper", "elaiJS/promise"],
				function(widgetManager, binder, helper, Promise) {
	'use strict';
	var properties = {};
	properties.parent = null;

	properties.builder = function() {

		this.create = function create(skipEvent) {
			return this.processAction("create", false, undefined, skipEvent);
		};

		this.initialize = function initialize(params, skipEvent) {
			return this.processAction("initialize", false, function() {
				this.params = params || {};
				this.renderParams = {};
				return this.destroyChildren();
			}, skipEvent);
		};

		this.refresh = function refresh(skipEvent) {
			this.before("refresh", skipEvent);

			var fireCallback = this.buildAfterCB("refresh", undefined, skipEvent);
			return this.refreshData().then(function() {
				return this.refreshRender(false).then(function() {
					return callChildrenIfNeeded.call(this, "refresh").then(fireCallback);
				}.bind(this));
			}.bind(this));
		};

		this.refreshData = function refreshData(skipEvent) {
			this.before("refreshData", skipEvent);

			var fireCallback = this.buildAfterCB("refreshData", undefined, skipEvent);
			return this.fetchData().then(function(rawData) {
				return this.setData(rawData, true).then(fireCallback);
			}.bind(this));
		};

		this.fetchData = function fetchData(skipEvent) {
			return this.processAction("fetchData", false, undefined, skipEvent);
		};

		this.setData = function setData(data, needProcess, skipEvent) {
			this.before("setData", skipEvent);

			var promise;

			if(needProcess) {
				this.rawData = data;
				promise = this.processRawData().then(function(result) {
					this.data = result;
				}.bind(this));
			}
			else {
				this.rawData = undefined;
				this.data = data;
				promise = Promise.resolve();
			}

			var fireCallback = this.buildAfterCB("setData", undefined, skipEvent);
			return promise.then(fireCallback);
		};

		this.processRawData = function processRawData(skipEvent) {
			return this.processAction("processRawData", false, undefined, skipEvent);
		};

		this.refreshRender = function refreshRender(callChildren, skipEvent) {
			return this.processAction("refreshRender", false, undefined, skipEvent);
		};

		this.render = function render(renderParams, skipEvent) {
			return this.processAction("render", true, function() {
				this.renderParams = renderParams;
			}, skipEvent);
		};

		this.removeRender = function removeRender(callChildren, skipEvent) {
			return this.processAction("removeRender", true, undefined, skipEvent);
		};

		this.reload = function reload(params, renderParams, skipEvent) {
			this.before("reload", skipEvent);

			var fireCallback = this.buildAfterCB("reload", undefined, skipEvent);
			return this.initialize(params || this.params).then(function () {
				return this.removeRender(false).then(function() {
					return this.render(renderParams || this.renderParams).then(fireCallback);
				}.bind(this));
			}.bind(this));
		};

		this.destroy = function destroy(skipEvent) {
			return this.processAction("destroy", false, function() {
				if(this.widgetParent)
					delete this.widgetParent.children[this.id];

				return this.destroyChildren().then(function() {
					return this.remove(false).then(this.unbindAll.bind(this));
				}.bind(this));
			}, skipEvent);
		};

		this.remove = function (callChildren, skipEvent) {
			this.before("remove", skipEvent);

			return this.removeRender(callChildren).then(function() {
				if(widgetManager.get(this.id))
					widgetManager.remove(this);

				this.after("remove", skipEvent);
			}.bind(this));
		};

		/**********************************************************************
		******************************* Helpers *******************************
		**********************************************************************/
		this.processAction = function(actionName, callChildren, duringAction, skipEvent) {
			this.before(actionName, skipEvent);

			var promise = Promise.resolve();
			if(helper.isFunction(duringAction))
				promise = promise.then(duringAction.bind(this));

			return promise.then(function() {
				var fireCallback = this.buildAfterCB(actionName, undefined, skipEvent);
				return executeAsynCycleLifeFcts.call(this, actionName, callChildren).then(fireCallback);
			}.bind(this));
		};

		function executeAsynCycleLifeFcts(name, callChildren) {
			return this.callPluginsFct(name + "BeforeWidget").then(function() {
				return callInternalWidgetFctPromise.call(this, name).then(function(result) {
					return this.callPluginsFct(name + "AfterWidget").then(function() {
						if(!callChildren)
							return result;
						return callChildrenIfNeeded.call(this, name).then(function() {
							return result;
						}.bind(this));
					}.bind(this));
				}.bind(this));
			}.bind(this));
		}

		function callInternalWidgetFctPromise(name) {
			return Promise.resolve(name).then(callInternalWidgetFct.bind(this));
		}

		function callInternalWidgetFct(name) {
			var fctName = "_" + name;
			if(helper.isFunction(this[fctName]))
				return this[fctName]();
		}

		this.callPluginsFct = function callPluginsFct(name) {
			this.before(name + "Plugins");

			var promises = [];
			for(var i in this.plugins) {
				var plugin = this.plugins[i].plugin;
				if(helper.isFunction(plugin[name]))
					promises.push(plugin[name].call(this));
			}

			var fireCallback = this.buildAfterCB(name + "Plugins");
			return Promise.all(promises).then(fireCallback);
		};

		function callChildrenIfNeeded(name, overrideValue) {
			return Promise.resolve().then(function() {
				var needChildren = isNeedChildren.call(this, name, overrideValue);
				if(needChildren)
					return this[name + "Children"]();
			}.bind(this));
		}

		function isNeedChildren(name, defaultValue) {
			defaultValue = defaultValue === undefined ? true : defaultValue;
			var value = "need" + helper.capitalize(name) + "Children";
			return getBooleanValue.call(this, value, defaultValue);
		}

		function getBooleanValue(name, defaultValue) {
			var result = this[name];
			if(helper.isFunction(this[name]))
				result = this[name]();

			if(result === undefined)
				result = defaultValue;

		 return result;
		}

		/**********************************************************************
		******************************* Childs ********************************
		**********************************************************************/
		this.children = {};

		this.findChild = function findChild(name, recursively) {
			for(var key in this.children) {
				var child = this.children[key];
				if(child.name.toLowerCase() === name.toLowerCase())
					return child;

				if(recursively) {
					var childResult = child.findChild(name, true);
					if(childResult)
						return childResult;
				}
			}
		};

		this.findChilds = function findChilds(name, recursively) {
			var result = [];
			for(var key in this.children) {
				var child = this.children[key];
				if(child.name.toLowerCase() === name.toLowerCase())
					result.push(child);

				if(recursively)
					result = result.concat(child.findChilds(name, true));
			}

			return result;
		};

		this.addChild = function addChild(child, skipEvent) {
			this.before("addChild", skipEvent);

			this.children[child.id] = child;
			child.widgetParent = this;

			if(!widgetManager.get(child.id))
				widgetManager.add(child);

			this.after("addChild", skipEvent);
		};

		this.createChild = function createChild(widgetInfo, id, params, skipEvent) {
			this.before("createChild", skipEvent);

			if(!helper.isObject(widgetInfo))
				widgetInfo = {name: widgetInfo, mode: this.mode};
			else if(widgetInfo.mode === undefined)
				widgetInfo.mode = this.mode;

			var fireCallback = this.buildAfterCB("createChild", undefined, skipEvent);
			return widgetManager.create(widgetInfo, id, params).then(function (widget) {
				this.addChild(widget);
				fireCallback(widget);
				return widget;
			}.bind(this));
		};

		this.createChildAndRender = function (widgetInfo, id, params, renderParams) {
			return this.createChild(widgetInfo, id, params).then(function(child) {
				return child.render(renderParams).then(function() {
					return child;
				});
			});
		};

		this.removeRenderChildren = function removeRender(skipEvent) {
			return this.applyOnChildren("removeRender", undefined, skipEvent);
		};

		this.renderChildren = function renderChildren(skipEvent) {
			return this.applyOnChildren("render", undefined, skipEvent);
		};

		this.reloadChildren = function reloadChildren(skipEvent) {
			return this.applyOnChildren("reload", undefined, skipEvent);
		};

		this.refreshChildren = function refreshChildren(skipEvent) {
			return this.applyOnChildren("refresh", undefined, skipEvent);
		};

		this.destroyChildren = function destroyChildren(skipEvent) {
			return this.applyOnChildren("destroy", undefined, skipEvent);
		};

		this.applyOnChildren = function applyOnChildren(actionName, action, skipEvent) {
			this.before(actionName + "Children", skipEvent);

			if(!action) {
				action = function(child) {
					return child[actionName]();
				};
			}

			var fireCallback = this.buildAfterCB(actionName + "Children", undefined, skipEvent);
			var promises = [];
			for(var key in this.children)
				promises.push(action(this.children[key]));
			return Promise.all(promises).then(fireCallback);
		};

		function addChildrenActionByID(action) {
			this[action + "ChildByID"] = function(id, args) {
				var wChild = this.children[id];
				if(!wChild)
					return;

				return wChild[action].apply(wChild, args);
			};
		}

		function addChildrenActionByName(action) {
			this[action + "ChildsByName"] = function(name, args) {
				var wChilds = this.findChilds(name);
				var promises = [];
				for(var i in wChilds)
					promises.push(wChilds[i][action].apply(wChilds[i], args));
				return Promise.all(promises);
			};
		}

		addChildrenActionByID.call(this, "destroy");
		addChildrenActionByName.call(this, "destroy");

		/**********************************************************************
		******************************* Events ********************************
		**********************************************************************/
		binder.addAllFunctions(this);

		this.fire = function fireWidget(event, data) {
			event = binder.buildEvent(event, data);
			binder.fire.call(this, event);

			if(helper.isFunction(this[event.type]))
				this[event.type](event);
		};

		this.before = function before(functionName, skipEvent) {
			sendEvent.call(this, functionName, "before", skipEvent);
		};

		this.after = function after(functionName, skipEvent) {
			sendEvent.call(this, functionName, "after", skipEvent);
		};

		function sendEvent(functionName, suffix, skipEvent) {
			if(skipEvent !== true)
				this.fire(suffix + helper.capitalize(functionName));
		}

		this.buildAfterCB = function buildAfterCB(functionName, callback, skipEvent) {
			return function(result) {
				this.after(functionName, skipEvent);
				if(helper.isFunction(callback))
					callback.apply(this, arguments);
				return result;
			}.bind(this);
		};

		this.async = function(callback, timeout, params) {
			setTimeout(function() {
				callback.apply(this, params);
			}.bind(this), timeout);
		};
	};

	return properties;
});<commit_msg>Prevent issue on special actionChildByID<commit_after>define(["elaiJS/widget", "elaiJS/binder", "elaiJS/helper", "elaiJS/promise"],
				function(widgetManager, binder, helper, Promise) {
	'use strict';
	var properties = {};
	properties.parent = null;

	properties.builder = function() {

		this.create = function create(skipEvent) {
			return this.processAction("create", false, undefined, skipEvent);
		};

		this.initialize = function initialize(params, skipEvent) {
			return this.processAction("initialize", false, function() {
				this.params = params || {};
				this.renderParams = {};
				return this.destroyChildren();
			}, skipEvent);
		};

		this.refresh = function refresh(skipEvent) {
			this.before("refresh", skipEvent);

			var fireCallback = this.buildAfterCB("refresh", undefined, skipEvent);
			return this.refreshData().then(function() {
				return this.refreshRender(false).then(function() {
					return callChildrenIfNeeded.call(this, "refresh").then(fireCallback);
				}.bind(this));
			}.bind(this));
		};

		this.refreshData = function refreshData(skipEvent) {
			this.before("refreshData", skipEvent);

			var fireCallback = this.buildAfterCB("refreshData", undefined, skipEvent);
			return this.fetchData().then(function(rawData) {
				return this.setData(rawData, true).then(fireCallback);
			}.bind(this));
		};

		this.fetchData = function fetchData(skipEvent) {
			return this.processAction("fetchData", false, undefined, skipEvent);
		};

		this.setData = function setData(data, needProcess, skipEvent) {
			this.before("setData", skipEvent);

			var promise;

			if(needProcess) {
				this.rawData = data;
				promise = this.processRawData().then(function(result) {
					this.data = result;
				}.bind(this));
			}
			else {
				this.rawData = undefined;
				this.data = data;
				promise = Promise.resolve();
			}

			var fireCallback = this.buildAfterCB("setData", undefined, skipEvent);
			return promise.then(fireCallback);
		};

		this.processRawData = function processRawData(skipEvent) {
			return this.processAction("processRawData", false, undefined, skipEvent);
		};

		this.refreshRender = function refreshRender(callChildren, skipEvent) {
			return this.processAction("refreshRender", false, undefined, skipEvent);
		};

		this.render = function render(renderParams, skipEvent) {
			return this.processAction("render", true, function() {
				this.renderParams = renderParams;
			}, skipEvent);
		};

		this.removeRender = function removeRender(callChildren, skipEvent) {
			return this.processAction("removeRender", true, undefined, skipEvent);
		};

		this.reload = function reload(params, renderParams, skipEvent) {
			this.before("reload", skipEvent);

			var fireCallback = this.buildAfterCB("reload", undefined, skipEvent);
			return this.initialize(params || this.params).then(function () {
				return this.removeRender(false).then(function() {
					return this.render(renderParams || this.renderParams).then(fireCallback);
				}.bind(this));
			}.bind(this));
		};

		this.destroy = function destroy(skipEvent) {
			return this.processAction("destroy", false, function() {
				if(this.widgetParent)
					delete this.widgetParent.children[this.id];

				return this.destroyChildren().then(function() {
					return this.remove(false).then(this.unbindAll.bind(this));
				}.bind(this));
			}, skipEvent);
		};

		this.remove = function (callChildren, skipEvent) {
			this.before("remove", skipEvent);

			return this.removeRender(callChildren).then(function() {
				if(widgetManager.get(this.id))
					widgetManager.remove(this);

				this.after("remove", skipEvent);
			}.bind(this));
		};

		/**********************************************************************
		******************************* Helpers *******************************
		**********************************************************************/
		this.processAction = function(actionName, callChildren, duringAction, skipEvent) {
			this.before(actionName, skipEvent);

			var promise = Promise.resolve();
			if(helper.isFunction(duringAction))
				promise = promise.then(duringAction.bind(this));

			return promise.then(function() {
				var fireCallback = this.buildAfterCB(actionName, undefined, skipEvent);
				return executeAsynCycleLifeFcts.call(this, actionName, callChildren).then(fireCallback);
			}.bind(this));
		};

		function executeAsynCycleLifeFcts(name, callChildren) {
			return this.callPluginsFct(name + "BeforeWidget").then(function() {
				return callInternalWidgetFctPromise.call(this, name).then(function(result) {
					return this.callPluginsFct(name + "AfterWidget").then(function() {
						if(!callChildren)
							return result;
						return callChildrenIfNeeded.call(this, name).then(function() {
							return result;
						}.bind(this));
					}.bind(this));
				}.bind(this));
			}.bind(this));
		}

		function callInternalWidgetFctPromise(name) {
			return Promise.resolve(name).then(callInternalWidgetFct.bind(this));
		}

		function callInternalWidgetFct(name) {
			var fctName = "_" + name;
			if(helper.isFunction(this[fctName]))
				return this[fctName]();
		}

		this.callPluginsFct = function callPluginsFct(name) {
			this.before(name + "Plugins");

			var promises = [];
			for(var i in this.plugins) {
				var plugin = this.plugins[i].plugin;
				if(helper.isFunction(plugin[name]))
					promises.push(plugin[name].call(this));
			}

			var fireCallback = this.buildAfterCB(name + "Plugins");
			return Promise.all(promises).then(fireCallback);
		};

		function callChildrenIfNeeded(name, overrideValue) {
			return Promise.resolve().then(function() {
				var needChildren = isNeedChildren.call(this, name, overrideValue);
				if(needChildren)
					return this[name + "Children"]();
			}.bind(this));
		}

		function isNeedChildren(name, defaultValue) {
			defaultValue = defaultValue === undefined ? true : defaultValue;
			var value = "need" + helper.capitalize(name) + "Children";
			return getBooleanValue.call(this, value, defaultValue);
		}

		function getBooleanValue(name, defaultValue) {
			var result = this[name];
			if(helper.isFunction(this[name]))
				result = this[name]();

			if(result === undefined)
				result = defaultValue;

		 return result;
		}

		/**********************************************************************
		******************************* Childs ********************************
		**********************************************************************/
		this.children = {};

		this.findChild = function findChild(name, recursively) {
			for(var key in this.children) {
				var child = this.children[key];
				if(child.name.toLowerCase() === name.toLowerCase())
					return child;

				if(recursively) {
					var childResult = child.findChild(name, true);
					if(childResult)
						return childResult;
				}
			}
		};

		this.findChilds = function findChilds(name, recursively) {
			var result = [];
			for(var key in this.children) {
				var child = this.children[key];
				if(child.name.toLowerCase() === name.toLowerCase())
					result.push(child);

				if(recursively)
					result = result.concat(child.findChilds(name, true));
			}

			return result;
		};

		this.addChild = function addChild(child, skipEvent) {
			this.before("addChild", skipEvent);

			this.children[child.id] = child;
			child.widgetParent = this;

			if(!widgetManager.get(child.id))
				widgetManager.add(child);

			this.after("addChild", skipEvent);
		};

		this.createChild = function createChild(widgetInfo, id, params, skipEvent) {
			this.before("createChild", skipEvent);

			if(!helper.isObject(widgetInfo))
				widgetInfo = {name: widgetInfo, mode: this.mode};
			else if(widgetInfo.mode === undefined)
				widgetInfo.mode = this.mode;

			var fireCallback = this.buildAfterCB("createChild", undefined, skipEvent);
			return widgetManager.create(widgetInfo, id, params).then(function (widget) {
				this.addChild(widget);
				fireCallback(widget);
				return widget;
			}.bind(this));
		};

		this.createChildAndRender = function (widgetInfo, id, params, renderParams) {
			return this.createChild(widgetInfo, id, params).then(function(child) {
				return child.render(renderParams).then(function() {
					return child;
				});
			});
		};

		this.removeRenderChildren = function removeRender(skipEvent) {
			return this.applyOnChildren("removeRender", undefined, skipEvent);
		};

		this.renderChildren = function renderChildren(skipEvent) {
			return this.applyOnChildren("render", undefined, skipEvent);
		};

		this.reloadChildren = function reloadChildren(skipEvent) {
			return this.applyOnChildren("reload", undefined, skipEvent);
		};

		this.refreshChildren = function refreshChildren(skipEvent) {
			return this.applyOnChildren("refresh", undefined, skipEvent);
		};

		this.destroyChildren = function destroyChildren(skipEvent) {
			return this.applyOnChildren("destroy", undefined, skipEvent);
		};

		this.applyOnChildren = function applyOnChildren(actionName, action, skipEvent) {
			this.before(actionName + "Children", skipEvent);

			if(!action) {
				action = function(child) {
					return child[actionName]();
				};
			}

			var fireCallback = this.buildAfterCB(actionName + "Children", undefined, skipEvent);
			var promises = [];
			for(var key in this.children)
				promises.push(action(this.children[key]));
			return Promise.all(promises).then(fireCallback);
		};

		function addChildrenActionByID(action) {
			this[action + "ChildByID"] = function(id, args) {
				var wChild = this.children[id];
				if(!wChild)
					return Promise.resolve();

				return wChild[action].apply(wChild, args);
			};
		}

		function addChildrenActionByName(action) {
			this[action + "ChildsByName"] = function(name, args) {
				var wChilds = this.findChilds(name);
				var promises = [];
				for(var i in wChilds)
					promises.push(wChilds[i][action].apply(wChilds[i], args));
				return Promise.all(promises);
			};
		}

		addChildrenActionByID.call(this, "destroy");
		addChildrenActionByName.call(this, "destroy");

		/**********************************************************************
		******************************* Events ********************************
		**********************************************************************/
		binder.addAllFunctions(this);

		this.fire = function fireWidget(event, data) {
			event = binder.buildEvent(event, data);
			binder.fire.call(this, event);

			if(helper.isFunction(this[event.type]))
				this[event.type](event);
		};

		this.before = function before(functionName, skipEvent) {
			sendEvent.call(this, functionName, "before", skipEvent);
		};

		this.after = function after(functionName, skipEvent) {
			sendEvent.call(this, functionName, "after", skipEvent);
		};

		function sendEvent(functionName, suffix, skipEvent) {
			if(skipEvent !== true)
				this.fire(suffix + helper.capitalize(functionName));
		}

		this.buildAfterCB = function buildAfterCB(functionName, callback, skipEvent) {
			return function(result) {
				this.after(functionName, skipEvent);
				if(helper.isFunction(callback))
					callback.apply(this, arguments);
				return result;
			}.bind(this);
		};

		this.async = function(callback, timeout, params) {
			setTimeout(function() {
				callback.apply(this, params);
			}.bind(this), timeout);
		};
	};

	return properties;
});<|endoftext|>


<commit_before>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Gets configuration information that is required by the 'build' task.
 * Reads information from multiple places, including:
 *
 * chipper/build.json
 * $HOME/.phet/build-local.json
 * REPO/package.json
 * grunt.options( optionName )
 *
 * All entries are @public (read-only).
 * Entries include:
 *
 * {string} name - name of the repository being built
 * {string} version - version identifier
 * {string} license - license
 * {string} simTitleStringKey - key of the sim's title string
 * {string} requirejsNamespace - the repository's requirejs namespace in config.js, eg AREA_BUILDER
 * {string} brand - brand identifier
 * {string[]} phetLibs - other repositories that are required by the sim
 * {string[]} preload - scripts that need to be preloaded in the .html file, in the order that they will be preloaded
 * {string[]} licenseKeys - keys to licenses in sherpa/lib/license.json, for third-party dependencies
 * {string[]} locales - locales to build
 * {Object} gruntConfig
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

// The following comment permits node-specific globals (such as process.cwd()) to pass jshint
/* jslint node: true */
'use strict';

// built-in node APIs
var assert = require( 'assert' );
var fs = require( 'fs' );

// 3rd-party packages
/* jshint -W079 */
var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // allow _ to be redefined, contrary to jshintOptions.js
/* jshint +W079 */

// modules
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );

/**
 * @param {Object} grunt - the grunt instance
 */
module.exports = function( grunt ) {

  /**
   * Gets the brand identifier.
   *
   * @param {Object} grunt - the grunt instance
   * @param {Object} buildLocalJSON - build-local.json
   * @returns {string}
   */
  function getBrand( grunt, buildLocalJSON ) {
    var brand = grunt.option( 'brand' ) || buildLocalJSON.brand || 'adapted-from-phet';
    assert( fs.existsSync( '../brand/' + brand ), 'no such brand: ' + brand );
    return brand;
  }

  /**
   * Gets phetLibs, the set of repositories on which the repository being built depends.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPhetLibs( packageJSON, buildJSON, brand ) {

    // start with package.json
    var phetLibs = packageJSON.phet.phetLibs || [];

    // add the repo that's being built
    phetLibs.push( packageJSON.name );

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].phetLibs ) {
        phetLibs = phetLibs.concat( buildJSON[ id ].phetLibs );
      }
    } );

    // sort and remove duplicates
    return _.uniq( phetLibs.sort() );
  }

  /**
   * Gets preload, the set of scripts to be preloaded in the .html file.
   * NOTE! Order of the return value is significant, since it corresponds to the order in which scripts will be preloaded.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPreload( packageJSON, buildJSON, brand ) {

    var preload = [];

    // add preloads that are common to all sims, from build.json
    if ( buildJSON.common && buildJSON.common.preload ) {
      preload = preload.concat( buildJSON.common.preload );
    }

    // add sim-specific preloads from package.json
    if ( packageJSON.preload ) {
      preload = preload.concat( packageJSON.preload );
    }

    // add brand-specific preloads from build.json
    if ( buildJSON[ buildConfig.brand ] && buildJSON[ buildConfig.brand ].preload ) {
      preload = preload.concat( buildJSON[ buildConfig.brand ].preload );
    }

    // add the together API file
    if ( brand === 'phet-io' ) {
      var TOGETHER_API_FILENAME = '../together/js/api/' + packageJSON.name + '-api.js';
      assert( fs.existsSync( TOGETHER_API_FILENAME ), 'together API file does not exist: ' + TOGETHER_API_FILENAME );
      preload.push( TOGETHER_API_FILENAME );
    }

    // sort and remove duplicates
    return _.uniq( preload.sort() );
  }

  /**
   * Gets the license keys for sherpa (third-party) libs that are used.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @param {string[]} preload
   * @returns {string[]}
   */
  function getLicenseKeys( packageJSON, buildJSON, brand, preload ) {

    // start with package.json
    var licenseKeys = packageJSON.phet.licenseKeys || [];

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].licenseKeys ) {
        licenseKeys = licenseKeys.concat( buildJSON[ id ].licenseKeys );
      }
    } );

    // Extract keys from preload for sherpa (third-party) dependencies
    preload.forEach( function( path ) {
      if ( path.indexOf( '/sherpa/' ) !== -1 ) {
        var lastSlash = path.lastIndexOf( '/' );
        var key = path.substring( lastSlash + 1 );
        licenseKeys.push( key );
      }
    } );

    // sort and remove duplicates
    return _.uniq( _.sortBy( licenseKeys, function( key ) { return key.toUpperCase(); } ) );
  }

  /*
   * Gets the locales from a repository, by inspecting the names of the string files in that repository.
   *
   * @param {string} repository - name of the repository to get locales from
   */
  function getLocalesFromRepository( repository ) {

    // confirm that the repository has a strings directory
    var stringsDirectory = '../babel/' + repository;
    var stats = fs.statSync( stringsDirectory );
    assert( stats.isDirectory(), stringsDirectory + 'is not a directory' );

    // Get names of string files.
    var stringFiles = fs.readdirSync( stringsDirectory ).filter( function( filename ) {
      return (/^.*-strings.*\.json/).test( filename );
    } );
    assert( stringFiles.length > 0, 'no string files found in ' + stringsDirectory );

    // Extract the locales from the file names.
    // File names must have a form like 'graphing-lines-strings_ar_SA.json', where no '_' appear in the repo name.
    var locales = stringFiles.map( function( filename ) {
      return filename.substring( filename.indexOf( '_' ) + 1, filename.lastIndexOf( '.' ) );
    } );
    assert( locales.length > 0, 'no locales found in ' + stringsDirectory );

    return locales;
  }

  /**
   * Gets the set of locales to be built.
   *
   * The grunt options are:
   *
   * --locales=* : all locales from the repo's strings/ directory
   * --locales=fr : French
   * --locales=ar,fr,es : Arabic, French and Spanish (comma-separated locales)
   * --localesRepo=beers-law-lab: all locales from another repository's strings/ directory, ignored if --locales is specified
   *
   * @param {Object} grunt - the grunt instance
   * @param {string} repository - name of the repository that is being built
   */
  function getLocales( grunt, repository ) {

    var locales = grunt.option( 'locales' );

    if ( locales ) {
      if ( locales === '*' ) {
        locales = getLocalesFromRepository( repository ); // all locales for the repository that we're building
      }
      else {
        locales = _.uniq( locales.split( ',' ) );
      }
    }
    else {
      var localesRepo = grunt.option( 'localesRepo' );
      if ( localesRepo ) {
        locales = getLocalesFromRepository( localesRepo ); // all locales for some other repository
      }
      else {
        locales = [ ChipperConstants.FALLBACK_LOCALE ];
      }
    }
    return locales;
  }

  /**
   * Gets the JSHint configuration object.
   *
   * @param {Object} packageJSON - package.json
   * @param {string[]} phetLibs
   * @returns {Object}
   */
  function getJSHintConfig( packageJSON, phetLibs ) {

    // Repository files to be linted. brand has a non-standard directory structure.
    var repoFilesToLint = ( packageJSON.name === 'brand' ) ? [ '*/js/**/*.js' ] : [ 'js/**/*.js' ];

    // All files to be linted
    var allFilesToLint = _.map( phetLibs, function( repo ) {
      return '../' + repo + '/js/**/*.js';
    } );

    // brand repo has a non-standard directory structure, so add it explicitly if it's a dependency.
    if ( packageJSON.name !== 'brand' ) {
      allFilesToLint.push( '../brand/*/js/**/*.js' );
    }

    // Exclude svgPath.js, it was automatically generated and doesn't match pass lint.
    allFilesToLint.push( '!../kite/js/parser/svgPath.js' );
    allFilesToLint = _.uniq( allFilesToLint );

    return {

      // PhET-specific, passed to the 'lint' grunt task
      // Source files that are specific to this repository
      repoFiles: repoFilesToLint,

      // PhET-specific, passed to the 'lint-all' grunt task
      // All source files for this repository (repository-specific and dependencies).
      allFiles: allFilesToLint,

      // Reference external options in jshintOptions.js
      options: require( './jshintOptions' )
    };
  }

  /**
   * Gets the Grunt configuration object.
   *
   * @param {Object} packageJSON - package.json
   * @param {strings[]} phetLibs
   */
  function getGruntConfig( packageJSON, phetLibs ) {
    return {

      jshint: getJSHintConfig( packageJSON, phetLibs ),

      // configure the RequireJS plugin, see https://github.com/jrburke/r.js/blob/master/build/example.build.js
      requirejs: {

        // builds the minified script
        build: {

          options: {

            almond: true,

            mainConfigFile: 'js/' + packageJSON.name + '-config.js',
            out: 'build/' + packageJSON.name + '.min.js',
            name: packageJSON.name + '-config',

            // Minification strategy.  Put this to none if you want to debug a non-minified but compiled version
            optimize: 'uglify2',
            wrap: true,
            preserveLicenseComments: false,
            uglify2: {
              output: {
                inline_script: true // escape </script
              },
              compress: {
                global_defs: {
                  // global assertions
                  assert: false,
                  assertSlow: false,
                  // scenery logging
                  sceneryLog: false,
                  sceneryLayerLog: false,
                  sceneryEventLog: false,
                  sceneryAccessibilityLog: false,
                  phetAllocation: false
                },
                dead_code: true
              }
            },

            //TODO chipper#275 should 'mipmap' be included here too?
            // stub out the plugins, so their source code won't be included in the minified file
            stubModules: [ 'string', 'audio', 'image' ]
          }
        }
      }
    };
  }

  //------------------------------------------------------------------------------------
  // read configuration files

  // ./package.json (required)
  var PACKAGE_FILENAME = 'package.json';
  assert( fs.existsSync( PACKAGE_FILENAME ), 'missing ' + PACKAGE_FILENAME );
  var packageJSON = grunt.file.readJSON( PACKAGE_FILENAME );
  assert( packageJSON.name, 'name missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.version, 'version missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.license, 'license missing from ' + PACKAGE_FILENAME );

  // chipper/build.json (required)
  var BUILD_FILENAME = '../chipper/build.json';
  assert( fs.existsSync( BUILD_FILENAME ), 'missing ' + BUILD_FILENAME );
  var buildJSON = grunt.file.readJSON( BUILD_FILENAME );

  // $HOME/.phet/build-local.json (optional)
  var BUILD_LOCAL_FILENAME = process.env.HOME + '/.phet/build-local.json';
  var buildLocalJSON = {};
  if ( fs.existsSync( BUILD_LOCAL_FILENAME ) ) {
    buildLocalJSON = grunt.file.readJSON( BUILD_LOCAL_FILENAME );
  }

  //------------------------------------------------------------------------------------
  // Assemble the buildConfig

  var buildConfig = {
    // These entries have no dependencies on other entries in buildConfig.
    name: packageJSON.name,
    version: packageJSON.version,
    license: packageJSON.license,
    simTitleStringKey: packageJSON.phet.simTitleStringKey,
    requirejsNamespace: packageJSON.phet.requirejsNamespace,
    brand: getBrand( grunt, buildLocalJSON )
  };

  // These entries depend on other entries in buildConfig.
  buildConfig.phetLibs = getPhetLibs( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.preload = getPreload( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.licenseKeys = getLicenseKeys( packageJSON, buildJSON, buildConfig.brand, buildConfig.preload );
  buildConfig.locales = getLocales( grunt, buildConfig.name );
  buildConfig.gruntConfig = getGruntConfig( packageJSON, buildConfig.phetLibs );

  grunt.log.debug( 'buildConfig=' + JSON.stringify( buildConfig, null, 2 ) );
  return buildConfig;
};
<commit_msg>#268 don't sort preloads, order is significant<commit_after>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Gets configuration information that is required by the 'build' task.
 * Reads information from multiple places, including:
 *
 * chipper/build.json
 * $HOME/.phet/build-local.json
 * REPO/package.json
 * grunt.options( optionName )
 *
 * All entries are @public (read-only).
 * Entries include:
 *
 * {string} name - name of the repository being built
 * {string} version - version identifier
 * {string} license - license
 * {string} simTitleStringKey - key of the sim's title string
 * {string} requirejsNamespace - the repository's requirejs namespace in config.js, eg AREA_BUILDER
 * {string} brand - brand identifier
 * {string[]} phetLibs - other repositories that are required by the sim
 * {string[]} preload - scripts that need to be preloaded in the .html file, in the order that they will be preloaded
 * {string[]} licenseKeys - keys to licenses in sherpa/lib/license.json, for third-party dependencies
 * {string[]} locales - locales to build
 * {Object} gruntConfig
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

// The following comment permits node-specific globals (such as process.cwd()) to pass jshint
/* jslint node: true */
'use strict';

// built-in node APIs
var assert = require( 'assert' );
var fs = require( 'fs' );

// 3rd-party packages
/* jshint -W079 */
var _ = require( '../../../sherpa/lib/lodash-2.4.1.min' ); // allow _ to be redefined, contrary to jshintOptions.js
/* jshint +W079 */

// modules
var ChipperConstants = require( '../../../chipper/js/common/ChipperConstants' );

/**
 * @param {Object} grunt - the grunt instance
 */
module.exports = function( grunt ) {

  /**
   * Gets the brand identifier.
   *
   * @param {Object} grunt - the grunt instance
   * @param {Object} buildLocalJSON - build-local.json
   * @returns {string}
   */
  function getBrand( grunt, buildLocalJSON ) {
    var brand = grunt.option( 'brand' ) || buildLocalJSON.brand || 'adapted-from-phet';
    assert( fs.existsSync( '../brand/' + brand ), 'no such brand: ' + brand );
    return brand;
  }

  /**
   * Gets phetLibs, the set of repositories on which the repository being built depends.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPhetLibs( packageJSON, buildJSON, brand ) {

    // start with package.json
    var phetLibs = packageJSON.phet.phetLibs || [];

    // add the repo that's being built
    phetLibs.push( packageJSON.name );

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].phetLibs ) {
        phetLibs = phetLibs.concat( buildJSON[ id ].phetLibs );
      }
    } );

    // sort and remove duplicates
    return _.uniq( phetLibs.sort() );
  }

  /**
   * Gets preload, the set of scripts to be preloaded in the .html file.
   * NOTE! Order of the return value is significant, since it corresponds to the order in which scripts will be preloaded.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @returns {string[]}
   */
  function getPreload( packageJSON, buildJSON, brand ) {

    var preload = [];

    // add preloads that are common to all sims, from build.json
    if ( buildJSON.common && buildJSON.common.preload ) {
      preload = preload.concat( buildJSON.common.preload );
    }

    // add sim-specific preloads from package.json
    if ( packageJSON.preload ) {
      preload = preload.concat( packageJSON.preload );
    }

    // add brand-specific preloads from build.json
    if ( buildJSON[ buildConfig.brand ] && buildJSON[ buildConfig.brand ].preload ) {
      preload = preload.concat( buildJSON[ buildConfig.brand ].preload );
    }

    // add the together API file
    if ( brand === 'phet-io' ) {
      var TOGETHER_API_FILENAME = '../together/js/api/' + packageJSON.name + '-api.js';
      assert( fs.existsSync( TOGETHER_API_FILENAME ), 'together API file does not exist: ' + TOGETHER_API_FILENAME );
      preload.push( TOGETHER_API_FILENAME );
    }

    // remove duplicates (do NOT sort, order is significant!)
    return _.uniq( preload );
  }

  /**
   * Gets the license keys for sherpa (third-party) libs that are used.
   *
   * @param {Object} packageJSON - package.json
   * @param {Object} buildJSON - build.json
   * @param {string} brand
   * @param {string[]} preload
   * @returns {string[]}
   */
  function getLicenseKeys( packageJSON, buildJSON, brand, preload ) {

    // start with package.json
    var licenseKeys = packageJSON.phet.licenseKeys || [];

    // add common and brand-specific entries from build.json
    [ 'common', brand ].forEach( function( id ) {
      if ( buildJSON[ id ] && buildJSON[ id ].licenseKeys ) {
        licenseKeys = licenseKeys.concat( buildJSON[ id ].licenseKeys );
      }
    } );

    // Extract keys from preload for sherpa (third-party) dependencies
    preload.forEach( function( path ) {
      if ( path.indexOf( '/sherpa/' ) !== -1 ) {
        var lastSlash = path.lastIndexOf( '/' );
        var key = path.substring( lastSlash + 1 );
        licenseKeys.push( key );
      }
    } );

    // sort and remove duplicates
    return _.uniq( _.sortBy( licenseKeys, function( key ) { return key.toUpperCase(); } ) );
  }

  /*
   * Gets the locales from a repository, by inspecting the names of the string files in that repository.
   *
   * @param {string} repository - name of the repository to get locales from
   */
  function getLocalesFromRepository( repository ) {

    // confirm that the repository has a strings directory
    var stringsDirectory = '../babel/' + repository;
    var stats = fs.statSync( stringsDirectory );
    assert( stats.isDirectory(), stringsDirectory + 'is not a directory' );

    // Get names of string files.
    var stringFiles = fs.readdirSync( stringsDirectory ).filter( function( filename ) {
      return (/^.*-strings.*\.json/).test( filename );
    } );
    assert( stringFiles.length > 0, 'no string files found in ' + stringsDirectory );

    // Extract the locales from the file names.
    // File names must have a form like 'graphing-lines-strings_ar_SA.json', where no '_' appear in the repo name.
    var locales = stringFiles.map( function( filename ) {
      return filename.substring( filename.indexOf( '_' ) + 1, filename.lastIndexOf( '.' ) );
    } );
    assert( locales.length > 0, 'no locales found in ' + stringsDirectory );

    return locales;
  }

  /**
   * Gets the set of locales to be built.
   *
   * The grunt options are:
   *
   * --locales=* : all locales from the repo's strings/ directory
   * --locales=fr : French
   * --locales=ar,fr,es : Arabic, French and Spanish (comma-separated locales)
   * --localesRepo=beers-law-lab: all locales from another repository's strings/ directory, ignored if --locales is specified
   *
   * @param {Object} grunt - the grunt instance
   * @param {string} repository - name of the repository that is being built
   */
  function getLocales( grunt, repository ) {

    var locales = grunt.option( 'locales' );

    if ( locales ) {
      if ( locales === '*' ) {
        locales = getLocalesFromRepository( repository ); // all locales for the repository that we're building
      }
      else {
        locales = _.uniq( locales.split( ',' ) );
      }
    }
    else {
      var localesRepo = grunt.option( 'localesRepo' );
      if ( localesRepo ) {
        locales = getLocalesFromRepository( localesRepo ); // all locales for some other repository
      }
      else {
        locales = [ ChipperConstants.FALLBACK_LOCALE ];
      }
    }
    return locales;
  }

  /**
   * Gets the JSHint configuration object.
   *
   * @param {Object} packageJSON - package.json
   * @param {string[]} phetLibs
   * @returns {Object}
   */
  function getJSHintConfig( packageJSON, phetLibs ) {

    // Repository files to be linted. brand has a non-standard directory structure.
    var repoFilesToLint = ( packageJSON.name === 'brand' ) ? [ '*/js/**/*.js' ] : [ 'js/**/*.js' ];

    // All files to be linted
    var allFilesToLint = _.map( phetLibs, function( repo ) {
      return '../' + repo + '/js/**/*.js';
    } );

    // brand repo has a non-standard directory structure, so add it explicitly if it's a dependency.
    if ( packageJSON.name !== 'brand' ) {
      allFilesToLint.push( '../brand/*/js/**/*.js' );
    }

    // Exclude svgPath.js, it was automatically generated and doesn't match pass lint.
    allFilesToLint.push( '!../kite/js/parser/svgPath.js' );
    allFilesToLint = _.uniq( allFilesToLint );

    return {

      // PhET-specific, passed to the 'lint' grunt task
      // Source files that are specific to this repository
      repoFiles: repoFilesToLint,

      // PhET-specific, passed to the 'lint-all' grunt task
      // All source files for this repository (repository-specific and dependencies).
      allFiles: allFilesToLint,

      // Reference external options in jshintOptions.js
      options: require( './jshintOptions' )
    };
  }

  /**
   * Gets the Grunt configuration object.
   *
   * @param {Object} packageJSON - package.json
   * @param {strings[]} phetLibs
   */
  function getGruntConfig( packageJSON, phetLibs ) {
    return {

      jshint: getJSHintConfig( packageJSON, phetLibs ),

      // configure the RequireJS plugin, see https://github.com/jrburke/r.js/blob/master/build/example.build.js
      requirejs: {

        // builds the minified script
        build: {

          options: {

            almond: true,

            mainConfigFile: 'js/' + packageJSON.name + '-config.js',
            out: 'build/' + packageJSON.name + '.min.js',
            name: packageJSON.name + '-config',

            // Minification strategy.  Put this to none if you want to debug a non-minified but compiled version
            optimize: 'uglify2',
            wrap: true,
            preserveLicenseComments: false,
            uglify2: {
              output: {
                inline_script: true // escape </script
              },
              compress: {
                global_defs: {
                  // global assertions
                  assert: false,
                  assertSlow: false,
                  // scenery logging
                  sceneryLog: false,
                  sceneryLayerLog: false,
                  sceneryEventLog: false,
                  sceneryAccessibilityLog: false,
                  phetAllocation: false
                },
                dead_code: true
              }
            },

            //TODO chipper#275 should 'mipmap' be included here too?
            // stub out the plugins, so their source code won't be included in the minified file
            stubModules: [ 'string', 'audio', 'image' ]
          }
        }
      }
    };
  }

  //------------------------------------------------------------------------------------
  // read configuration files

  // ./package.json (required)
  var PACKAGE_FILENAME = 'package.json';
  assert( fs.existsSync( PACKAGE_FILENAME ), 'missing ' + PACKAGE_FILENAME );
  var packageJSON = grunt.file.readJSON( PACKAGE_FILENAME );
  assert( packageJSON.name, 'name missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.version, 'version missing from ' + PACKAGE_FILENAME );
  assert( packageJSON.license, 'license missing from ' + PACKAGE_FILENAME );

  // chipper/build.json (required)
  var BUILD_FILENAME = '../chipper/build.json';
  assert( fs.existsSync( BUILD_FILENAME ), 'missing ' + BUILD_FILENAME );
  var buildJSON = grunt.file.readJSON( BUILD_FILENAME );

  // $HOME/.phet/build-local.json (optional)
  var BUILD_LOCAL_FILENAME = process.env.HOME + '/.phet/build-local.json';
  var buildLocalJSON = {};
  if ( fs.existsSync( BUILD_LOCAL_FILENAME ) ) {
    buildLocalJSON = grunt.file.readJSON( BUILD_LOCAL_FILENAME );
  }

  //------------------------------------------------------------------------------------
  // Assemble the buildConfig

  var buildConfig = {
    // These entries have no dependencies on other entries in buildConfig.
    name: packageJSON.name,
    version: packageJSON.version,
    license: packageJSON.license,
    simTitleStringKey: packageJSON.phet.simTitleStringKey,
    requirejsNamespace: packageJSON.phet.requirejsNamespace,
    brand: getBrand( grunt, buildLocalJSON )
  };

  // These entries depend on other entries in buildConfig.
  buildConfig.phetLibs = getPhetLibs( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.preload = getPreload( packageJSON, buildJSON, buildConfig.brand );
  buildConfig.licenseKeys = getLicenseKeys( packageJSON, buildJSON, buildConfig.brand, buildConfig.preload );
  buildConfig.locales = getLocales( grunt, buildConfig.name );
  buildConfig.gruntConfig = getGruntConfig( packageJSON, buildConfig.phetLibs );

  grunt.log.debug( 'buildConfig=' + JSON.stringify( buildConfig, null, 2 ) );
  return buildConfig;
};
<|endoftext|>


<commit_before>// @flow

import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import {
  Button,
  Form,
  FormControl,
  FormGroup,
  ControlLabel
} from 'react-bootstrap'
import Select from 'react-select'

import {updateActiveGtfsEntity} from '../actions/active'
import toSentenceCase from '../../common/util/to-sentence-case'
import ExceptionDate from './ExceptionDate'
import {EXCEPTION_EXEMPLARS} from '../util'
import {getTableById} from '../util/gtfs'

import type {ServiceCalendar, ScheduleException} from '../../types'
import type {EditorTables} from '../../types/reducers'
import type {EditorValidationIssue} from '../util/validation'

type Props = {
  activeComponent: string,
  activeEntity: ScheduleException,
  tableData: EditorTables,
  updateActiveGtfsEntity: typeof updateActiveGtfsEntity,
  validationErrors: Array<EditorValidationIssue>
}

type SelectOption = {
  value: ServiceCalendar
}

export default class ScheduleExceptionForm extends Component<Props> {
  _onAddDate = () => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    const dates = [...activeEntity.dates]
    dates.push(0)
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {dates}
    })
  }

  _filterByList = (calendars: Array<ServiceCalendar>, list: ?Array<string>) => calendars
    .filter(cal => !list || list.indexOf(cal.service_id) === -1)

  _mapInput = (input: ?Array<SelectOption>): ?Array<ServiceCalendar> =>
    input && input.length ? input.map(i => i.value) : null

  _onCalendarChange = (input: ?Array<SelectOption>, id: string) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    const value = this._mapInput(input)
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {[id]: value}
    })
  }

  _onExemplarChange = (evt: SyntheticInputEvent<HTMLInputElement>) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {
        exemplar: +evt.target.value,
        custom_schedule: null,
        removed_service: null,
        added_service: null
      }
    })
  }

  _onNameChange = (evt: SyntheticInputEvent<HTMLInputElement>) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {name: evt.target.value}
    })
  }

  _checkValidation = (id: string) => this.props.validationErrors
    .find(e => e.field === id)
    ? 'error'
    : undefined

  render () {
    const {activeEntity, tableData} = this.props
    if (!activeEntity) return null
    const calendars = (getTableById(tableData, 'calendar'): Array<ServiceCalendar>)
    const {
      exemplar,
      name,
      custom_schedule: customSchedule,
      added_service: addedService,
      removed_service: removedService,
      dates
    } = activeEntity
    const hasExemplar = typeof exemplar === 'number'
    return (

        <Form>
          <FormGroup
            className={`col-xs-12`}
            controlId={`name`}
            data-test-id='exception-name-input-container'
            validationState={this._checkValidation('name')}
          >
            <ControlLabel>
              Exception name*
            </ControlLabel>
            <FormControl
              value={name}
              placeholder='Thanksgiving Day'
              onChange={this._onNameChange} />
          </FormGroup>
          <FormGroup
            className={`col-xs-12`}
            controlId={`schedule`}
            data-test-id='exception-type-input-container'
            validationState={this._checkValidation('exemplar')}
          >
            <ControlLabel>
              Run the following schedule:
            </ControlLabel>
            <FormControl
              componentClass='select'
              value={hasExemplar ? exemplar : ''}
              onChange={this._onExemplarChange}>
              <option value='' disabled>
                -- Select exception type --
              </option>
              {Object.keys(EXCEPTION_EXEMPLARS)
                .map(exemplar => {
                  const value = EXCEPTION_EXEMPLARS[exemplar]
                  return (
                    <option
                      value={value}
                      key={value}>
                      {value === EXCEPTION_EXEMPLARS.SWAP
                        ? 'Swap, add, or remove'
                        : toSentenceCase(exemplar)}
                    </option>
                  )
                })
              }
            </FormControl>
          </FormGroup>
          {exemplar === EXCEPTION_EXEMPLARS.CUSTOM
            ? <ExceptionCalendarSelector
              label={'Select calendar to run*'}
              id={`custom_schedule`}
              value={customSchedule}
              onChange={this._onCalendarChange}
              calendars={calendars} />
            : null
          }
          {exemplar === EXCEPTION_EXEMPLARS.SWAP
            ? (

                <ExceptionCalendarSelector
                  label={'Select calendars to add (optional):'}
                  id={`added_service`}
                  value={addedService}
                  onChange={this._onCalendarChange}
                  calendars={this._filterByList(calendars, removedService)} />
                <ExceptionCalendarSelector
                  label={'Select calendars to remove (optional):'}
                  id={`removed_service`}
                  value={removedService}
                  onChange={this._onCalendarChange}
                  calendars={this._filterByList(calendars, addedService)} />

            )
            : null
          }
          <FormGroup
            className={`col-xs-12`}
            controlId={`exception-dates`}
            data-test-id='exception-dates-container'
            validationState={this._checkValidation('dates')}
          >
            <ControlLabel>
              On these dates*
            </ControlLabel>
            {dates && dates.length
              ? dates.map((date, index) => (
                <ExceptionDate
                  index={index}
                  key={index}
                  date={date}
                  // Exception dates expects a boolean
                  isNotValid={Boolean(this._checkValidation(`dates-${index}`))}
                  {...this.props}
                />
              ))
              :
No dates specified

            }
          </FormGroup>
          <div className={`col-xs-12`}>
            <Button
              data-test-id='exception-add-date-button'
              onClick={this._onAddDate}
            >
              <Icon type='plus' /> Add date
            </Button>

        </Form>

    )
  }
}

type SelectorProps = {
  calendars: Array<ServiceCalendar>,
  id: string,
  label: string,
  onChange: (any, string) => void,
  value: ?Array<any>
}

class ExceptionCalendarSelector extends Component<SelectorProps> {
  _onChange = (input: ?Array<SelectOption>) => {
    const {id, onChange} = this.props
    onChange && onChange(input, id)
  }

  _calendarToOption = calendar => ({
    value: calendar.service_id,
    label: this._getCalendarName(calendar),
    calendar
  })

  _getCalendarName = calendar => {
    let name = '[unnamed]'
    if (calendar) {
      name = calendar.description ? calendar.description : ''
      name += calendar.id ? ` (${calendar.id})` : ''
    }
    return name
  }

  render () {
    const {id, label, calendars, value} = this.props
    return (
      <FormGroup controlId={id} className={`col-xs-12`}>
        <ControlLabel>{label}</ControlLabel>
        <Select
          placeholder='Select calendar...'
          clearable
          multi
          value={value}
          onChange={this._onChange}
          options={calendars.map(this._calendarToOption)} />
      </FormGroup>
    )
  }
}
<commit_msg>fix(ScheduleExceptionForm): Remove underscore in No Service exception option<commit_after>// @flow

import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import {
  Button,
  Form,
  FormControl,
  FormGroup,
  ControlLabel
} from 'react-bootstrap'
import Select from 'react-select'

import {updateActiveGtfsEntity} from '../actions/active'
import toSentenceCase from '../../common/util/to-sentence-case'
import {EXCEPTION_EXEMPLARS} from '../util'
import {getTableById} from '../util/gtfs'
import type {ServiceCalendar, ScheduleException} from '../../types'
import type {EditorTables} from '../../types/reducers'
import type {EditorValidationIssue} from '../util/validation'

import ExceptionDate from './ExceptionDate'

type Props = {
  activeComponent: string,
  activeEntity: ScheduleException,
  tableData: EditorTables,
  updateActiveGtfsEntity: typeof updateActiveGtfsEntity,
  validationErrors: Array<EditorValidationIssue>
}

type SelectOption = {
  value: ServiceCalendar
}

export default class ScheduleExceptionForm extends Component<Props> {
  _onAddDate = () => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    const dates = [...activeEntity.dates]
    dates.push(0)
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {dates}
    })
  }

  _filterByList = (calendars: Array<ServiceCalendar>, list: ?Array<string>) => calendars
    .filter(cal => !list || list.indexOf(cal.service_id) === -1)

  _mapInput = (input: ?Array<SelectOption>): ?Array<ServiceCalendar> =>
    input && input.length ? input.map(i => i.value) : null

  _onCalendarChange = (input: ?Array<SelectOption>, id: string) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    const value = this._mapInput(input)
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {[id]: value}
    })
  }

  _onExemplarChange = (evt: SyntheticInputEvent<HTMLInputElement>) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {
        exemplar: +evt.target.value,
        custom_schedule: null,
        removed_service: null,
        added_service: null
      }
    })
  }

  _onNameChange = (evt: SyntheticInputEvent<HTMLInputElement>) => {
    const {activeComponent, activeEntity, updateActiveGtfsEntity} = this.props
    updateActiveGtfsEntity({
      component: activeComponent,
      entity: activeEntity,
      props: {name: evt.target.value}
    })
  }

  _renderExceptionExemplars (exemplar: string, value: number): string {
    let exemplarText = ''
    switch (value) {
      case EXCEPTION_EXEMPLARS.SWAP:
        exemplarText = 'Swap, add, or remove'
        break
      case EXCEPTION_EXEMPLARS.NO_SERVICE:
        const a = exemplar.replace('_', ' ')
        exemplarText = toSentenceCase(a)
        break
      default:
        exemplarText = toSentenceCase(exemplar)
    }
    return exemplarText
  }

  _checkValidation = (id: string) => this.props.validationErrors
    .find(e => e.field === id)
    ? 'error'
    : undefined

  render () {
    const {activeEntity, tableData} = this.props
    if (!activeEntity) return null
    const calendars = (getTableById(tableData, 'calendar'): Array<ServiceCalendar>)
    const {
      exemplar,
      name,
      custom_schedule: customSchedule,
      added_service: addedService,
      removed_service: removedService,
      dates
    } = activeEntity
    const hasExemplar = typeof exemplar === 'number'
    return (

        <Form>
          <FormGroup
            className={`col-xs-12`}
            controlId={`name`}
            data-test-id='exception-name-input-container'
            validationState={this._checkValidation('name')}
          >
            <ControlLabel>
              Exception name*
            </ControlLabel>
            <FormControl
              value={name}
              placeholder='Thanksgiving Day'
              onChange={this._onNameChange} />
          </FormGroup>
          <FormGroup
            className={`col-xs-12`}
            controlId={`schedule`}
            data-test-id='exception-type-input-container'
            validationState={this._checkValidation('exemplar')}
          >
            <ControlLabel>
              Run the following schedule:
            </ControlLabel>
            <FormControl
              componentClass='select'
              value={hasExemplar ? exemplar : ''}
              onChange={this._onExemplarChange}>
              <option value='' disabled>
                -- Select exception type --
              </option>
              {Object.keys(EXCEPTION_EXEMPLARS)
                .map(exemplar => {
                  const value = EXCEPTION_EXEMPLARS[exemplar]
                  return (
                    <option
                      value={value}
                      key={value}>
                      {this._renderExceptionExemplars(exemplar, value)}
                    </option>
                  )
                })
              }
            </FormControl>
          </FormGroup>
          {exemplar === EXCEPTION_EXEMPLARS.CUSTOM
            ? <ExceptionCalendarSelector
              label={'Select calendar to run*'}
              id={`custom_schedule`}
              value={customSchedule}
              onChange={this._onCalendarChange}
              calendars={calendars} />
            : null
          }
          {exemplar === EXCEPTION_EXEMPLARS.SWAP
            ? (

                <ExceptionCalendarSelector
                  label={'Select calendars to add (optional):'}
                  id={`added_service`}
                  value={addedService}
                  onChange={this._onCalendarChange}
                  calendars={this._filterByList(calendars, removedService)} />
                <ExceptionCalendarSelector
                  label={'Select calendars to remove (optional):'}
                  id={`removed_service`}
                  value={removedService}
                  onChange={this._onCalendarChange}
                  calendars={this._filterByList(calendars, addedService)} />

            )
            : null
          }
          <FormGroup
            className={`col-xs-12`}
            controlId={`exception-dates`}
            data-test-id='exception-dates-container'
            validationState={this._checkValidation('dates')}
          >
            <ControlLabel>
              On these dates*
            </ControlLabel>
            {dates && dates.length
              ? dates.map((date, index) => (
                <ExceptionDate
                  index={index}
                  key={index}
                  date={date}
                  // Exception dates expects a boolean
                  isNotValid={Boolean(this._checkValidation(`dates-${index}`))}
                  {...this.props}
                />
              ))
              :
No dates specified

            }
          </FormGroup>
          <div className={`col-xs-12`}>
            <Button
              data-test-id='exception-add-date-button'
              onClick={this._onAddDate}
            >
              <Icon type='plus' /> Add date
            </Button>

        </Form>

    )
  }
}

type SelectorProps = {
  calendars: Array<ServiceCalendar>,
  id: string,
  label: string,
  onChange: (any, string) => void,
  value: ?Array<any>
}

class ExceptionCalendarSelector extends Component<SelectorProps> {
  _onChange = (input: ?Array<SelectOption>) => {
    const {id, onChange} = this.props
    onChange && onChange(input, id)
  }

  _calendarToOption = calendar => ({
    value: calendar.service_id,
    label: this._getCalendarName(calendar),
    calendar
  })

  _getCalendarName = calendar => {
    let name = '[unnamed]'
    if (calendar) {
      name = calendar.description ? calendar.description : ''
      name += calendar.id ? ` (${calendar.id})` : ''
    }
    return name
  }

  render () {
    const {id, label, calendars, value} = this.props
    return (
      <FormGroup controlId={id} className={`col-xs-12`}>
        <ControlLabel>{label}</ControlLabel>
        <Select
          placeholder='Select calendar...'
          clearable
          multi
          value={value}
          onChange={this._onChange}
          options={calendars.map(this._calendarToOption)} />
      </FormGroup>
    )
  }
}
<|endoftext|>