<commit_before>import WriterService from './WriterService';
/**
 * Provides an ExportService that uses WriterService to mentain an in-memory representation of the CSV file.
 * Uses downloadCSV to map the [data] via the Writer to a Blob and initiate download which triggers the download of the actual file in the browser.
 * Provides static methods for Singleton-like usage.
 */
export default class CSVExportService {
  /**
   * Default constructor. Takes an optional options param.
   * @param  {Object} options - Provides the configuration options.
   */
  constructor(options) {
    this.options = options || {};
  }

  /**
   * Shorthand for createCSV() with a pre-set writerType
  */
  createCSVBlob(data) {
    return this.createCSV(data);
  }

  /**
    * Shorthand for createCSV() with a pre-set writerType
  */
  dataToString(data) {
    return this.createCSV(data, 'string');
  }

  /**
   * Creates a Blob based on the provided data and configuration options.
   * @param  {Array} data - An array of JSON objects that will be mapped to the Blob.
   * @param  {String} writerType - ENUM for choosing the return type. Default to 'blo'
   * @return {Object|String} - The Blob object (Is an instance of Blob, but typeof Object) or a String version of the CSV with newlines.
   */
  createCSV(data, writerType = 'blob') {
    const {
      columns: optionsColumns,
      contentType: optionsContentType,
      delimeter: optionsDelimeter, // delimeter is a common miss-spelling of “delimiter”
      delimiter: optionsDelimiter,
      formatters: optionsFormatters,
      headers: optionsHeaders,
      includeHeaders: optionsIncludeHeaders,
    } = this.options || {};
    const contentType = optionsContentType || 'text/csv';
    const delimiter = ptionsDelimiter || optionsDelimeter || ',';
    const formatters = optionsFormatters || {};
    const getFormater = header => formatters[header] || (v => v);
    const headerNames = optionsHeaders || {};
    const headers = optionsColumns || Object.getOwnPropertyNames(data[0]);
    const includeHeaders = optionsIncludeHeaders;
    const writer = new WriterService(delimiter, contentType);

    if (includeHeaders === undefined || includeHeaders) {
      headers.forEach(header => writer.writeValue(headerNames[header] || header));
      writer.writeLine();
    }
    data.forEach(row => {
      headers.forEach(header => writer.writeValue(getFormater(header)(row[header])));
      writer.writeLine();
    });
    return writerType === 'string' ? writer.toString() : writer.toBlob();
  }
  /**
   * Triggers the download of the file.
   * @param  {Object} blob - The blob to be downloaded.
   * @param  {String} filename - Name of the file.
   */
  download(blob, filename) {
    if (navigator.msSaveBlob) {
      navigator.msSaveBlob(blob, filename);
      return;
    }
    const link = document.createElement('A');
    const url = URL.createObjectURL(blob);
    link.href = url;
    link.download = filename;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
  /**
   * Creates and passes the blob and filename params to the download() method. In case of error, calls the onError callback (if provided) and prints a error mesage if devMode is enabled via options.
   * @param  {Array} data - An array of objects to map and store.
   */
  downloadCSV(data) {
    try {
      const blob = this.createCSV(data, 'blob');
      const filename = this.options.filename || `export-${new Date().getTime() / 1000 | 0}.csv`;
      this.download(blob, filename);
    } catch (err) {
      if (this.options.devMode) {
        console.error('Error downloading CSV. Send this log to the developers: \n', err);
      }
      if (this.options.onError) {
        this.options.onError(err);
      }
    }
  }

  toString(data) {
    const writer = new WriterService(delimiter, contentType);
  }

  /**
   * Shorthand for constructor.
   * @param  {Object} options
   * @return {Object} - An instance of CSVExportService
   */
  static create(options) {
    return new CSVExportService(options);
  }
  /**
   * Shorthand for initialization and download() call.
   * @param  {Array} data - An array of objects to map and store.
   * @return {undefined}
   */
  static download(data, options = {}) {
    return new CSVExportService(options).downloadCSV(data);
  }
}
<commit_msg>fix typo<commit_after>import WriterService from './WriterService';
/**
 * Provides an ExportService that uses WriterService to mentain an in-memory representation of the CSV file.
 * Uses downloadCSV to map the [data] via the Writer to a Blob and initiate download which triggers the download of the actual file in the browser.
 * Provides static methods for Singleton-like usage.
 */
export default class CSVExportService {
  /**
   * Default constructor. Takes an optional options param.
   * @param  {Object} options - Provides the configuration options.
   */
  constructor(options) {
    this.options = options || {};
  }

  /**
   * Shorthand for createCSV() with a pre-set writerType
  */
  createCSVBlob(data) {
    return this.createCSV(data);
  }

  /**
    * Shorthand for createCSV() with a pre-set writerType
  */
  dataToString(data) {
    return this.createCSV(data, 'string');
  }

  /**
   * Creates a Blob based on the provided data and configuration options.
   * @param  {Array} data - An array of JSON objects that will be mapped to the Blob.
   * @param  {String} writerType - ENUM for choosing the return type. Default to 'blo'
   * @return {Object|String} - The Blob object (Is an instance of Blob, but typeof Object) or a String version of the CSV with newlines.
   */
  createCSV(data, writerType = 'blob') {
    const {
      columns: optionsColumns,
      contentType: optionsContentType,
      delimeter: optionsDelimeter, // delimeter is a common miss-spelling of “delimiter”
      delimiter: optionsDelimiter,
      formatters: optionsFormatters,
      headers: optionsHeaders,
      includeHeaders: optionsIncludeHeaders,
    } = this.options || {};
    const contentType = optionsContentType || 'text/csv';
    const delimiter = optionsDelimeter || ',';
    const formatters = optionsFormatters || {};
    const getFormater = header => formatters[header] || (v => v);
    const headerNames = optionsHeaders || {};
    const headers = optionsColumns || Object.getOwnPropertyNames(data[0]);
    const includeHeaders = optionsIncludeHeaders;
    const writer = new WriterService(delimiter, contentType);

    if (includeHeaders === undefined || includeHeaders) {
      headers.forEach(header => writer.writeValue(headerNames[header] || header));
      writer.writeLine();
    }
    data.forEach(row => {
      headers.forEach(header => writer.writeValue(getFormater(header)(row[header])));
      writer.writeLine();
    });
    return writerType === 'string' ? writer.toString() : writer.toBlob();
  }
  /**
   * Triggers the download of the file.
   * @param  {Object} blob - The blob to be downloaded.
   * @param  {String} filename - Name of the file.
   */
  download(blob, filename) {
    if (navigator.msSaveBlob) {
      navigator.msSaveBlob(blob, filename);
      return;
    }
    const link = document.createElement('A');
    const url = URL.createObjectURL(blob);
    link.href = url;
    link.download = filename;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
  /**
   * Creates and passes the blob and filename params to the download() method. In case of error, calls the onError callback (if provided) and prints a error mesage if devMode is enabled via options.
   * @param  {Array} data - An array of objects to map and store.
   */
  downloadCSV(data) {
    try {
      const blob = this.createCSV(data, 'blob');
      const filename = this.options.filename || `export-${new Date().getTime() / 1000 | 0}.csv`;
      this.download(blob, filename);
    } catch (err) {
      if (this.options.devMode) {
        console.error('Error downloading CSV. Send this log to the developers: \n', err);
      }
      if (this.options.onError) {
        this.options.onError(err);
      }
    }
  }

  toString(data) {
    const writer = new WriterService(delimiter, contentType);
  }

  /**
   * Shorthand for constructor.
   * @param  {Object} options
   * @return {Object} - An instance of CSVExportService
   */
  static create(options) {
    return new CSVExportService(options);
  }
  /**
   * Shorthand for initialization and download() call.
   * @param  {Array} data - An array of objects to map and store.
   * @return {undefined}
   */
  static download(data, options = {}) {
    return new CSVExportService(options).downloadCSV(data);
  }
}
<|endoftext|>


<commit_before>// CONSTANTS
var image_default = '/home/pi/losp/keep_walking.jpg';
var file1 = '/home/pi/losp/ZinovieffBirthdaySong.mp3';
var image1 = '/home/pi/losp/LichenOhmsSeriatim3-sml.png';
var file2 = '/home/pi/losp/Simpleloop.wav';
var image2 = '/home/pi/losp/nodejs.png';
var file3 = '/home/pi/losp/blimp_zones.wav';
var image3 = '/home/pi/losp/festival.jpg';
var file4 = '/home/pi/losp/tron_bike.wav';
var image4 = '/home/pi/losp/post-its.jpg';
var rangeLimit = 3;
var timeLimit = 120;

// Load beacon scanner package
var UriBeaconScanner = require('uri-beacon-scanner');
// load package to run child process
var childProcess = require('child_process');

// Ranging function
function calc_range(rssi, tx_power) {
    var diff_db = tx_power - rssi;
    var ratio_linear = Math.pow(10, diff_db / 10);
    var r = Math.sqrt(ratio_linear);
    console.log('Range: ' +Math.round(r))
    return Math.round(r);
}

// Ranging function for Blesh beacons
function calc_range_alt(rssi, tx_power) {
    // Alternative calculation to test, likely very similar answer!
    // rssi1m needs to be found with testing
    // Based on; http://matts-soup.blogspot.co.uk/2013/12/finding-distance-from-rssi.html
    var rssi1m = -40;
    var pathLoss = 5.2; // free space
    if (rssi > 0) { rssi = 0 };
    var rawRange = Math.pow(10, (rssi - (tx_power + rssi1m)) / (-10*pathLoss));
    console.log('rangeAlt = ' + rawRange)
    return Math.round(rawRange);
}

// Step up 'listen' station
var station = function(audio, image) {
    console.log('station creation');
    this.lastPlayed = 0;
    this.showImage = true;
    this.audio = audio;
    this.image = image;
};

// Test for if station is in range and hasn't just been played
station.prototype.playTest = function(txPower, rssi) {
    now = Math.floor(new Date()/1000);
    if (calc_range_alt(rssi, txPower) < rangeLimit && now - this.lastPlayed > timeLimit) {
        console.log('Reset last played');
        this.lastPlayed = now;
        this.showImage = true;
        return true;
    } else {
        console.log('Keep last played');
        this.showImage = false;
        return false;
    }
}

// Show the image for this station
station.prototype.show = function() {
    if (this.showImage) {
        this.picture = childProcess.exec('sudo fbi -T 2 -d /dev/fb1 -noverbose -a ' +this.image, function(error, stdout, stderr) {
            if (error) {
                console.log(error.stack);
                console.log('Error code: '+error.code);
                console.log('Signal received: '+error.signal);
            }
            console.log('Child Process STDOUT: '+stdout);
            console.log('Child Process STDERR: '+stderr);
        });

        this.picture.on('exit', function(code) {
            console.log('Show exit code '+code);
            // UriBeaconScanner.startScanning();
        });
    };
};

// Play the audio for this station
station.prototype.play = function(txPower, rssi) {
    if (this.playTest(txPower, rssi)) {
        UriBeaconScanner.stopScanning();
        console.log('Play tune...');
        this.sound = childProcess.exec('omxplayer ' +this.audio, function(error, stdout, stderr) {
            if (error) {
                console.log(error.stack);
                console.log('Error code: '+error.code);
                console.log('Signal received: '+error.signal);
            }
            console.log('Child Process STDOUT: '+stdout);
            console.log('Child Process STDERR: '+stderr);
        });

        this.sound.on('exit', function(code) {
            console.log('Child process exited with exit code '+code);
            console.log('Tune has finished');
            walking.show();
            UriBeaconScanner.startScanning(true);
        });
    };
};

// Create instances of the stations
walking = new station('', image_default);
number1 = new station(file1, image1);
number2 = new station(file2, image2);
number3 = new station(file3, image3);
number4 = new station(file4, image4);

// Event handler for when beacon is discovered
UriBeaconScanner.on('discover', function(uriBeacon) {
    console.log();
    console.log('discovered UriBeacon:');
    console.log('  uri      = ' + uriBeacon.uri);
    console.log('  flags    = ' + uriBeacon.flags);
    console.log('  TX power = ' + uriBeacon.txPower);
    console.log('  RSSI     = ' + uriBeacon.rssi);
    if(new RegExp('1972').test(uriBeacon.uri)) {
        number1.play(uriBeacon.txPower, uriBeacon.rssi);
        number1.show();
    } else if(new RegExp('1966').test(uriBeacon.uri)) {
        number2.play(uriBeacon.txPower, uriBeacon.rssi);
        number2.show();
    } else if(new RegExp('2000').test(uriBeacon.uri)) {
        number3.play(uriBeacon.txPower, uriBeacon.rssi);
        number3.show();
    } else if(new RegExp('2007').test(uriBeacon.uri)) {
        number4.play(uriBeacon.txPower, uriBeacon.rssi);
        number4.show();
    }
});

// Start scanning for beacons (duplicates allowed)
UriBeaconScanner.startScanning(true);
<commit_msg>Two audio/image files per beacon<commit_after>// CONSTANTS
var image_default = '/home/pi/losp/keep_walking.jpg';
var image10 = '/home/pi/losp/image10.png';
var image11 = '/home/pi/losp/image11.png';
var image20 = '/home/pi/losp/image20.png';
var image21 = '/home/pi/losp/image21.png';
var image30 = '/home/pi/losp/image30.png';
var image31 = '/home/pi/losp/image31.png';
var image40 = '/home/pi/losp/image40.png';
var image41 = '/home/pi/losp/image41.png';
var image50 = '/home/pi/losp/image50.png';
var image51 = '/home/pi/losp/image51.png';
var image60 = '/home/pi/losp/image60.png';
var image61 = '/home/pi/losp/image61.png';
var image70 = '/home/pi/losp/image70.png';
var image71 = '/home/pi/losp/image71.png';
var image80 = '/home/pi/losp/image80.png';
var image81 = '/home/pi/losp/image81.png';
var image90 = '/home/pi/losp/image90.png';
var image91 = '/home/pi/losp/image91.png';
var audio10 = '/home/pi/losp/audio10.wav';
var audio11 = '/home/pi/losp/audio11.wav';
var audio20 = '/home/pi/losp/audio20.wav';
var audio21 = '/home/pi/losp/audio21.wav';
var audio30 = '/home/pi/losp/audio30.wav';
var audio31 = '/home/pi/losp/audio31.wav';
var audio40 = '/home/pi/losp/audio40.wav';
var audio41 = '/home/pi/losp/audio41.wav';
var audio50 = '/home/pi/losp/audio50.wav';
var audio51 = '/home/pi/losp/audio51.wav';
var audio60 = '/home/pi/losp/audio60.wav';
var audio61 = '/home/pi/losp/audio61.wav';
var audio70 = '/home/pi/losp/audio70.wav';
var audio71 = '/home/pi/losp/audio71.wav';
var audio80 = '/home/pi/losp/audio80.wav';
var audio81 = '/home/pi/losp/audio81.wav';
var audio90 = '/home/pi/losp/audio90.wav';
var audio91 = '/home/pi/losp/audio91.wav';

var rangeLimit = 3;
var playMax = 2;
var lastPlayed = '';
var checking = false;

// Load beacon scanner package
var UriBeaconScanner = require('uri-beacon-scanner');
// load package to run child process
var childProcess = require('child_process');

// Ranging function
function calc_range(rssi, tx_power) {
    var diff_db = tx_power - rssi;
    var ratio_linear = Math.pow(10, diff_db / 10);
    var r = Math.sqrt(ratio_linear);
    console.log('Range: ' +Math.round(r))
    return Math.round(r);
}

// Ranging function for Blesh beacons
function calc_range_alt(rssi, tx_power) {
    // Alternative calculation to test, likely very similar answer!
    // rssi1m needs to be found with testing
    // Based on; http://matts-soup.blogspot.co.uk/2013/12/finding-distance-from-rssi.html
    var rssi1m = -40;
    var pathLoss = 5.2; // free space
    if (rssi > 0) { rssi = 0 };
    var rawRange = Math.pow(10, (rssi - (tx_power + rssi1m)) / (-10*pathLoss));
    console.log('rangeAlt = ' +  Math.round(rawRange));
    return Math.round(rawRange);
}

// Step up 'listen' station
var station = function(name, audio1, image1, audio2, image2) {
    console.log('station creation');
    this.playCount = 0;
    this.showImage = true;
    this.beacon = name;
    this.audio1 = audio1;
    this.image1 = image1;
    this.audio2 = audio2;
    this.image2 = image2;
    this.audio = this.audio1;
    this.image = this.image1;
};

// Test for if station is in range and hasn't just been played
station.prototype.playTest = function(txPower, rssi) {
    if (calc_range_alt(rssi, txPower) < rangeLimit
                && this.playCount < playMax
                && this.beacon != lastPlayed) {
        if (this.playCount == 0) {
            console.log('Play beacon ' + this.beacon + ' ' + this.playCount)
            this.playCount = 1;
            this.image = this.image1;
            this.audio = this.audio1;
            lastPlayed = this.beacon;
        } else {
            console.log('Play beacon ' + this.beacon + ' ' + this.playCount)
            this.playCount = 2;
            this.image = this.image2;
            this.audio = this.audio2;
            lastPlayed = this.beacon;
        }
        this.showImage = true;
        return true;
    } else {
        console.log('This beacon: ' + this.beacon + ' last played: ' + lastPlayed);
        console.log('playCount: ' + this.playCount)
        this.showImage = false;
        return false;
    }
}

// Show the image for this station
station.prototype.show = function() {
    if (this.showImage) {
        this.picture = childProcess.exec('sudo fbi -T 2 -d /dev/fb1 -noverbose -a '
                                            + this.image, function(error, stdout, stderr) {
            if (error) {
                console.log(error.stack);
                console.log('Error code: '+error.code);
                console.log('Signal received: '+error.signal);
            }
            console.log('Child Process STDOUT: '+stdout);
            console.log('Child Process STDERR: '+stderr);
        });

        this.picture.on('exit', function(code) {
            console.log('Show exit code '+code);
            // UriBeaconScanner.startScanning();
        });
    };
};

// Play the audio for this station
station.prototype.play = function(txPower, rssi) {
    if (this.playTest(txPower, rssi)) {
        UriBeaconScanner.stopScanning();
        console.log('Play tune...');
        this.sound = childProcess.exec('aplay ' +this.audio, function(error, stdout, stderr) {
            if (error) {
                console.log(error.stack);
                console.log('Error code: '+error.code);
                console.log('Signal received: '+error.signal);
            }
            console.log('Child Process STDOUT: '+stdout);
            console.log('Child Process STDERR: '+stderr);
        });

        this.sound.on('exit', function(code) {
            console.log('Child process exited with exit code '+code);
            console.log('Tune has finished');
            walking.show();
            UriBeaconScanner.startScanning(true);
        });
    };
};

// Create instances of the stations
walking = new station('walking', '', image_default, '', '');
number1 = new station('number1', audio10, image10, audio11, image11);
number2 = new station('number2', audio20, image20, audio21, image21);
number3 = new station('number3', audio30, image30, audio31, image31);
number4 = new station('number4', audio40, image40, audio41, image41);
number5 = new station('number5', audio50, image50, audio51, image51);
number6 = new station('number6', audio60, image60, audio61, image61);
number7 = new station('number7', audio70, image70, audio71, image71);
number8 = new station('number8', audio80, image80, audio81, image81);
number9 = new station('number9', audio90, image90, audio91, image91);

// Event handler for when beacon is discovered
UriBeaconScanner.on('discover', function(uriBeacon) {
    if (!checking) {
        checking = true;
        console.log();
        console.log('discovered UriBeacon:');
        console.log('  uri      = ' + uriBeacon.uri);
        console.log('  flags    = ' + uriBeacon.flags);
        console.log('  TX power = ' + uriBeacon.txPower);
        console.log('  RSSI     = ' + uriBeacon.rssi);
        if(uriBeacon.uri.search('number1') > 0) {
            number1.play(uriBeacon.txPower, uriBeacon.rssi);
            number1.show();
        } else if(uriBeacon.uri.search('number2') > 0) {
            number2.play(uriBeacon.txPower, uriBeacon.rssi);
            number2.show();
        } else if(uriBeacon.uri.search('number3') > 0) {
            number3.play(uriBeacon.txPower, uriBeacon.rssi);
            number3.show();
        } else if(uriBeacon.uri.search('number4') > 0) {
            number4.play(uriBeacon.txPower, uriBeacon.rssi);
            number4.show();
        } else if(uriBeacon.uri.search('number5') > 0) {
            number5.play(uriBeacon.txPower, uriBeacon.rssi);
            number5.show();
        } else if(uriBeacon.uri.search('number6') > 0) {
            number6.play(uriBeacon.txPower, uriBeacon.rssi);
            number6.show();
        } else if(uriBeacon.uri.search('number7') > 0) {
            number7.play(uriBeacon.txPower, uriBeacon.rssi);
            number7.show();
        } else if(uriBeacon.uri.search('number8') > 0) {
            number8.play(uriBeacon.txPower, uriBeacon.rssi);
            number8.show();
        } else if(uriBeacon.uri.search('number9') > 0) {
            number9.play(uriBeacon.txPower, uriBeacon.rssi);
            number9.show();
        }
        checking = false;
    }
});

// Start scanning for beacons (duplicates allowed)
UriBeaconScanner.startScanning(true);
<|endoftext|>


<commit_before>(function () {
    "use strict";

    Mapbender.Digitizer = Mapbender.Digitizer || {};
    Mapbender.Digitizer.TableRenderer = function() {
        Mapbender.DataManager.TableRenderer.apply(this, arguments);
    };
    Mapbender.Digitizer.TableRenderer.prototype = Object.create(Mapbender.DataManager.TableRenderer.prototype);
    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        constructor: Mapbender.Digitizer.TableRenderer
    });

    Mapbender.Digitizer.Menu = function (schema) {

        var menu = this;
        menu.schema = schema;

        var frame = $("
").addClass('frame');

        frame.append('
');

        menu.appendResultTableControlButtons_(frame);

        menu.appendCurrentExtentSwitch_(frame);

        frame.hide();

        menu.currentExtentSearch = schema.currentExtentSearch;
    };

    Mapbender.Digitizer.Menu.prototype.appendResultTableControlButtons_ = function (frame) {
        var menu = this;
        var schema = menu.schema;

        var buttons = {};

        if (schema.showVisibilityNavigation && schema.allowChangeVisibility) {

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-eyeOff eyeOff");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.hideAll'));
            $button.click(function (event) {
                schema.layer.getSource().getFeatures().forEach(function (feature) {
                    feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: true});
                });
            });
            buttons['hideAll'] = $button;

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-eyeOn eyeOn");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.showAll'));
            $button.click(function (event) {
                schema.layer.getSource().getFeatures().forEach(function (feature) {
                    feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: false});
                });
            });
            buttons['showAll'] = $button;
        }
        if (schema.allowSaveAll) {

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-save save");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.saveAll'));
            $button.attr("disabled", "disabled");
            $button.click(function () {
                schema.layer.getSource().getFeatures().filter(function (feature) {
                    return (["isNew", "isChanged", "isCopy"].includes(feature.get("modificationState")));
                }).forEach(function (feature) {
                    schema.saveFeature(feature);
                });
            });
            buttons['allowSaveAll'] = $button;

        }

        var $div = $("
");
        $div.addClass("resultTableControlButtons");
        $.each(buttons, function (name, $button) {
            $div.append($button);
        });
        frame.append($div);
    };

    Mapbender.Digitizer.Menu.prototype.appendCurrentExtentSwitch_ = function (frame) {
        var menu = this;
        var schema = menu.schema;
        if (schema.showExtendSearchSwitch) {
            var $checkbox = $("<input type='checkbox' />");
            var title = Mapbender.trans('mb.digitizer.toolset.current-extent');
            $checkbox.attr('title', title);
            if (schema.currentExtentSearch) {
                $checkbox.attr("checked", "checked");
            }
            $checkbox.change(function (e) {
                var currentExtentSearch = !!$(e.originalEvent.target).prop("checked");
                menu.changeCurrentExtentSearch_(currentExtentSearch)
            });
            frame.append("
");
            var $div = $("
");
            $div.addClass("form-group checkbox onlyExtent");
            var $label = $("");
            $label.append($checkbox);
            $label.append(title);
            $div.append($label);
            frame.append($div);
        }
    };

    Mapbender.Digitizer.Menu.prototype.changeCurrentExtentSearch_ = function(currentExtentSearch) {
        var widget = this.widget;
        this.currentExtentSearch = !!currentExtentSearch;
        if (this.resultTable) {
            var features = this.schema.layer.getSource().getFeatures();
            if (this.currentExtentSearch) {
                features = features.filter(function(feature) {
                    return widget.isInExtent(feature);
                });
            }
            this.resultTable.redraw(features);
        }
    };

    Mapbender.Digitizer.Menu.prototype.registerResultTableEvents = function (resultTable, frame) {
        var menu = this;
        var schema = menu.schema;
        var widget = schema.widget;
        var map = widget.map;
        menu.resultTable = resultTable;

        map.on(ol.MapEventType.MOVEEND, function (event) {

            var resolution = map.getView().getResolution();

            if (resolution > schema.layer.getMaxResolution() || resolution < schema.layer.getMinResolution()) {

                resultTable.clear();

            } else {

                if (resultTable.currentExtentSearch) {
                    var features = schema.layer.getSource().getFeatures().filter(function (feature) {
                        return widget.isInExtent(feature);
                    });
                    resultTable.redraw(features);
                } else {
                    resultTable.redraw(schema.layer.getSource().getFeatures());
                }

            }

        });

        schema.layer.getSource().on(ol.source.VectorEventType.ADDFEATURE, function (event) {
            var feature = event.feature;

            feature.on('Digitizer.UnmodifyFeature', function (event) {
                var length = schema.layer.getSource().getFeatures().filter(function (feature) {
                    return ["isNew", "isChanged", "isCopy"].includes(feature.get("modificationState"));
                }).length;

                if (length === 0) {
                    frame.find(".resultTableControlButtons .save").attr("disabled", "disabled");
                }

            });

        });

        schema.layer.getSource().on(ol.source.VectorEventType.REMOVEFEATURE, function (event) {
            resultTable.deleteRow(event.feature);
        });
    };

    Mapbender.Digitizer.TableRenderer.prototype.getButtonsOption = function(schema) {
        var buttons = [];

        if (schema.allowLocate) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.zoomTo'),
                cssClass: 'icon-zoom -fn-zoom-to-feature'
            });
        }

        if (schema.allowEditData && schema.allowSaveInResultTable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.save.title'),
                cssClass: '-fn-save icon-save'
            });
        }

        if (schema.copy && schema.copy.enable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.clone.title'),
                cssClass: 'icon-copy copy', // NOTE: "copy" class required for getButtonByFeature ...
                onClick: function (feature, ui) {
                    schema.copyFeature(feature);
                }
            });
        }
        if (schema.allowCustomStyle) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.style.change'),
                cssClass: 'icon-style style', // NOTE: "style" class required for getButtonByFeature ...
                onClick: function (feature, ui) {
                    schema.openChangeStyleDialog(feature);
                }
            });
        }

        if (schema.allowChangeVisibility) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.visibility.toggleoff'),
                cssClass: 'icon-eyeOn -fn-toggle-visibility'
            });
        }
        var upstreamButtons = Mapbender.DataManager.TableRenderer.prototype.getButtonsOption.call(this, schema);
        return _.union(buttons, upstreamButtons);
    };

    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        render: function(schema) {
            var table = Mapbender.DataManager.TableRenderer.prototype.render.call(this, schema);
            this.registerEvents(schema, $(table));
            return table;
        },
        registerEvents: function(schema, $table) {
            $table.on('mouseenter', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.HoverFeature'});
                }
            });
            $table.on('mouseleave', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.UnhoverFeature'});
                }
            });
            $table.on('click', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    if (schema.zoomOnResultTableClick) {
                        schema.zoomToFeature(feature);
                    }
                    if (schema.openDialogOnResultTableClick) {
                        schema.openFeatureEditDialog(feature);
                    }
                }
            });
            this.registerSchemaEvents(schema);
            this.registerButtonEvents(schema, $table);
        },
        registerSchemaEvents: function(schema) {
            // @todo: DO NOT use events on non-DOM Objects; they cannot be listened to from anyone without access to the exact object
            var self = this;
            $(schema).on("Digitizer.FeatureAddedManually", function (event) {
                var dt = self.getDatatablesInstance_(schema);
                dt.row.add(event.feature);
                dt.draw();
            });
        },
        registerButtonEvents: function(schema, $table) {
            $table.on('click', 'tbody > tr .-fn-save', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.feature) {
                    schema.saveFeature(data.feature);
                }
            });
            $table.on('click', 'tbody > tr .-fn-toggle-visibility', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var feature = $(this).closest('tr').data().item;
                feature.set('hidden', !feature.get('hidden'));
                // @todo: resolve self-pollination via events (we listen to this ourselves)
                feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: feature.get("hidden")});
            });
            $table.on('click', 'tbody > tr .-fn-zoom-to-feature', function(event) {
                // Avoid calling row click handlers (may already try to zoom to feature, or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var feature = $(this).closest('tr').data().item;
                schema.zoomToFeature(feature);
            })
        },
        getCustomOptions: function(schema) {
            // Unlike upstream DM, we DO NOT want to forward any random value from schema config
            // into the dataTables / resultTable widget constructor
            return undefined;
        },
        getOptions: function(schema) {
            var options = Mapbender.DataManager.TableRenderer.prototype.getOptions.call(this, schema);
            if (typeof (schema.pageLength) !== 'undefined') {
                options.pageLength = schema.pageLength;
            }
            options.searching = !!schema.inlineSearch || (typeof (schema.inlineSearch) === 'undefined');
            return options;
        },
        getDefaultColumnConfigs: function(schema) {
            // @todo DataManager: data manager does not use default columns, but it should; without
            //                    columns, dataTables crashes immediately.
            var tableFields = [];

            tableFields.push({
                data: schema.featureType.uniqueId,
                label: 'Nr.',
                width: '20%'
            });
            return tableFields;
        },
        getColumnsConfigs: function(schema) {
            var fieldConfigs = schema.tableFields || [];
            if (!Array.isArray(fieldConfigs)) {
                // Digitizer vs DM quirk: digitizer uses a PHP-style mapping of attribute name to other config values
                // Adapt by unravelling object-to-object mapping to list of object; add the top-level key as the "data" property
                fieldConfigs = _.map(fieldConfigs, function(value, key) {
                    return Object.assign({}, value, {
                        data: key
                    });
                });
            }
            if (!fieldConfigs.length) {
                fieldConfigs = this.getDefaultColumnConfigs(schema);
            }
            return fieldConfigs;
        },
        onRowCreation: function(schema, tr, feature) {
            Mapbender.DataManager.TableRenderer.prototype.onRowCreation.apply(this, arguments);
            // Place table row into feature data for quick access (synchronized highlighting etc)
            feature.set('table-row', tr);
            // Inline save buttons start out disabled
            $('.-fn-save', tr).prop('disabled', true);
            this.registerFeatureEvents(schema, feature);
        },
        registerFeatureEvents: function(schema, feature) {
            var self = this;
            feature.on('Digitizer.HoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $(tr).addClass('hover');
                    // Page to currently hovered feature
                    // @todo: this behaviour is highly irritating. It should be configurable and off by default
                    var dt = self.getDatatablesInstance_(schema);
                    // NOTE: current dataTables versions could just do dt.row(tr).show().draw(false)
                    var rowIndex = dt.row(tr).index();
                    var pageLength = dt.page.len();
                    var rowPage = Math.floor(rowIndex / pageLength);
                    dt.page(rowPage);
                    dt.draw(false);
                }
            });

            feature.on('Digitizer.UnhoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $(tr).removeClass('hover');
                }
            });

            feature.on('Digitizer.ModifyFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $('.-fn-save', tr).prop('disabled', false);
                }
                // @todo: integrate with "save all" button (outside table)
                // frame.find(".resultTableControlButtons .save").removeAttr("disabled");
            });
            feature.on('Digitizer.UnmodifyFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $('.-fn-save', tr).prop('disabled', true);
                }
                // @todo: integrate with "save all" button (outside table)?
            });
            feature.on('Digitizer.toggleVisibility', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    var hidden = event.hide;
                    var tooltip;
                    if (hidden) {
                        tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
                    } else {
                        tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleoff')
                    }
                    $('.-fn-toggle-visibility', tr)
                        .toggleClass('icon-eyeOff', hidden)
                        .toggleClass('icon-eyeOn', !hidden)
                        .attr('title', tooltip)
                    ;
                }
            });
        }
    });
})();
<commit_msg>Extract all button visual state updates into updateButtonStates_<commit_after>(function () {
    "use strict";

    Mapbender.Digitizer = Mapbender.Digitizer || {};
    Mapbender.Digitizer.TableRenderer = function() {
        Mapbender.DataManager.TableRenderer.apply(this, arguments);
    };
    Mapbender.Digitizer.TableRenderer.prototype = Object.create(Mapbender.DataManager.TableRenderer.prototype);
    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        constructor: Mapbender.Digitizer.TableRenderer
    });

    Mapbender.Digitizer.Menu = function (schema) {

        var menu = this;
        menu.schema = schema;

        var frame = $("
").addClass('frame');

        frame.append('
');

        menu.appendResultTableControlButtons_(frame);

        menu.appendCurrentExtentSwitch_(frame);

        frame.hide();

        menu.currentExtentSearch = schema.currentExtentSearch;
    };

    Mapbender.Digitizer.Menu.prototype.appendResultTableControlButtons_ = function (frame) {
        var menu = this;
        var schema = menu.schema;

        var buttons = {};

        if (schema.showVisibilityNavigation && schema.allowChangeVisibility) {

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-eyeOff eyeOff");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.hideAll'));
            $button.click(function (event) {
                schema.layer.getSource().getFeatures().forEach(function (feature) {
                    feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: true});
                });
            });
            buttons['hideAll'] = $button;

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-eyeOn eyeOn");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.showAll'));
            $button.click(function (event) {
                schema.layer.getSource().getFeatures().forEach(function (feature) {
                    feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: false});
                });
            });
            buttons['showAll'] = $button;
        }
        if (schema.allowSaveAll) {

            var $button = $("<button class='button' type='button'/>");
            $button.addClass("icon-save save");
            $button.attr("title", Mapbender.trans('mb.digitizer.toolset.saveAll'));
            $button.attr("disabled", "disabled");
            $button.click(function () {
                schema.layer.getSource().getFeatures().filter(function (feature) {
                    return (["isNew", "isChanged", "isCopy"].includes(feature.get("modificationState")));
                }).forEach(function (feature) {
                    schema.saveFeature(feature);
                });
            });
            buttons['allowSaveAll'] = $button;

        }

        var $div = $("
");
        $div.addClass("resultTableControlButtons");
        $.each(buttons, function (name, $button) {
            $div.append($button);
        });
        frame.append($div);
    };

    Mapbender.Digitizer.Menu.prototype.appendCurrentExtentSwitch_ = function (frame) {
        var menu = this;
        var schema = menu.schema;
        if (schema.showExtendSearchSwitch) {
            var $checkbox = $("<input type='checkbox' />");
            var title = Mapbender.trans('mb.digitizer.toolset.current-extent');
            $checkbox.attr('title', title);
            if (schema.currentExtentSearch) {
                $checkbox.attr("checked", "checked");
            }
            $checkbox.change(function (e) {
                var currentExtentSearch = !!$(e.originalEvent.target).prop("checked");
                menu.changeCurrentExtentSearch_(currentExtentSearch)
            });
            frame.append("
");
            var $div = $("
");
            $div.addClass("form-group checkbox onlyExtent");
            var $label = $("");
            $label.append($checkbox);
            $label.append(title);
            $div.append($label);
            frame.append($div);
        }
    };

    Mapbender.Digitizer.Menu.prototype.changeCurrentExtentSearch_ = function(currentExtentSearch) {
        var widget = this.widget;
        this.currentExtentSearch = !!currentExtentSearch;
        if (this.resultTable) {
            var features = this.schema.layer.getSource().getFeatures();
            if (this.currentExtentSearch) {
                features = features.filter(function(feature) {
                    return widget.isInExtent(feature);
                });
            }
            this.resultTable.redraw(features);
        }
    };

    Mapbender.Digitizer.Menu.prototype.registerResultTableEvents = function (resultTable, frame) {
        var menu = this;
        var schema = menu.schema;
        var widget = schema.widget;
        var map = widget.map;
        menu.resultTable = resultTable;

        map.on(ol.MapEventType.MOVEEND, function (event) {

            var resolution = map.getView().getResolution();

            if (resolution > schema.layer.getMaxResolution() || resolution < schema.layer.getMinResolution()) {

                resultTable.clear();

            } else {

                if (resultTable.currentExtentSearch) {
                    var features = schema.layer.getSource().getFeatures().filter(function (feature) {
                        return widget.isInExtent(feature);
                    });
                    resultTable.redraw(features);
                } else {
                    resultTable.redraw(schema.layer.getSource().getFeatures());
                }

            }

        });

        schema.layer.getSource().on(ol.source.VectorEventType.ADDFEATURE, function (event) {
            var feature = event.feature;

            feature.on('Digitizer.UnmodifyFeature', function (event) {
                var length = schema.layer.getSource().getFeatures().filter(function (feature) {
                    return ["isNew", "isChanged", "isCopy"].includes(feature.get("modificationState"));
                }).length;

                if (length === 0) {
                    frame.find(".resultTableControlButtons .save").attr("disabled", "disabled");
                }

            });

        });

        schema.layer.getSource().on(ol.source.VectorEventType.REMOVEFEATURE, function (event) {
            resultTable.deleteRow(event.feature);
        });
    };

    Mapbender.Digitizer.TableRenderer.prototype.getButtonsOption = function(schema) {
        var buttons = [];

        if (schema.allowLocate) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.zoomTo'),
                cssClass: 'icon-zoom -fn-zoom-to-feature'
            });
        }

        if (schema.allowEditData && schema.allowSaveInResultTable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.save.title'),
                cssClass: '-fn-save icon-save'
            });
        }

        if (schema.copy && schema.copy.enable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.clone.title'),
                cssClass: 'icon-copy copy', // NOTE: "copy" class required for getButtonByFeature ...
                onClick: function (feature, ui) {
                    schema.copyFeature(feature);
                }
            });
        }
        if (schema.allowCustomStyle) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.style.change'),
                cssClass: 'icon-style style', // NOTE: "style" class required for getButtonByFeature ...
                onClick: function (feature, ui) {
                    schema.openChangeStyleDialog(feature);
                }
            });
        }

        if (schema.allowChangeVisibility) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.visibility.toggleoff'),
                cssClass: 'icon-eyeOn -fn-toggle-visibility'
            });
        }
        var upstreamButtons = Mapbender.DataManager.TableRenderer.prototype.getButtonsOption.call(this, schema);
        return _.union(buttons, upstreamButtons);
    };

    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        render: function(schema) {
            var table = Mapbender.DataManager.TableRenderer.prototype.render.call(this, schema);
            this.registerEvents(schema, $(table));
            return table;
        },
        registerEvents: function(schema, $table) {
            $table.on('mouseenter', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.HoverFeature'});
                }
            });
            $table.on('mouseleave', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.UnhoverFeature'});
                }
            });
            $table.on('click', 'tbody > tr', function () {
                var feature = $(this).data().item;
                if (feature) {
                    if (schema.zoomOnResultTableClick) {
                        schema.zoomToFeature(feature);
                    }
                    if (schema.openDialogOnResultTableClick) {
                        schema.openFeatureEditDialog(feature);
                    }
                }
            });
            this.registerSchemaEvents(schema);
            this.registerButtonEvents(schema, $table);
        },
        registerSchemaEvents: function(schema) {
            // @todo: DO NOT use events on non-DOM Objects; they cannot be listened to from anyone without access to the exact object
            var self = this;
            $(schema).on("Digitizer.FeatureAddedManually", function (event) {
                var dt = self.getDatatablesInstance_(schema);
                dt.row.add(event.feature);
                dt.draw();
            });
        },
        registerButtonEvents: function(schema, $table) {
            $table.on('click', 'tbody > tr .-fn-save', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.feature) {
                    schema.saveFeature(data.feature);
                }
            });
            $table.on('click', 'tbody > tr .-fn-toggle-visibility', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var feature = $(this).closest('tr').data().item;
                feature.set('hidden', !feature.get('hidden'));
                // @todo: resolve self-pollination via events (we listen to this ourselves)
                feature.dispatchEvent({type: 'Digitizer.toggleVisibility', hide: feature.get("hidden")});
            });
            $table.on('click', 'tbody > tr .-fn-zoom-to-feature', function(event) {
                // Avoid calling row click handlers (may already try to zoom to feature, or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var feature = $(this).closest('tr').data().item;
                schema.zoomToFeature(feature);
            })
        },
        getCustomOptions: function(schema) {
            // Unlike upstream DM, we DO NOT want to forward any random value from schema config
            // into the dataTables / resultTable widget constructor
            return undefined;
        },
        getOptions: function(schema) {
            var options = Mapbender.DataManager.TableRenderer.prototype.getOptions.call(this, schema);
            if (typeof (schema.pageLength) !== 'undefined') {
                options.pageLength = schema.pageLength;
            }
            options.searching = !!schema.inlineSearch || (typeof (schema.inlineSearch) === 'undefined');
            return options;
        },
        getDefaultColumnConfigs: function(schema) {
            // @todo DataManager: data manager does not use default columns, but it should; without
            //                    columns, dataTables crashes immediately.
            var tableFields = [];

            tableFields.push({
                data: schema.featureType.uniqueId,
                label: 'Nr.',
                width: '20%'
            });
            return tableFields;
        },
        getColumnsConfigs: function(schema) {
            var fieldConfigs = schema.tableFields || [];
            if (!Array.isArray(fieldConfigs)) {
                // Digitizer vs DM quirk: digitizer uses a PHP-style mapping of attribute name to other config values
                // Adapt by unravelling object-to-object mapping to list of object; add the top-level key as the "data" property
                fieldConfigs = _.map(fieldConfigs, function(value, key) {
                    return Object.assign({}, value, {
                        data: key
                    });
                });
            }
            if (!fieldConfigs.length) {
                fieldConfigs = this.getDefaultColumnConfigs(schema);
            }
            return fieldConfigs;
        },
        onRowCreation: function(schema, tr, feature) {
            Mapbender.DataManager.TableRenderer.prototype.onRowCreation.apply(this, arguments);
            // Place table row into feature data for quick access (synchronized highlighting etc)
            feature.set('table-row', tr);
            // Inline save buttons start out disabled
            $('.-fn-save', tr).prop('disabled', true);
            this.registerFeatureEvents(schema, feature);
        },
        registerFeatureEvents: function(schema, feature) {
            var self = this;
            feature.on([
                'Digitizer.ModifyFeature',
                'Digitizer.UnmodifyFeature',
                'Digitizer.toggleVisibility'
                ], function(event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    self.updateButtonStates_(tr, event);
                }
            });
            feature.on('Digitizer.HoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $(tr).addClass('hover');
                    // Page to currently hovered feature
                    // @todo: this behaviour is highly irritating. It should be configurable and off by default
                    var dt = self.getDatatablesInstance_(schema);
                    // NOTE: current dataTables versions could just do dt.row(tr).show().draw(false)
                    var rowIndex = dt.row(tr).index();
                    var pageLength = dt.page.len();
                    var rowPage = Math.floor(rowIndex / pageLength);
                    dt.page(rowPage);
                    dt.draw(false);
                }
            });

            feature.on('Digitizer.UnhoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    $(tr).removeClass('hover');
                }
            });
        },
        updateButtonStates_: function(tr, event) {
            var feature = event.target;
            var hidden = !!feature.get('hidden');
            var tooltip;
            if (hidden) {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
            } else {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleoff')
            }
            $('.-fn-toggle-visibility', tr)
                .toggleClass('icon-eyeOff', hidden)
                .toggleClass('icon-eyeOn', !hidden)
                .attr('title', tooltip)
            ;
            var activateSave = ['Digitizer.UnmodifyFeature', 'Digitizer.ModifyFeature'].indexOf(event.type);
            if (activateSave !== -1) {
                $('.-fn-save', tr).prop('disabled', !activateSave);
                // @todo: integrate with "save all" button (outside table)?
                // activate (trivial): frame.find(".resultTableControlButtons .save").removeAttr("disabled");
                // @todo: deactivation requires counting of modified features...
            }
        }
    });
})();
<|endoftext|>


<commit_before><commit_msg>Complete lesson 12 - List View<commit_after>function ListView() {
	var self = Ti.UI.createWindow({});

	// list views are designed to perform better than table views
		// table view is more customizable than list view

	var listView = Ti.UI.createListView();
	// sections are required for lists views and not for table views
	var sections = [];

	var breakfastSection = Ti.UI.createListSection({
		headerTitle : 'breakfast'
	});
	var breakfastDataSet = [
		{ properties: { title: 'panckaes' }},
		{ properties: { title: 'french toast' }},
		{ properties: { title: 'waffles' }}
	];
	breakfastSection.setItems(breakfastDataSet);
	sections.push(breakfastSection);

	var lunchSection = Ti.UI.createListSection({
		headerTitle : 'lunch'
	});
	var lunchDataSet = [
		{ properties: { title: 'grilled cheese' }},
		{ properties: { title: 'turkey sandwich' }},
		{ properties: { title: 'soup' }}
	];
	lunchSection.setItems(lunchDataSet);
	sections.push(lunchSection);

	listView.setSections(sections);
	self.add(listView);

	var dinnerSection = Ti.UI.createListSection({
		headerTitle: 'dinner'
	});
	var dinnerDataSet = [
		{ properties: { title: 'salmon' }},
		{ properties: { title: 'steak' }},
		{ properties: { title: 'portobello mushroom' }}
	];
	dinnerSection.setItems(dinnerDataSet);
	listView.appendSection(dinnerSection);

	return self;
}

module.exports = ListView;
<|endoftext|>


<commit_before>var _ = require('_');

var _slice = _.toArray,

    // See jQuery
    // src\selector-native.js: 37
    _elementSort = (function() {

        var _hasDuplicate = false;
        var _sort = function(a, b) {
            // Flag for duplicate removal
            if (a === b) {
                _hasDuplicate = true;
                return 0;
            }

            var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition(b);

            // Not directly comparable, sort on existence of method
            if (!compare) { return a.compareDocumentPosition ? -1 : 1; }

            // Disconnected nodes
            if (compare & 1) {

                // Choose the first element that is related to our document
                if (a === document || b === document) { return 1; }

                // Maintain original order
                return 0;
            }

            return compare & 4 ? -1 : 1;
        };

        return function(array) {
            _hasDuplicate = false;
            array.sort(_sort);
            return _hasDuplicate;
        };

    }()),

    _unique = function(results) {
        var hasDuplicates = _elementSort(results);
        if (!hasDuplicates) { return results; }

        var elem,
            idx = 0,
            // create the array here
            // so that a new array isn't
            // created/destroyed every unique call
            duplicates = [];

        // Go through the array and identify
        // the duplicates to be removed
        while ((elem = results[idx++])) {
            if (elem === results[idx]) {
                duplicates.push(idx);
            }
        }

        // Remove the duplicates from the results
        idx = duplicates.length;
        while (idx--) {
           results.splice(duplicates[idx], 1);
        }

        return results;
    },

    _map = function(arr, iterator) {
        var results = [];
        if (!arr.length || !iterator) { return results; }

        var idx = 0, length = arr.length,
            item;
        for (; idx < length; idx++) {
            item = arr[idx];
            results.push(iterator.call(item, item, idx));
        }

        return _.concatFlat(results);
    },

    _each = function(obj, iterator) {
        if (!obj || !iterator) { return; }

        // Array support
        if (obj.length === +obj.length) {
            var idx = 0, length = obj.length,
                item;
            for (; idx < length; idx++) {
                item = obj[idx];
                if (iterator.call(item, item, idx) === false) { return; }
            }

            return;
        }

        // Object support
        var key, value;
        for (key in obj) {
            value = obj[key];
            if (iterator.call(value, value, key) === false) { return; }
        }
    };

module.exports = {
    slice: _slice,
    elementSort: _elementSort,
    unique: _unique,
    each: _each,

    fn: {
        at: function(index) {
            return this[+index];
        },

        get: function(index) {
            // No index, return all
            if (!_.exists(index)) { return this.toArray(); }

            index = +index;

            // Looking to get an index from the end of the set
            if (index < 0) { index = (this.length + index); }

            return this[index];
        },

        eq: function(index) {
            return D(this.get(index));
        },

        slice: function(start, end) {
            return D(_slice(this.toArray(), start, end));
        },

        first: function() {
            return D(this[0]);
        },

        last: function() {
            return D(this[this.length - 1]);
        },

        toArray: function() {
            return _slice(this);
        },

        map: function(iterator) {
            return D(_map(this, iterator));
        },

        each: function(iterator) {
            _each(this, iterator);
            return this;
        },

        forEach: function(iterator) {
            _each(this, iterator);
            return this;
        }
    }
};
<commit_msg>Revert "Fixed IE 11 in IE 8 document mode bug"<commit_after>var _ = require('_');

var _slice = (function(_slice) {
        return function(arr, start, end) {
            // Exit early for empty array
            if (!arr || !arr.length) { return []; }

            // End, naturally, has to be higher than 0 to matter,
            // so a simple existance check will do
            if (end) { return _slice.call(arr, start, end); }

            return _slice.call(arr, start || 0);
        };
    }([].slice)),

    // See jQuery
    // src\selector-native.js: 37
    _elementSort = (function() {

        var _hasDuplicate = false;
        var _sort = function(a, b) {
            // Flag for duplicate removal
            if (a === b) {
                _hasDuplicate = true;
                return 0;
            }

            var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition(b);

            // Not directly comparable, sort on existence of method
            if (!compare) { return a.compareDocumentPosition ? -1 : 1; }

            // Disconnected nodes
            if (compare & 1) {

                // Choose the first element that is related to our document
                if (a === document || b === document) { return 1; }

                // Maintain original order
                return 0;
            }

            return compare & 4 ? -1 : 1;
        };

        return function(array) {
            _hasDuplicate = false;
            array.sort(_sort);
            return _hasDuplicate;
        };

    }()),

    _unique = function(results) {
        var hasDuplicates = _elementSort(results);
        if (!hasDuplicates) { return results; }

        var elem,
            idx = 0,
            // create the array here
            // so that a new array isn't
            // created/destroyed every unique call
            duplicates = [];

        // Go through the array and identify
        // the duplicates to be removed
        while ((elem = results[idx++])) {
            if (elem === results[idx]) {
                duplicates.push(idx);
            }
        }

        // Remove the duplicates from the results
        idx = duplicates.length;
        while (idx--) {
           results.splice(duplicates[idx], 1);
        }

        return results;
    },

    _map = function(arr, iterator) {
        var results = [];
        if (!arr.length || !iterator) { return results; }

        var idx = 0, length = arr.length,
            item;
        for (; idx < length; idx++) {
            item = arr[idx];
            results.push(iterator.call(item, item, idx));
        }

        return _.concatFlat(results);
    },

    _each = function(obj, iterator) {
        if (!obj || !iterator) { return; }

        // Array support
        if (obj.length === +obj.length) {
            var idx = 0, length = obj.length,
                item;
            for (; idx < length; idx++) {
                item = obj[idx];
                if (iterator.call(item, item, idx) === false) { return; }
            }

            return;
        }

        // Object support
        var key, value;
        for (key in obj) {
            value = obj[key];
            if (iterator.call(value, value, key) === false) { return; }
        }
    };

module.exports = {
    slice: _slice,
    elementSort: _elementSort,
    unique: _unique,
    each: _each,

    fn: {
        at: function(index) {
            return this[+index];
        },

        get: function(index) {
            // No index, return all
            if (!_.exists(index)) { return this.toArray(); }

            index = +index;

            // Looking to get an index from the end of the set
            if (index < 0) { index = (this.length + index); }

            return this[index];
        },

        eq: function(index) {
            return D(this.get(index));
        },

        slice: function(start, end) {
            return D(_slice(this.toArray(), start, end));
        },

        first: function() {
            return D(this[0]);
        },

        last: function() {
            return D(this[this.length - 1]);
        },

        toArray: function() {
            return _slice(this);
        },

        map: function(iterator) {
            return D(_map(this, iterator));
        },

        each: function(iterator) {
            _each(this, iterator);
            return this;
        },

        forEach: function(iterator) {
            _each(this, iterator);
            return this;
        }
    }
};
<|endoftext|>


<commit_before>'use strict';

var alias = require('identify-resource').alias
	, clone = require('lodash/lang/clone')
	, cnsl = require('./utils/cnsl')
	, extend = require('lodash/object/extend')
	, fs = require('fs')
	, fsUtils = require('recur-fs')
	, glob = require('glob').sync
	, path = require('path')
	, unique = require('lodash/array/unique')

	, DEFAULT_FILE_EXTENSIONS = {
			js: ['js', 'json'],
			css: ['css'],
			html: ['html']}
	, DEFAULT_JS = 'buddy.js'
	, DEFAULT_JSON = 'buddy.json'
	, DEFAULT_OPTIONS = {
			compress: false,
			lint: false,
			script: false,
			lazy: false,
			reload: false,
			serve: false,
			watch: false,
			deploy: false,
			verbose: false,
			targets: ['js','css','html']}
	, DEFAULT_PACKAGE_JSON = 'package.json'
	, DEFAULT_SERVER = {
			directory: '.',
			port: 8080}
	, DEFAULT_SOURCES = {
			js: ['.'],
			css: ['.'],
			html: ['.']}
	, RE_GLOB = /[\*\[\{]/
	, RE_UNIQUE = /{(?:hash|date)}/

	, debug = cnsl.debug
	, error = cnsl.error
	, hunt = fsUtils.hunt.sync
	, indir = fsUtils.indir
	, strong = cnsl.strong
	, warn = cnsl.warn;

exports.data = {
	fileExtensions: extend({}, DEFAULT_FILE_EXTENSIONS),
	runtimeOptions: extend({}, DEFAULT_OPTIONS),
	server: extend({}, DEFAULT_SERVER)
};

/**
 * Load and parse configuration file
 * Accepts optional path to file or directory
 * @param {String | Object} [configpath]
 * @param {String} [fileExtensions]
 * @param {Object} [runtimeOptions]
 * @returns {Object}
 */
exports.load = function (configpath, fileExtensions, runtimeOptions) {
	var url, data;

	runtimeOptions = runtimeOptions || {};
	runtimeOptions.version = require(path.resolve(__dirname, '../package.json')).version;

	// Load
	if ('string' == typeof configpath || configpath == null) {
		url = exports.locate(configpath);
		data = require(url);
		// Set current directory to location of file
		process.chdir(path.dirname(url));
		// Store
		exports.data.url = url;

	// Passed in JSON object
	} else {
		data = configpath;
	}

	// Package.json
	if (data.buddy) data = data.buddy;
	extend(exports.data.fileExtensions, fileExtensions);
	// Override default runtimeOptions
	extend(exports.data.runtimeOptions, runtimeOptions);
	extend(exports.data.server, data.server);
	exports.data.script = data.script;

	// Validate
	if (data.build) {
		// Store
		exports.data.build = exports.parse(data.build, exports.data.runtimeOptions, exports.data.cache);
		// Error parsing
		if (!exports.data.build) throw new Error('invalid "build" data for ' + strong(url));
		return exports.data;
	} else {
		throw new Error('no "build" data for ' + strong(url));
	}

	return data;
};

/**
 * Locate the configuration file
 * Walks the directory tree if no file/directory specified
 * @param {String} [url]
 * @returns {String}
 */
exports.locate = function (url) {
	var configpath;

	function check (dir) {
			// Support js, json, and package.json
		var urljs = path.join(dir, DEFAULT_JS)
			, urljson = path.join(dir, DEFAULT_JSON)
			, urlpkgjson = path.join(dir, DEFAULT_PACKAGE_JSON)
			, url;

		if (fs.existsSync(url = urljs)
			|| fs.existsSync(url = urljson)
			|| fs.existsSync(url = urlpkgjson)) {
				return url;
		} else {
			return '';
		}
	}

	if (url) {
		configpath = path.resolve(url);
			try {
				// Try default file name if passed directory
				if (!path.extname(configpath).length || fs.statSync(configpath).isDirectory()) {
					configpath = check(configpath);
					if (!configpath) throw 'no default found';
				}
			} catch (err) {
				throw new Error(strong('buddy') + ' config not found in ' + strong(path.dirname(url)));
			}

	// No url specified
	} else {
		try {
			// Find the first instance of a DEFAULT file based on the current working directory
			configpath = hunt(process.cwd(), function (resource, stat) {
				if (stat.isFile()) {
					var basename = path.basename(resource);
					return (basename == DEFAULT_PACKAGE_JSON || basename == DEFAULT_JS || basename == DEFAULT_JSON);
				}
			}, true);
		} catch (err) {
			throw new Error(strong('buddy') + ' config not found');
		}
	}

	return configpath;
};

/**
 * Parse and validate config 'data'
 * @param {Object} data
 * @returns {Object}
 */
exports.parse = function (data) {
	var build = {
		targets: []
	};

	// Loop through target types 'js/css/html'
	for (var type in data) {
		// Allow --targets to specify which targets to build
		if (!exports.data.runtimeOptions.targets || ~exports.data.runtimeOptions.targets.indexOf(type)) {
			var bld = data[type];

			// Validate
			if (bld && bld.targets && bld.targets.length) {
				// Store targets
				build.targets = build.targets.concat(parseTargets(type, bld.sources, bld.targets));
			} else {
				warn('invalid build configuration: ' + strong(type));
			}
		}
	}

	// Return nothing if validation errors
	return build.targets.length ? build : null;
};

/**
 * Parse 'targets' of 'type'
 * @param {String} type
 * @param {Array} sources
 * @param {Array} [targets]
 * @returns {Array}
 */
function parseTargets (type, sources, targets) {
	var valid = []
		, target;

	for (var i = 0; i < targets.length; i++) {
		target = targets[i];
		target.type = type;
		if (target.modular == null) target.modular = true;
		target.fileExtensions = exports.data.fileExtensions[type];
		target.runtimeOptions = clone(exports.data.runtimeOptions);
		parseInput(target);
		// Single target
		if (parseOutput(target)) {
			parseAppServer(target);
			parseSources(target, sources);
			// Store hooks
			if (target.before) target.before = defineHook(target.before);
			if (target.afterEach) target.afterEach = defineHook(target.afterEach);
			if (target.after) target.after = defineHook(target.after);
			// Register aliases
			if (target.alias) alias(target.alias);
			// Override bootstrap/boilerplate
			if (target.type != 'js' || (target.type == 'js' && !target.modular)) {
				target.bootstrap = target.boilerplate = false;
			}
			// Generate processing workflow
			processWorkflow(target);
			// Traverse child targets
			if (target.targets) {
				target.hasChildren = true;
				target.targets = parseTargets(type, sources, target.targets);
			}
			// Store
			valid.push(target);

		// Multiple targets
		} else {
			// Split target if multiple inputs/outputs
			target.runtimeOptions = target.fileExtensions = null;
			var inputs = target.input
				, outputs = target.output
				, t;

			for (var j = 0, m = inputs.length; j < m; j++) {
				t = clone(target);
				t.input = inputs[j];
				t.output = outputs[j];
				t.inputPath = null;
				targets.push(t);
			}
		}
	}

	return valid;
}

/**
 * Parse input path(s) for 'target'
 * @param {Object} target
 */
function parseInput (target) {
	function existentialWarning (input) {
		warn(strong(input) + ' doesn\'t exist');
	}

	// Match glob pattern
	if (RE_GLOB.test(target.input)) {
		target.input = glob(target.input);
	}

	// Handle array of input paths
	if (Array.isArray(target.input)) {
		target.inputPath = target.input.map(function (input) {
			var inputPath = path.resolve(input);
			if (!fs.existsSync(inputPath)) existentialWarning(input);
			return inputPath;
		});
		target.isBatch = true;

	// Handle single file
	} else {
		target.inputPath = path.resolve(target.input);
		if (!fs.existsSync(target.inputPath)) existentialWarning(target.input);
		// Directory
		target.isBatch = !path.extname(target.inputPath).length;
	}
}

/**
 * Parse output path(s) for 'target'
 * @param {Object} target
 * @returns {Boolean}
 */
function parseOutput (target) {
	// Output is optional...
	if (target.output) {
		// Handle array of output paths
		if (Array.isArray(target.output)) {
			if (target.output.length == 1) {
				// Remove array if only 1 element
				target.output = target.output[0];
			} else {
				// Abort if input is not also array or lengths don't match
				if (!Array.isArray(target.inputPath) || target.input.length != target.output.length) {
					throw new error('multiple files/directories outputs ('
						+ (strong(target.output))
						+ ') are not valid targets for ('
						+ (strong(target.input))
						+ ') input target');
				}
				// Trigger split into separate targets
				return false;
			}
		}

		// Handle specified compressed output path
		target.outputPath = (target.runtimeOptions.compress && target.output_compressed)
			? path.resolve(target.output_compressed)
			: path.resolve(target.output);
		// Outputting to file
		var isOutputFile = path.extname(target.outputPath).length;
		// Abort if input is directory and output is file
		if (target.isBatch && isOutputFile) {
			throw new error('a file ('
				+ (strong(target.output))
				+ ') is not a valid output target for ('
				+ (strong(target.input))
				+ ') input target');
		}

		// Resolve default output file name for file>directory target
		if (!target.isBatch && !isOutputFile) {
			target.outputPath = path.join(target.outputPath, path.basename(target.inputPath))
				.replace(path.extname(target.inputPath), '.' + target.type);
		}
	}

	return true;
}

/**
 * Parse app server for 'target'
 * @param {Object} target
 */
function parseAppServer (target) {
	// Test if 'p' is in 'dirs'
	function contains (dirs, p) {
		if (!Array.isArray(dirs)) dirs = [dirs];
		return dirs.some(function (dir) {
			return indir(dir, p);
		});
	}

	if (exports.data.server && exports.data.server.file) {
		if (target.server != null) {
			target.isAppServer = target.server;
		} else {
			var server = path.resolve(exports.data.server.file);
			// Accept 'server' boolean setting
			target.isAppServer = target.isBatch
				? contains(target.inputPath, server)
				: (server == target.inputPath);
		}
	}
}

/**
 * Parse sources for 'target'
 * @param {Object} target
 * @param {Array} [sources]
 */
function parseSources (target, sources) {
	// Use input directory as default if none specified
	function derivedSources (inputs) {
		if (!Array.isArray(inputs)) inputs = [inputs];
		return inputs.map(function (input) {
			return path.extname(input).length
				? path.dirname(input)
				: input;
		});
	}

	target.sources = (sources ? sources : derivedSources(target.input))
		.concat(DEFAULT_SOURCES[target.type])
		.map(function (source) {
			return path.resolve(source);
		});
}

/**
 * Determine processing workflow
 * @param {Target} target
 */
function processWorkflow (target) {
	target.workflow = [['load']];
	if (target.type == 'html') {
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('parse', 'replaceReferences', 'compress')
			target.workflow.push(['compile', 'inline']);
		}

	} else if (target.type == 'css') {
		if (target.runtimeOptions.lint) target.workflow[0].push('lint');
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('parse');
			target.workflow.push(['inline', 'compile']);
			if (target.runtimeOptions.compress) target.workflow[1].push('compress');
		}

	} else {
		if (target.runtimeOptions.lint) target.workflow[0].push('lint');
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('replaceEnvironment', 'compile', 'parse');
			if (target.modular) {
				target.workflow[0].push('inline', 'replaceReferences');
				if (target.runtimeOptions.lazy) {
					if (target.runtimeOptions.compress) target.workflow[0].push('compress');
					target.workflow[0].push('escape');
				}
				target.workflow[0].push('wrap');
				target.workflow.push(['concat']);
				if (target.runtimeOptions.compress) target.workflow[1].push('compress');
			}
		}
	}
}

/**
 * Convert hook path or expression to Function
 * @param {String} hook
 * @returns {Function}
 */
function defineHook (hook) {
	var hookpath;

	// Load file content if filepath
	if (fs.existsSync(hookpath = path.resolve(hook))) {
		hook = fs.readFileSync(hookpath, 'utf8');
	}

	return new Function('global', 'process', 'console', 'require', 'context', 'options', 'done', hook);
}
<commit_msg>guard against inadvertent throw<commit_after>'use strict';

var alias = require('identify-resource').alias
	, clone = require('lodash/lang/clone')
	, cnsl = require('./utils/cnsl')
	, extend = require('lodash/object/extend')
	, fs = require('fs')
	, fsUtils = require('recur-fs')
	, glob = require('glob').sync
	, path = require('path')
	, unique = require('lodash/array/unique')

	, DEFAULT_FILE_EXTENSIONS = {
			js: ['js', 'json'],
			css: ['css'],
			html: ['html']}
	, DEFAULT_JS = 'buddy.js'
	, DEFAULT_JSON = 'buddy.json'
	, DEFAULT_OPTIONS = {
			compress: false,
			lint: false,
			script: false,
			lazy: false,
			reload: false,
			serve: false,
			watch: false,
			deploy: false,
			verbose: false,
			targets: ['js','css','html']}
	, DEFAULT_PACKAGE_JSON = 'package.json'
	, DEFAULT_SERVER = {
			directory: '.',
			port: 8080}
	, DEFAULT_SOURCES = {
			js: ['.'],
			css: ['.'],
			html: ['.']}
	, RE_GLOB = /[\*\[\{]/
	, RE_UNIQUE = /{(?:hash|date)}/

	, debug = cnsl.debug
	, error = cnsl.error
	, hunt = fsUtils.hunt.sync
	, indir = fsUtils.indir
	, strong = cnsl.strong
	, warn = cnsl.warn;

exports.data = {
	fileExtensions: extend({}, DEFAULT_FILE_EXTENSIONS),
	runtimeOptions: extend({}, DEFAULT_OPTIONS),
	server: extend({}, DEFAULT_SERVER)
};

/**
 * Load and parse configuration file
 * Accepts optional path to file or directory
 * @param {String | Object} [configpath]
 * @param {String} [fileExtensions]
 * @param {Object} [runtimeOptions]
 * @returns {Object}
 */
exports.load = function (configpath, fileExtensions, runtimeOptions) {
	var url, data;

	runtimeOptions = runtimeOptions || {};
	runtimeOptions.version = require(path.resolve(__dirname, '../package.json')).version;

	// Load
	if ('string' == typeof configpath || configpath == null) {
		url = exports.locate(configpath);
		data = require(url);
		// Set current directory to location of file
		process.chdir(path.dirname(url));
		// Store
		exports.data.url = url;

	// Passed in JSON object
	} else {
		data = configpath;
	}

	// Package.json
	if (data.buddy) data = data.buddy;
	extend(exports.data.fileExtensions, fileExtensions);
	// Override default runtimeOptions
	extend(exports.data.runtimeOptions, runtimeOptions);
	extend(exports.data.server, data.server);
	exports.data.script = data.script;

	// Validate
	if (data.build) {
		// Store
		exports.data.build = exports.parse(data.build, exports.data.runtimeOptions, exports.data.cache);
		// Error parsing
		if (!exports.data.build) throw new Error('invalid "build" data for ' + strong(url));
		return exports.data;
	} else {
		throw new Error('no "build" data for ' + strong(url));
	}

	return data;
};

/**
 * Locate the configuration file
 * Walks the directory tree if no file/directory specified
 * @param {String} [url]
 * @returns {String}
 */
exports.locate = function (url) {
	var configpath;

	function check (dir) {
			// Support js, json, and package.json
		var urljs = path.join(dir, DEFAULT_JS)
			, urljson = path.join(dir, DEFAULT_JSON)
			, urlpkgjson = path.join(dir, DEFAULT_PACKAGE_JSON)
			, url;

		if (fs.existsSync(url = urljs)
			|| fs.existsSync(url = urljson)
			|| fs.existsSync(url = urlpkgjson)) {
				return url;
		} else {
			return '';
		}
	}

	if (url) {
		configpath = path.resolve(url);
			try {
				// Try default file name if passed directory
				if (!path.extname(configpath).length || fs.statSync(configpath).isDirectory()) {
					configpath = check(configpath);
					if (!configpath) throw 'no default found';
				}
			} catch (err) {
				throw new Error(strong('buddy') + ' config not found in ' + strong(path.dirname(url)));
			}

	// No url specified
	} else {
		try {
			// Find the first instance of a DEFAULT file based on the current working directory
			configpath = hunt(process.cwd(), function (resource, stat) {
				if (stat.isFile()) {
					var basename = path.basename(resource);
					return (basename == DEFAULT_PACKAGE_JSON || basename == DEFAULT_JS || basename == DEFAULT_JSON);
				}
			}, true);
		} catch (err) {
			if (!configpath) throw new Error(strong('buddy') + ' config not found');
		}
	}

	return configpath;
};

/**
 * Parse and validate config 'data'
 * @param {Object} data
 * @returns {Object}
 */
exports.parse = function (data) {
	var build = {
		targets: []
	};

	// Loop through target types 'js/css/html'
	for (var type in data) {
		// Allow --targets to specify which targets to build
		if (!exports.data.runtimeOptions.targets || ~exports.data.runtimeOptions.targets.indexOf(type)) {
			var bld = data[type];

			// Validate
			if (bld && bld.targets && bld.targets.length) {
				// Store targets
				build.targets = build.targets.concat(parseTargets(type, bld.sources, bld.targets));
			} else {
				warn('invalid build configuration: ' + strong(type));
			}
		}
	}

	// Return nothing if validation errors
	return build.targets.length ? build : null;
};

/**
 * Parse 'targets' of 'type'
 * @param {String} type
 * @param {Array} sources
 * @param {Array} [targets]
 * @returns {Array}
 */
function parseTargets (type, sources, targets) {
	var valid = []
		, target;

	for (var i = 0; i < targets.length; i++) {
		target = targets[i];
		target.type = type;
		if (target.modular == null) target.modular = true;
		target.fileExtensions = exports.data.fileExtensions[type];
		target.runtimeOptions = clone(exports.data.runtimeOptions);
		parseInput(target);
		// Single target
		if (parseOutput(target)) {
			parseAppServer(target);
			parseSources(target, sources);
			// Store hooks
			if (target.before) target.before = defineHook(target.before);
			if (target.afterEach) target.afterEach = defineHook(target.afterEach);
			if (target.after) target.after = defineHook(target.after);
			// Register aliases
			if (target.alias) alias(target.alias);
			// Override bootstrap/boilerplate
			if (target.type != 'js' || (target.type == 'js' && !target.modular)) {
				target.bootstrap = target.boilerplate = false;
			}
			// Generate processing workflow
			processWorkflow(target);
			// Traverse child targets
			if (target.targets) {
				target.hasChildren = true;
				target.targets = parseTargets(type, sources, target.targets);
			}
			// Store
			valid.push(target);

		// Multiple targets
		} else {
			// Split target if multiple inputs/outputs
			target.runtimeOptions = target.fileExtensions = null;
			var inputs = target.input
				, outputs = target.output
				, t;

			for (var j = 0, m = inputs.length; j < m; j++) {
				t = clone(target);
				t.input = inputs[j];
				t.output = outputs[j];
				t.inputPath = null;
				targets.push(t);
			}
		}
	}

	return valid;
}

/**
 * Parse input path(s) for 'target'
 * @param {Object} target
 */
function parseInput (target) {
	function existentialWarning (input) {
		warn(strong(input) + ' doesn\'t exist');
	}

	// Match glob pattern
	if (RE_GLOB.test(target.input)) {
		target.input = glob(target.input);
	}

	// Handle array of input paths
	if (Array.isArray(target.input)) {
		target.inputPath = target.input.map(function (input) {
			var inputPath = path.resolve(input);
			if (!fs.existsSync(inputPath)) existentialWarning(input);
			return inputPath;
		});
		target.isBatch = true;

	// Handle single file
	} else {
		target.inputPath = path.resolve(target.input);
		if (!fs.existsSync(target.inputPath)) existentialWarning(target.input);
		// Directory
		target.isBatch = !path.extname(target.inputPath).length;
	}
}

/**
 * Parse output path(s) for 'target'
 * @param {Object} target
 * @returns {Boolean}
 */
function parseOutput (target) {
	// Output is optional...
	if (target.output) {
		// Handle array of output paths
		if (Array.isArray(target.output)) {
			if (target.output.length == 1) {
				// Remove array if only 1 element
				target.output = target.output[0];
			} else {
				// Abort if input is not also array or lengths don't match
				if (!Array.isArray(target.inputPath) || target.input.length != target.output.length) {
					throw new error('multiple files/directories outputs ('
						+ (strong(target.output))
						+ ') are not valid targets for ('
						+ (strong(target.input))
						+ ') input target');
				}
				// Trigger split into separate targets
				return false;
			}
		}

		// Handle specified compressed output path
		target.outputPath = (target.runtimeOptions.compress && target.output_compressed)
			? path.resolve(target.output_compressed)
			: path.resolve(target.output);
		// Outputting to file
		var isOutputFile = path.extname(target.outputPath).length;
		// Abort if input is directory and output is file
		if (target.isBatch && isOutputFile) {
			throw new error('a file ('
				+ (strong(target.output))
				+ ') is not a valid output target for ('
				+ (strong(target.input))
				+ ') input target');
		}

		// Resolve default output file name for file>directory target
		if (!target.isBatch && !isOutputFile) {
			target.outputPath = path.join(target.outputPath, path.basename(target.inputPath))
				.replace(path.extname(target.inputPath), '.' + target.type);
		}
	}

	return true;
}

/**
 * Parse app server for 'target'
 * @param {Object} target
 */
function parseAppServer (target) {
	// Test if 'p' is in 'dirs'
	function contains (dirs, p) {
		if (!Array.isArray(dirs)) dirs = [dirs];
		return dirs.some(function (dir) {
			return indir(dir, p);
		});
	}

	if (exports.data.server && exports.data.server.file) {
		if (target.server != null) {
			target.isAppServer = target.server;
		} else {
			var server = path.resolve(exports.data.server.file);
			// Accept 'server' boolean setting
			target.isAppServer = target.isBatch
				? contains(target.inputPath, server)
				: (server == target.inputPath);
		}
	}
}

/**
 * Parse sources for 'target'
 * @param {Object} target
 * @param {Array} [sources]
 */
function parseSources (target, sources) {
	// Use input directory as default if none specified
	function derivedSources (inputs) {
		if (!Array.isArray(inputs)) inputs = [inputs];
		return inputs.map(function (input) {
			return path.extname(input).length
				? path.dirname(input)
				: input;
		});
	}

	target.sources = (sources ? sources : derivedSources(target.input))
		.concat(DEFAULT_SOURCES[target.type])
		.map(function (source) {
			return path.resolve(source);
		});
}

/**
 * Determine processing workflow
 * @param {Target} target
 */
function processWorkflow (target) {
	target.workflow = [['load']];
	if (target.type == 'html') {
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('parse', 'replaceReferences', 'compress')
			target.workflow.push(['compile', 'inline']);
		}

	} else if (target.type == 'css') {
		if (target.runtimeOptions.lint) target.workflow[0].push('lint');
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('parse');
			target.workflow.push(['inline', 'compile']);
			if (target.runtimeOptions.compress) target.workflow[1].push('compress');
		}

	} else {
		if (target.runtimeOptions.lint) target.workflow[0].push('lint');
		// If no output, watch task only
		if (target.outputPath) {
			target.workflow[0].push('replaceEnvironment', 'compile', 'parse');
			if (target.modular) {
				target.workflow[0].push('inline', 'replaceReferences');
				if (target.runtimeOptions.lazy) {
					if (target.runtimeOptions.compress) target.workflow[0].push('compress');
					target.workflow[0].push('escape');
				}
				target.workflow[0].push('wrap');
				target.workflow.push(['concat']);
				if (target.runtimeOptions.compress) target.workflow[1].push('compress');
			}
		}
	}
}

/**
 * Convert hook path or expression to Function
 * @param {String} hook
 * @returns {Function}
 */
function defineHook (hook) {
	var hookpath;

	// Load file content if filepath
	if (fs.existsSync(hookpath = path.resolve(hook))) {
		hook = fs.readFileSync(hookpath, 'utf8');
	}

	return new Function('global', 'process', 'console', 'require', 'context', 'options', 'done', hook);
}
<|endoftext|>