<commit_before>var path = require('path'),
	fs = require('fs'),
	parsers = require('./parsers'),
	woods = require('./woods'),
	Site = require('./Site'),
	Showdown = require('showdown'),
	converter = new Showdown.converter(),
	express = require('express');

// Helper function to get the uri or url of the page
var getUri = function(page, url) {
	var uri = page.directory,
		parent = page.parent;
	while (parent) {
		uri = path.join(parent.directory, uri);
		parent = parent.parent;
	}
	var site = url ? page.site.url : page.site.getUri('content');
	return path.join(site, uri);
};

var Page = function(site, parent, directory) {
	this.site = site;
	this.directory = directory;
	this.data = {};
	this.parent = parent || null;
	// Only pages with a number prefixed to their
	// directory name are marked as being visible.
	this.visible = /^\/[0-9]/.test(this.directory);

	// Remove numbers in path, since they are only used for order,
	// and replace spaces by dashes:
	this.url = getUri(this, true).replace(/\/[0-9]+-/g,'/')
				.replace(/\s/g, '-');
	this.uri = getUri(this);
	site.pageByUrl[this.url] = this;
	this.name = this.directory == ''
			? 'home'
			: path.basename(this.url).replace(/\/$/, '');

	installRoute(this);
	addChildren(this);
	addResources(this);
	parseContent(this);
};

Page.prototype.getTemplate = function(name) {
	var type = this.data.type,
		types = this.site.types;
	return type && types[type][name]
		? types[type][name]
		: types['default'][name] || function(param) {
			return 'Template not found: ' + name;
		};
};

Page.prototype.getTemplateLocation = function(name) {
	var templateLocations = this.site.templateLocations,
		type = this.data.type;
	return this.data.type && templateLocations[type][name]
		? templateLocations[type][name]
		: templateLocations['default'][name];
};

Page.prototype.renderTemplate = function(name) {
	return this.getTemplate(name)({
		page: this,
		parse: function(content, param) {
			return converter.makeHtml(content);
		}
	});
};

Page.prototype.getUri = function(file) {
	return path.join(this.uri, file || '');
};

Page.prototype.getUrl = function(file) {
	return path.join(this.url, file || '');
};

/**
 * The title of the page as defined in its document
 * or, when not defined, the name of its folder.
 *
 * @return {String}
 */
Page.prototype.getTitle = function() {
	return this.data.title || this.name;
};

/**
 * Checks whether this page has any children.
 *
 * @return {Boolean} {@true it is has one or more children}
 */
Page.prototype.hasChildren = function() {
	return this.children.length > 0;
};

/**
 * Checks whether the specified page is the parent of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is the parent of the page}
 */
Page.prototype.isParent = function(page) {
	return this.parent == page;
};

/**
 * Checks whether the specified page is a child of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true it is a child of the page}
 */
Page.prototype.isChild = function(page) {
	return page && page.parent == this;
};

/**
 * Checks if this page is contained within the specified page
 * or one of its parents.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is inside the specified page}
 */
Page.prototype.isDescendant = function(page) {
	var parent = this;
	while (parent = parent.parent) {
		if (parent == page)
			return true;
	}
	return false;
};

/**
 * Checks if the page is an ancestor of the specified page.
 *
 * @param {Item} page the page to check against
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.isAncestor = function(page) {
	return page ? page.isDescendant(this) : false;
};

/**
 * Checks whether there is a previous page on the same level as this one.
 *
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.hasPrevious = function() {
	return this.visible && this.index > 0;
};

/**
 * Checks whether there is a next page on the same level as this one.
 *
 * @return {Boolean}
 */
Page.prototype.hasNext = function() {
	return this.visible && this.index + 1 < this.parent.children.length;
};

/**
 * The previous page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getPrevious = function() {
	return this.hasPrevious() ? this.parent.children[this.index - 1] : null;
};

/**
 * The next page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getNext = function(page) {
	return this.hasNext() ? this.parent.children[this.index + 1] : null;
};

/**
 * Check whether this page has images.
 *
 * @return {Boolean}
 */
Page.prototype.hasImages = function(page) {
	return !!this.images.length;
};

var addResources = function(page) {
	var routeUrl = page.getUri('resources'),
		resourcesDir = page.getUri('resources'),
		images = page.images = [],
		resources = page.resources = [];

	woods.express.use(routeUrl, express.static(resourcesDir));

	var addFile = function(file) {
		if (/jpg|jpeg|png|gif/.test(file)) {
			images.push(file);
		} else {
			resources.push(file);
		}
	};

	var addFiles = function() {
		fs.readdir(resourcesDir, function(err, files) {
			files.forEach(addFile);
		});
	};

	fs.exists(resourcesDir, function(exists) {
		console.log(exists);
		if (!exists) return;
		addFiles();
	});
};

var addChildren = function(page) {
	// Read the directory and add child pagees (if any):
	var children = page.children = [],
		// TODO: make async:
		files = fs.readdirSync(page.uri),
		index = 0;
	for (var i in files) {
		var name = files[i],
			file = path.resolve(page.uri, name),
			// TODO: make async:
			isDirectory = fs.statSync(file).isDirectory(),
			nameReserved = /^(resources|error)$/.test(name);
		if (isDirectory && !nameReserved) {
			var child = new Page(page.site, page, name);
			// Only add to children list if it is a child
			// - i.e. its name starts in the style of: 01- or 02-
			children.push(child);
			child.index = index++;
		}
	}
};

var parseContent = function(page) {
	// Parse the content file and place values in page.data:
	var contentPath = page.getUri('content.md');
	parsers.parse(contentPath, page);
};

var installRoute = function(page) {
	// Install the path in express:
	woods.express.get(page.url, function(req, res) {
		var template = page.getTemplateLocation('html');
		res.render(template, {
			site: page.site,
			page: page,
			parse: function(content, param) {
				return converter.makeHtml(content);
			}
		});
	});
};

module.exports = Page;
<commit_msg>Move local requires together in Page.<commit_after>var path = require('path'),
	fs = require('fs'),
	Showdown = require('showdown'),
	converter = new Showdown.converter(),
	express = require('express');

var woods = require('./woods'),
	parsers = require('./parsers'),
	Site = require('./Site'),
	Image = require('./Image');

// Helper function to get the uri or url of the page
var getUri = function(page, url) {
	var uri = page.directory,
		parent = page.parent;
	while (parent) {
		uri = path.join(parent.directory, uri);
		parent = parent.parent;
	}
	var site = url ? page.site.url : page.site.getUri('content');
	return path.join(site, uri);
};

var Page = function(site, parent, directory) {
	this.site = site;
	this.directory = directory;
	this.data = {};
	this.parent = parent || null;
	// Only pages with a number prefixed to their
	// directory name are marked as being visible.
	this.visible = /^\/[0-9]/.test(this.directory);

	// Remove numbers in path, since they are only used for order,
	// and replace spaces by dashes:
	this.url = getUri(this, true).replace(/\/[0-9]+-/g,'/')
				.replace(/\s/g, '-');
	this.uri = getUri(this);
	site.pageByUrl[this.url] = this;
	this.name = this.directory == ''
			? 'home'
			: path.basename(this.url).replace(/\/$/, '');

	installRoute(this);
	addChildren(this);
	addResources(this);
	parseContent(this);
};

Page.prototype.getTemplate = function(name) {
	var type = this.data.type,
		types = this.site.types;
	return type && types[type][name]
		? types[type][name]
		: types['default'][name] || function(param) {
			return 'Template not found: ' + name;
		};
};

Page.prototype.getTemplateLocation = function(name) {
	var templateLocations = this.site.templateLocations,
		type = this.data.type;
	return this.data.type && templateLocations[type][name]
		? templateLocations[type][name]
		: templateLocations['default'][name];
};

Page.prototype.renderTemplate = function(name) {
	return this.getTemplate(name)({
		page: this,
		parse: function(content, param) {
			return converter.makeHtml(content);
		}
	});
};

Page.prototype.getUri = function(file) {
	return path.join(this.uri, file || '');
};

Page.prototype.getUrl = function(file) {
	return path.join(this.url, file || '');
};

/**
 * The title of the page as defined in its document
 * or, when not defined, the name of its folder.
 *
 * @return {String}
 */
Page.prototype.getTitle = function() {
	return this.data.title || this.name;
};

/**
 * Checks whether this page has any children.
 *
 * @return {Boolean} {@true it is has one or more children}
 */
Page.prototype.hasChildren = function() {
	return this.children.length > 0;
};

/**
 * Checks whether the specified page is the parent of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is the parent of the page}
 */
Page.prototype.isParent = function(page) {
	return this.parent == page;
};

/**
 * Checks whether the specified page is a child of this page.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true it is a child of the page}
 */
Page.prototype.isChild = function(page) {
	return page && page.parent == this;
};

/**
 * Checks if this page is contained within the specified page
 * or one of its parents.
 *
 * @param {Item} page The page to check against
 * @return {Boolean} {@true if it is inside the specified page}
 */
Page.prototype.isDescendant = function(page) {
	var parent = this;
	while (parent = parent.parent) {
		if (parent == page)
			return true;
	}
	return false;
};

/**
 * Checks if the page is an ancestor of the specified page.
 *
 * @param {Item} page the page to check against
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.isAncestor = function(page) {
	return page ? page.isDescendant(this) : false;
};

/**
 * Checks whether there is a previous page on the same level as this one.
 *
 * @return {Boolean} {@true if the page is an ancestor of the specified
 * page}
 */
Page.prototype.hasPrevious = function() {
	return this.visible && this.index > 0;
};

/**
 * Checks whether there is a next page on the same level as this one.
 *
 * @return {Boolean}
 */
Page.prototype.hasNext = function() {
	return this.visible && this.index + 1 < this.parent.children.length;
};

/**
 * The previous page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getPrevious = function() {
	return this.hasPrevious() ? this.parent.children[this.index - 1] : null;
};

/**
 * The next page on the same level as this page.
 *
 * @return {Page}
 */
Page.prototype.getNext = function(page) {
	return this.hasNext() ? this.parent.children[this.index + 1] : null;
};

/**
 * Check whether this page has images.
 *
 * @return {Boolean}
 */
Page.prototype.hasImages = function(page) {
	return !!this.images.length;
};

var addResources = function(page) {
	var routeUrl = page.getUri('resources'),
		resourcesDir = page.getUri('resources'),
		images = page.images = [],
		resources = page.resources = [];

	woods.express.use(routeUrl, express.static(resourcesDir));

	var addFile = function(file) {
		if (/jpg|jpeg|png|gif/.test(file)) {
			images.push(file);
		} else {
			resources.push(file);
		}
	};

	var addFiles = function() {
		fs.readdir(resourcesDir, function(err, files) {
			files.forEach(addFile);
		});
	};

	fs.exists(resourcesDir, function(exists) {
		console.log(exists);
		if (!exists) return;
		addFiles();
	});
};

var addChildren = function(page) {
	// Read the directory and add child pagees (if any):
	var children = page.children = [],
		// TODO: make async:
		files = fs.readdirSync(page.uri),
		index = 0;
	for (var i in files) {
		var name = files[i],
			file = path.resolve(page.uri, name),
			// TODO: make async:
			isDirectory = fs.statSync(file).isDirectory(),
			nameReserved = /^(resources|error)$/.test(name);
		if (isDirectory && !nameReserved) {
			var child = new Page(page.site, page, name);
			// Only add to children list if it is a child
			// - i.e. its name starts in the style of: 01- or 02-
			children.push(child);
			child.index = index++;
		}
	}
};

var parseContent = function(page) {
	// Parse the content file and place values in page.data:
	var contentPath = page.getUri('content.md');
	parsers.parse(contentPath, page);
};

var installRoute = function(page) {
	// Install the path in express:
	woods.express.get(page.url, function(req, res) {
		var template = page.getTemplateLocation('html');
		res.render(template, {
			site: page.site,
			page: page,
			parse: function(content, param) {
				return converter.makeHtml(content);
			}
		});
	});
};

module.exports = Page;
<|endoftext|>


<commit_before>/*
 #
 # Copyright (c) 2017 nexB Inc. and others. All rights reserved.
 # https://nexb.com and https://github.com/nexB/aboutcode-manager/
 # The ScanCode software is licensed under the Apache License version 2.0.
 # AboutCode is a trademark of nexB Inc.
 #
 # You may not use this software except in compliance with the License.
 # You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
 # Unless required by applicable law or agreed to in writing, software distributed
 # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 # CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 #
 */

const AboutCodeDB = require('./aboutCodeDB');
const Splitter = require('./helpers/splitter');
const Progress = require('./helpers/progress');
const DejaCodeExportDialog = require('./dejacodeExportDialog');
const ComponentDialog = require('./componentDialog');

const AboutCodeDashboard = require('./aboutCodeDashboard');
const AboutCodeBarChart = require('./aboutCodeBarChart');
const AboutCodeJsTree = require('./aboutCodeJsTree');
const AboutCodeNodeView = require('./aboutCodeNodeView');
const AboutCodeClueDataTable = require('./aboutCodeClueDataTable');
const AboutCodeComponentDataTable = require('./aboutCodeComponentDataTable');

const fs = require('fs');
const shell = require('electron').shell;
const dialog = require('electron').remote.dialog;
const path = require('path');

// The Electron module used to communicate asynchronously from a renderer process to the main process.
const ipcRenderer = require('electron').ipcRenderer;
const aboutCodeVersion = require('../../../package.json').version;

/**
 * This is the UI Controller for the application. It's responsible for
 * initializing and updating all of the views and handling user interaction
 */
$(document).ready(() => {
  // Create default values for all of the data and ui classes
  let aboutCodeDB = new AboutCodeDB();

  const dashboard = new AboutCodeDashboard('#dashboard-container', aboutCodeDB);

  const barChart = new AboutCodeBarChart('#summary-bar-chart', aboutCodeDB)
    .on('bar-clicked', (attribute, value) => {
      // Show files that contain attribute value selected by user in bar chart
      if (value !== 'No Value Detected') {
        cluesTable.clearColumnFilters();
        cluesTable.setColumnFilter(attribute, value);

        // This needs to be done only when the column is visible.
        // So we do it last to try our best
        showClueButton.trigger('click');
      }
    });

  const componentDialog = new ComponentDialog('#componentDialog', aboutCodeDB)
    .on('save', (component) => {
      nodeView.nodeData()[component.path].component = component;
      componentsTable.needsReload(true);
      nodeView.needsReload(true);
      redrawCurrentView();
    })
    .on('delete', (component) => {
      nodeView.nodeData()[component.path].component = null;
      componentsTable.needsReload(true);
      nodeView.needsReload(true);
      redrawCurrentView();
    });

  const dejaCodeExportDialog =
        new DejaCodeExportDialog('#componentExportModal', aboutCodeDB);

  const nodeView = new AboutCodeNodeView('#nodeview', aboutCodeDB)
    .on('node-clicked', (node) => componentDialog.show(node.id));

  const cluesTable = new AboutCodeClueDataTable('#clues-table', aboutCodeDB);
  const componentsTable = new AboutCodeComponentDataTable('#components-table', aboutCodeDB)
    .on('upload-clicked', (components) => {
      if (components.length > 0) {
        dejaCodeExportDialog.show();
      } else {
        dialog.showErrorBox(
          'No Components to Upload',
          'You have no Components to upload.\n\n' +
                    'Please create at least one Component and try again.');
      }
    })
    .on('export-json', exportJsonComponents);

  const jstree = new AboutCodeJsTree('#jstree', aboutCodeDB)
    .on('node-edit', (node) => componentDialog.show(node.id))
    .on('node-selected', (node) => {
      // Set the search value for the first column (path) equal to the
      // Selected jstree path and redraw the table
      const searchTerm = node.id + (node.type === 'file' ? '' : '/');
      cluesTable.columns(0).search(searchTerm);

      // update all views with the new selected path.
      componentDialog.selectedPath(node.id);
      dejaCodeExportDialog.selectedPath(node.id);
      jstree.selectedPath(node.id);
      cluesTable.selectedPath(node.id);
      componentsTable.selectedPath(node.id);
      dashboard.selectedPath(node.id);
      nodeView.selectedPath(node.id);
      barChart.selectedPath(node.id);

      redrawCurrentView();
    });

  const splitter = new Splitter('#leftCol', '#rightCol')
    .on('drag-end', () => redrawCurrentView());

  // The id of the currently selected nav bar button.
  const currentNavButtonId = '#sidebar-wrapper .sidebar-nav .active button';

  // Defines DOM element constants for sidebar buttons.
  const saveSQLiteFileButton = $('#save-file');
  const openSQLiteFileButton = $('#open-file');
  const showClueButton = $('#show-tab-clues');
  const showNodeViewButton = $('#show-tab-nodeview');
  const showComponentButton = $('#show-tab-component');
  const showBarChartButton = $('#show-tab-barchart');
  const showDashboardButton = $('#show-tab-dashboard');

  // Open a SQLite Database File
  openSQLiteFileButton.click(openSQLite);

  // Save a SQLite Database file
  saveSQLiteFileButton.click(saveSQLite);

  // Show clue DataTable. Hide node view and component summary table
  showClueButton.click(() => {
    splitter.show();
    cluesTable.redraw();
  });

  // Show node view. Hide clue and component table
  showNodeViewButton.click(() => {
    splitter.show();
    nodeView.redraw();
  });

  // Show component summary table. Hide DataTable and node view
  showComponentButton.click(() => {
    splitter.hide();
    componentsTable.redraw();
  });

  showBarChartButton.click(() => {
    splitter.show();
    barChart.redraw();
  });

  showDashboardButton.click(() => {
    splitter.hide();
    dashboard.redraw();
  });

  // Open links in default browser
  $('.open-in-default').click((evt) => {
    evt.preventDefault();
    shell.openExternal(evt.target.href);
  });

  ipcRenderer.on('table-view', () => showClueButton.trigger('click'));
  ipcRenderer.on('node-view', () => showNodeViewButton.trigger('click'));
  ipcRenderer.on('component-summary-view', () => showComponentButton.trigger('click'));
  ipcRenderer.on('open-SQLite', () => openSQLiteFileButton.trigger('click'));
  ipcRenderer.on('chart-summary-view', () => showBarChartButton.trigger('click'));
  ipcRenderer.on('save-SQLite', () => saveSQLiteFileButton.trigger('click'));
  ipcRenderer.on('import-JSON', importJson);
  ipcRenderer.on('export-JSON', exportJson);
  ipcRenderer.on('export-JSON-components-only', exportJsonComponents);

  // Opens the dashboard view when the app is first opened
  showDashboardButton.trigger('click');

  /** Creates the database and all View objects from a SQLite file */
  function loadDatabase(fileName) {
    // Create a new database when importing a json file
    aboutCodeDB = new AboutCodeDB({
      dbName: 'aboutcode_db',
      dbStorage: fileName
    });

    return updateViews();
  }

  /** Loads data for all views based on the current data */
  function updateViews() {
    return aboutCodeDB.sync
      .then(() => {
        const currFile = aboutCodeDB.sequelize.options.storage;
        document.title = 'AboutCode Manager - ' + path.basename(currFile);
        cluesTable.clearColumnFilters();

        // update all views with the new database.
        componentDialog.db(aboutCodeDB);
        dejaCodeExportDialog.db(aboutCodeDB);
        jstree.db(aboutCodeDB);
        cluesTable.db(aboutCodeDB);
        componentsTable.db(aboutCodeDB);
        dashboard.db(aboutCodeDB);
        nodeView.db(aboutCodeDB);
        barChart.db(aboutCodeDB);

        // Reload the jstree, then trigger the current view to reload.
        jstree.redraw();
      })
      .catch((reason) => { throw reason; });
  }

  function redrawCurrentView() {
    $(currentNavButtonId).trigger('click');
  }

  /** Open a SQLite Database File */
  function openSQLite() {
    dialog.showOpenDialog({
      properties: ['openFile'],
      title: 'Open a SQLite File',
      filters: [{
        name: 'SQLite File',
        extensions: ['sqlite']
      }]
    }, (fileNames) => {
      if (fileNames && fileNames[0]) {
        loadDatabase(fileNames[0]);
      }
    });
  }

  /** Save a SQLite Database File */
  function saveSQLite() {
    dialog.showSaveDialog({
      title: 'Save as a Database File',
      filters: [
        { name: 'SQLite File', extensions: ['sqlite'] }
      ]
    }, (newFileName) => {
      if (newFileName) {
        const oldFileName = aboutCodeDB.sequelize.options.storage;
        const reader = fs.createReadStream(oldFileName);
        const writer = fs.createWriteStream(newFileName);
        reader.pipe(writer);
        reader.on('end', () => loadDatabase(newFileName));
      }
    });
  }

  /** Import a ScanCode JSON file and create a SQLite database */
  function importJson() {
    dialog.showOpenDialog({
      title: 'Open a JSON File',
      filters: [{
        name: 'JSON File',
        extensions: ['json']
      }]
    }, (fileNames) => {
      if (fileNames === undefined) {
        return;
      }

      const jsonFileName = fileNames[0];

      // Immediately ask for a SQLite to save and create the database
      dialog.showSaveDialog({
        title: 'Save a SQLite Database File',
        filters: [{
          name: 'SQLite File',
          extensions: ['sqlite']
        }]
      }, (sqliteFileName) => {
        if (sqliteFileName === undefined) {
          return;
        }

        // Overwrite existing sqlite file
        if (fs.existsSync(sqliteFileName)) {
          fs.unlink(sqliteFileName, (err) => {
            if (err) {
              throw err;
            }
            console.info(`Deleted ${sqliteFileName}`);
          });
        }

        // Create a new database when importing a json file
        aboutCodeDB = new AboutCodeDB({
          dbName: 'demo_schema',
          dbStorage: sqliteFileName,
        });

        const progressbar = new Progress('#content', {
          title: 'Creating Database...',
          size: 100,
        });

        aboutCodeDB.sync
          .then(() => progressbar.showDeterminate())
          .then(() => aboutCodeDB.addFromJson(
            jsonFileName,
            aboutCodeVersion,
            (progress) => progressbar.update(progress / 100)))
          .then(() => progressbar.hide())
          .then(updateViews)
          .catch((err) => {
            progressbar.hide();
            if (err instanceof AboutCodeDB.MissingFileInfoError) {
              dialog.showErrorBox(
                'Missing File Type Information',
                'Missing file \'type\' information in the scanned data. ' +
                '\n\nThis probably means you ran the scan without the -i ' +
                'option in ScanCode. The app requires file information from ' +
                'a ScanCode scan. Rerun the scan using \n./scancode ' +
                '-clipeu options.'
              );
            } else {
              // Show error for problem with the JSON file
              dialog.showErrorBox(
                'JSON Error',
                'There is a problem with your JSON file. It may be malformed ' +
                '(e.g., the addition of a trailing comma), ' +
                'or there could be some other problem with the file. ' +
                '\n\nPlease check your file and try again. ' +
                '\n\nThe error thrown by the system is: \n\n' + err
              );
            }
            console.error(err);
          });
      });
    });
  }

  /** Export JSON file with original ScanCode data and components that have been created */
  function exportJson() {
    dialog.showSaveDialog({
      properties: ['openFile'],
      title: 'Save as JSON file',
      filters: [{
        name: 'JSON File Type',
        extensions: ['json']
      }]
    }, (fileName) => {
      if (fileName === undefined) {
        return;
      }

      const scanCodeInfoPromise = aboutCodeDB.getScanCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const aboutCodeInfoPromise = aboutCodeDB.getAboutCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const clueFilesPromise = aboutCodeDB.findAll({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const componentsPromise = aboutCodeDB.findAllComponents({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const filesCountPromise = aboutCodeDB.getFileCount({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      Promise.all([scanCodeInfoPromise, aboutCodeInfoPromise,
        filesCountPromise, clueFilesPromise, componentsPromise])
        .then(([scanCodeInfo, aboutCodeInfo, filesCount, clueFiles, components]) => {
          const json = {
            aboutcode_manager_notice: aboutCodeInfo.aboutcode_manager_notice,
            aboutcode_manager_version: aboutCodeInfo.aboutcode_manager_version,
            scancode_version: scanCodeInfo.scancode_version,
            scancode_options: scanCodeInfo.scancode_options,
            files_count: filesCount,
            files: clueFiles,
            components: components
          };

          fs.writeFile(fileName, JSON.stringify(json), (err) => {
            if (err) {
              throw err;
            }
          });
        });
    });
  }

  /** Export JSON file with only components that have been created */
  function exportJsonComponents() {
    dialog.showSaveDialog({
      properties: ['openFile'],
      title: 'Save as JSON file',
      filters: [{
        name: 'JSON File Type',
        extensions: ['json']
      }]
    }, (fileName) => {
      if (fileName === undefined) {
        return;
      }

      const aboutCodeInfoPromise = aboutCodeDB.getAboutCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const componentsPromise = aboutCodeDB.findAllComponents({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      Promise.all([aboutCodeInfoPromise, componentsPromise])
        .then(([aboutCodeInfo, components]) => {
          const json = {
            aboutcode_manager_notice: aboutCodeInfo.aboutcode_manager_notice,
            aboutcode_manager_version: aboutCodeInfo.aboutcode_manager_version,
            components: components
          };

          fs.writeFile(fileName, JSON.stringify(json), (err) => {
            if (err) {
              throw err;
            }
          });
        });
    });
  }
});

module.exports = aboutCodeVersion;<commit_msg>Validate database header #228<commit_after>/*
 #
 # Copyright (c) 2017 nexB Inc. and others. All rights reserved.
 # https://nexb.com and https://github.com/nexB/aboutcode-manager/
 # The ScanCode software is licensed under the Apache License version 2.0.
 # AboutCode is a trademark of nexB Inc.
 #
 # You may not use this software except in compliance with the License.
 # You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
 # Unless required by applicable law or agreed to in writing, software distributed
 # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 # CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 #
 */

const AboutCodeDB = require('./aboutCodeDB');
const Splitter = require('./helpers/splitter');
const Progress = require('./helpers/progress');
const DejaCodeExportDialog = require('./dejacodeExportDialog');
const ComponentDialog = require('./componentDialog');

const AboutCodeDashboard = require('./aboutCodeDashboard');
const AboutCodeBarChart = require('./aboutCodeBarChart');
const AboutCodeJsTree = require('./aboutCodeJsTree');
const AboutCodeNodeView = require('./aboutCodeNodeView');
const AboutCodeClueDataTable = require('./aboutCodeClueDataTable');
const AboutCodeComponentDataTable = require('./aboutCodeComponentDataTable');

const fs = require('fs');
const shell = require('electron').shell;
const dialog = require('electron').remote.dialog;
const path = require('path');

// The Electron module used to communicate asynchronously from a renderer process to the main process.
const ipcRenderer = require('electron').ipcRenderer;
const aboutCodeVersion = require('../../../package.json').version;

/**
 * This is the UI Controller for the application. It's responsible for
 * initializing and updating all of the views and handling user interaction
 */
$(document).ready(() => {
  // Create default values for all of the data and ui classes
  let aboutCodeDB = new AboutCodeDB();

  const dashboard = new AboutCodeDashboard('#dashboard-container', aboutCodeDB);

  const barChart = new AboutCodeBarChart('#summary-bar-chart', aboutCodeDB)
    .on('bar-clicked', (attribute, value) => {
      // Show files that contain attribute value selected by user in bar chart
      if (value !== 'No Value Detected') {
        cluesTable.clearColumnFilters();
        cluesTable.setColumnFilter(attribute, value);

        // This needs to be done only when the column is visible.
        // So we do it last to try our best
        showClueButton.trigger('click');
      }
    });

  const componentDialog = new ComponentDialog('#componentDialog', aboutCodeDB)
    .on('save', (component) => {
      nodeView.nodeData()[component.path].component = component;
      componentsTable.needsReload(true);
      nodeView.needsReload(true);
      redrawCurrentView();
    })
    .on('delete', (component) => {
      nodeView.nodeData()[component.path].component = null;
      componentsTable.needsReload(true);
      nodeView.needsReload(true);
      redrawCurrentView();
    });

  const dejaCodeExportDialog =
        new DejaCodeExportDialog('#componentExportModal', aboutCodeDB);

  const nodeView = new AboutCodeNodeView('#nodeview', aboutCodeDB)
    .on('node-clicked', (node) => componentDialog.show(node.id));

  const cluesTable = new AboutCodeClueDataTable('#clues-table', aboutCodeDB);
  const componentsTable = new AboutCodeComponentDataTable('#components-table', aboutCodeDB)
    .on('upload-clicked', (components) => {
      if (components.length > 0) {
        dejaCodeExportDialog.show();
      } else {
        dialog.showErrorBox(
          'No Components to Upload',
          'You have no Components to upload.\n\n' +
                    'Please create at least one Component and try again.');
      }
    })
    .on('export-json', exportJsonComponents);

  const jstree = new AboutCodeJsTree('#jstree', aboutCodeDB)
    .on('node-edit', (node) => componentDialog.show(node.id))
    .on('node-selected', (node) => {
      // Set the search value for the first column (path) equal to the
      // Selected jstree path and redraw the table
      const searchTerm = node.id + (node.type === 'file' ? '' : '/');
      cluesTable.columns(0).search(searchTerm);

      // update all views with the new selected path.
      componentDialog.selectedPath(node.id);
      dejaCodeExportDialog.selectedPath(node.id);
      jstree.selectedPath(node.id);
      cluesTable.selectedPath(node.id);
      componentsTable.selectedPath(node.id);
      dashboard.selectedPath(node.id);
      nodeView.selectedPath(node.id);
      barChart.selectedPath(node.id);

      redrawCurrentView();
    });

  const splitter = new Splitter('#leftCol', '#rightCol')
    .on('drag-end', () => redrawCurrentView());

  // The id of the currently selected nav bar button.
  const currentNavButtonId = '#sidebar-wrapper .sidebar-nav .active button';

  // Defines DOM element constants for sidebar buttons.
  const saveSQLiteFileButton = $('#save-file');
  const openSQLiteFileButton = $('#open-file');
  const showClueButton = $('#show-tab-clues');
  const showNodeViewButton = $('#show-tab-nodeview');
  const showComponentButton = $('#show-tab-component');
  const showBarChartButton = $('#show-tab-barchart');
  const showDashboardButton = $('#show-tab-dashboard');

  // Open a SQLite Database File
  openSQLiteFileButton.click(openSQLite);

  // Save a SQLite Database file
  saveSQLiteFileButton.click(saveSQLite);

  // Show clue DataTable. Hide node view and component summary table
  showClueButton.click(() => {
    splitter.show();
    cluesTable.redraw();
  });

  // Show node view. Hide clue and component table
  showNodeViewButton.click(() => {
    splitter.show();
    nodeView.redraw();
  });

  // Show component summary table. Hide DataTable and node view
  showComponentButton.click(() => {
    splitter.hide();
    componentsTable.redraw();
  });

  showBarChartButton.click(() => {
    splitter.show();
    barChart.redraw();
  });

  showDashboardButton.click(() => {
    splitter.hide();
    dashboard.redraw();
  });

  // Open links in default browser
  $('.open-in-default').click((evt) => {
    evt.preventDefault();
    shell.openExternal(evt.target.href);
  });

  ipcRenderer.on('table-view', () => showClueButton.trigger('click'));
  ipcRenderer.on('node-view', () => showNodeViewButton.trigger('click'));
  ipcRenderer.on('component-summary-view', () => showComponentButton.trigger('click'));
  ipcRenderer.on('open-SQLite', () => openSQLiteFileButton.trigger('click'));
  ipcRenderer.on('chart-summary-view', () => showBarChartButton.trigger('click'));
  ipcRenderer.on('save-SQLite', () => saveSQLiteFileButton.trigger('click'));
  ipcRenderer.on('import-JSON', importJson);
  ipcRenderer.on('export-JSON', exportJson);
  ipcRenderer.on('export-JSON-components-only', exportJsonComponents);

  // Opens the dashboard view when the app is first opened
  showDashboardButton.trigger('click');

  /** Creates the database and all View objects from a SQLite file */
  function loadDatabase(fileName) {
    // Create a new database when importing a json file
    aboutCodeDB = new AboutCodeDB({
      dbName: 'aboutcode_db',
      dbStorage: fileName
    });

    // Check that the database has the correct header information.
    aboutCodeDB.sync
      .then((db) => db.Header.findAll())
      .then((headers) => {
        if (headers.length === 0) {
          dialog.showErrorBox(
            'Invalid SQLite file: ' + fileName,
            'The SQLite file is invalid. Try re-importing the ScanCode JSON ' +
            'file and creating a new SQLite file.');
        }
      });

    return updateViews();
  }

  /** Loads data for all views based on the current data */
  function updateViews() {
    return aboutCodeDB.sync
      .then(() => {
        const currFile = aboutCodeDB.sequelize.options.storage;
        document.title = 'AboutCode Manager - ' + path.basename(currFile);
        cluesTable.clearColumnFilters();

        // update all views with the new database.
        componentDialog.db(aboutCodeDB);
        dejaCodeExportDialog.db(aboutCodeDB);
        jstree.db(aboutCodeDB);
        cluesTable.db(aboutCodeDB);
        componentsTable.db(aboutCodeDB);
        dashboard.db(aboutCodeDB);
        nodeView.db(aboutCodeDB);
        barChart.db(aboutCodeDB);

        // Reload the jstree, then trigger the current view to reload.
        jstree.redraw();
      })
      .catch((reason) => { throw reason; });
  }

  function redrawCurrentView() {
    $(currentNavButtonId).trigger('click');
  }

  /** Open a SQLite Database File */
  function openSQLite() {
    dialog.showOpenDialog({
      properties: ['openFile'],
      title: 'Open a SQLite File',
      filters: [{
        name: 'SQLite File',
        extensions: ['sqlite']
      }]
    }, (fileNames) => {
      if (fileNames && fileNames[0]) {
        loadDatabase(fileNames[0]);
      }
    });
  }

  /** Save a SQLite Database File */
  function saveSQLite() {
    dialog.showSaveDialog({
      title: 'Save as a Database File',
      filters: [
        { name: 'SQLite File', extensions: ['sqlite'] }
      ]
    }, (newFileName) => {
      if (newFileName) {
        const oldFileName = aboutCodeDB.sequelize.options.storage;
        const reader = fs.createReadStream(oldFileName);
        const writer = fs.createWriteStream(newFileName);
        reader.pipe(writer);
        reader.on('end', () => loadDatabase(newFileName));
      }
    });
  }

  /** Import a ScanCode JSON file and create a SQLite database */
  function importJson() {
    dialog.showOpenDialog({
      title: 'Open a JSON File',
      filters: [{
        name: 'JSON File',
        extensions: ['json']
      }]
    }, (fileNames) => {
      if (fileNames === undefined) {
        return;
      }

      const jsonFileName = fileNames[0];

      // Immediately ask for a SQLite to save and create the database
      dialog.showSaveDialog({
        title: 'Save a SQLite Database File',
        filters: [{
          name: 'SQLite File',
          extensions: ['sqlite']
        }]
      }, (sqliteFileName) => {
        if (sqliteFileName === undefined) {
          return;
        }

        // Overwrite existing sqlite file
        if (fs.existsSync(sqliteFileName)) {
          fs.unlink(sqliteFileName, (err) => {
            if (err) {
              throw err;
            }
            console.info(`Deleted ${sqliteFileName}`);
          });
        }

        // Create a new database when importing a json file
        aboutCodeDB = new AboutCodeDB({
          dbName: 'demo_schema',
          dbStorage: sqliteFileName,
        });

        const progressbar = new Progress('#content', {
          title: 'Creating Database...',
          size: 100,
        });

        aboutCodeDB.sync
          .then(() => progressbar.showDeterminate())
          .then(() => aboutCodeDB.addFromJson(
            jsonFileName,
            aboutCodeVersion,
            (progress) => progressbar.update(progress / 100)))
          .then(() => progressbar.hide())
          .then(updateViews)
          .catch((err) => {
            progressbar.hide();
            if (err instanceof AboutCodeDB.MissingFileInfoError) {
              dialog.showErrorBox(
                'Missing File Type Information',
                'Missing file \'type\' information in the scanned data. ' +
                '\n\nThis probably means you ran the scan without the -i ' +
                'option in ScanCode. The app requires file information from ' +
                'a ScanCode scan. Rerun the scan using \n./scancode ' +
                '-clipeu options.'
              );
            } else {
              // Show error for problem with the JSON file
              dialog.showErrorBox(
                'JSON Error',
                'There is a problem with your JSON file. It may be malformed ' +
                '(e.g., the addition of a trailing comma), ' +
                'or there could be some other problem with the file. ' +
                '\n\nPlease check your file and try again. ' +
                '\n\nThe error thrown by the system is: \n\n' + err
              );
            }
            console.error(err);
          });
      });
    });
  }

  /** Export JSON file with original ScanCode data and components that have been created */
  function exportJson() {
    dialog.showSaveDialog({
      properties: ['openFile'],
      title: 'Save as JSON file',
      filters: [{
        name: 'JSON File Type',
        extensions: ['json']
      }]
    }, (fileName) => {
      if (fileName === undefined) {
        return;
      }

      const scanCodeInfoPromise = aboutCodeDB.getScanCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const aboutCodeInfoPromise = aboutCodeDB.getAboutCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const clueFilesPromise = aboutCodeDB.findAll({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const componentsPromise = aboutCodeDB.findAllComponents({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const filesCountPromise = aboutCodeDB.getFileCount({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      Promise.all([scanCodeInfoPromise, aboutCodeInfoPromise,
        filesCountPromise, clueFilesPromise, componentsPromise])
        .then(([scanCodeInfo, aboutCodeInfo, filesCount, clueFiles, components]) => {
          const json = {
            aboutcode_manager_notice: aboutCodeInfo.aboutcode_manager_notice,
            aboutcode_manager_version: aboutCodeInfo.aboutcode_manager_version,
            scancode_version: scanCodeInfo.scancode_version,
            scancode_options: scanCodeInfo.scancode_options,
            files_count: filesCount,
            files: clueFiles,
            components: components
          };

          fs.writeFile(fileName, JSON.stringify(json), (err) => {
            if (err) {
              throw err;
            }
          });
        });
    });
  }

  /** Export JSON file with only components that have been created */
  function exportJsonComponents() {
    dialog.showSaveDialog({
      properties: ['openFile'],
      title: 'Save as JSON file',
      filters: [{
        name: 'JSON File Type',
        extensions: ['json']
      }]
    }, (fileName) => {
      if (fileName === undefined) {
        return;
      }

      const aboutCodeInfoPromise = aboutCodeDB.getAboutCodeInfo({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      const componentsPromise = aboutCodeDB.findAllComponents({
        attributes: {
          exclude: ['id', 'createdAt', 'updatedAt']
        }
      });

      Promise.all([aboutCodeInfoPromise, componentsPromise])
        .then(([aboutCodeInfo, components]) => {
          const json = {
            aboutcode_manager_notice: aboutCodeInfo.aboutcode_manager_notice,
            aboutcode_manager_version: aboutCodeInfo.aboutcode_manager_version,
            components: components
          };

          fs.writeFile(fileName, JSON.stringify(json), (err) => {
            if (err) {
              throw err;
            }
          });
        });
    });
  }
});

module.exports = aboutCodeVersion;<|endoftext|>


<commit_before>
var Mysql        = require('../');
var Connection   = require('./Connection');

module.exports = Pool;
function Pool(options) {
  this.config = options.config;
  this.config.connectionConfig.pool = this;

  this._allConnections   = [];
  this._freeConnections  = [];
  this._connectionQueue  = [];
  this._closed           = false;
}

Pool.prototype.getConnection = function(cb) {
  if (this._closed) {
    cb(new Error('Pool is closed.'));
    return;
  }

  if (this._freeConnections.length > 0) {
    var connection = this._freeConnections[0];
    this._freeConnections.shift();
    cb(null, connection);
  } else if (this.config.connectionLimit == 0 || this._allConnections.length < this.config.connectionLimit) {
    var self       = this;
    var connection = this._createConnection();
    this._allConnections.push(connection);
    connection.connect(function(err) {
      if (self._closed) {
        cb(new Error('Pool is closed.'));
      }
      else if (err) {
        cb(err);
      } else {
        cb(null, connection);
      }
    });
  } else if (this.config.waitForConnections) {
    this._connectionQueue.push(cb);
  } else {
    cb(new Error('No connections available.'));
  }
};

Pool.prototype.releaseConnection = function(connection) {
  if (this._connectionQueue.length) {
    var cb = this._connectionQueue[0];
    this._connectionQueue.shift();
    process.nextTick(cb.bind(null, null, connection));
  } else {
    this._freeConnections.push(connection);
  }
};

Pool.prototype.end = function(cb) {
  this._closed = true;
  var self              = this;
  var closedConnections = 0;
  var calledBack        = false;
  var endCB = function(err) {
    if (calledBack) {
      return;
    } else if (err) {
      calledBack = true;
      delete endCB;
      if (cb) cb(err);
    } else if (++closedConnections >= self._allConnections.length) {
      calledBack = true;
      delete endCB;
      if (cb) cb();
    }
  };

  if (this._allConnections.length == 0) {
    endCB();
    return;
  }

  for (var i = 0; i < this._allConnections.length; ++i) {
    var connection = this._allConnections[i];
    connection.destroy = connection._realDestroy;
    connection.end     = connection._realEnd;
    connection.end(endCB);
  }
};

Pool.prototype._createConnection = function() {
  var self = this;
  var connection = (this.config.createConnection)
    ? this.config.createConnection(this.config.connectionConfig)
    : Mysql.createConnection(this.config.connectionConfig);

  connection._realEnd = connection.end;
  connection.end      = function(cb) {
    self.releaseConnection(connection);
    if (cb) cb();
  };
  connection._realDestroy = connection.destroy;
  connection.destroy      = function() {
    for (var i = 0; i < self._allConnections.length; ++i) {
      if (self._allConnections[i] === this) {
        self._allConnections.splice(i, 1);
        break;
      }
    }
    this.end     = this._realEnd;
    this.destroy = this._realDestroy;
    this.destroy();
  };
  return connection;
};
<commit_msg>Added handling for connection disconnects.<commit_after>
var Mysql        = require('../');
var Connection   = require('./Connection');

module.exports = Pool;
function Pool(options) {
  this.config = options.config;
  this.config.connectionConfig.pool = this;

  this._allConnections   = [];
  this._freeConnections  = [];
  this._connectionQueue  = [];
  this._closed           = false;
}

Pool.prototype.getConnection = function(cb) {
  if (this._closed) {
    cb(new Error('Pool is closed.'));
    return;
  }

  if (this._freeConnections.length > 0) {
    var connection = this._freeConnections[0];
    this._freeConnections.shift();
    cb(null, connection);
  } else if (this.config.connectionLimit == 0 || this._allConnections.length < this.config.connectionLimit) {
    var self       = this;
    var connection = this._createConnection();
    this._allConnections.push(connection);
    connection.connect(function(err) {
      if (self._closed) {
        cb(new Error('Pool is closed.'));
      }
      else if (err) {
        cb(err);
      } else {
        cb(null, connection);
      }
    });
  } else if (this.config.waitForConnections) {
    this._connectionQueue.push(cb);
  } else {
    cb(new Error('No connections available.'));
  }
};

Pool.prototype.releaseConnection = function(connection) {
  if (connection._poolRemoved) {
    // The connection has been removed from the pool and is no longer good.
    if (this._connectionQueue.length) {
      var cb = this._connectionQueue[0];
      this._connectionQueue.shift();
      process.nextTick(this.getConnection.bind(this, cb));
    }
  } else if (this._connectionQueue.length) {
    var cb = this._connectionQueue[0];
    this._connectionQueue.shift();
    process.nextTick(cb.bind(null, null, connection));
  } else {
    this._freeConnections.push(connection);
  }
};

Pool.prototype.end = function(cb) {
  this._closed = true;
  cb = cb || function(err) { if( err ) throw err; };
  var self              = this;
  var closedConnections = 0;
  var calledBack        = false;
  var endCB = function(err) {
    if (calledBack) {
      return;
    } else if (err) {
      calledBack = true;
      delete endCB;
      cb(err);
    } else if (++closedConnections >= self._allConnections.length) {
      calledBack = true;
      delete endCB;
      cb();
    }
  };

  if (this._allConnections.length == 0) {
    endCB();
    return;
  }

  for (var i = 0; i < this._allConnections.length; ++i) {
    var connection = this._allConnections[i];
    connection.destroy = connection._realDestroy;
    connection.end     = connection._realEnd;
    connection.end(endCB);
  }
};

Pool.prototype._createConnection = function() {
  var self = this;
  var connection = (this.config.createConnection)
    ? this.config.createConnection(this.config.connectionConfig)
    : Mysql.createConnection(this.config.connectionConfig);

  connection._realEnd = connection.end;
  connection.end      = function(cb) {
    self.releaseConnection(connection);
    if (cb) cb();
  };

  connection._realDestroy = connection.destroy;
  connection.destroy      = function() {
    self._removeConnection(connection);
    connection.destroy();
  };

  // When a fatal error occurs the connection's protocol ends, which will cause
  // the connection to end as well, thus we only need to watch for the end event
  // and we will be notified of disconnects.
  connection.on('end', this._handleConnectionEnd.bind(this, connection));

  return connection;
};

Pool.prototype._handleConnectionEnd = function(connection) {
  if (this._closed || connection._poolRemoved) {
    return;
  }
  this._removeConnection(connection);
};

Pool.prototype._removeConnection = function(connection) {
  connection._poolRemoved = true;
  for (var i = 0; i < this._allConnections.length; ++i) {
    if (this._allConnections[i] === connection) {
      this._allConnections.splice(i, 1);
      break;
    }
  }
  for (var i = 0; i < this._freeConnections.length; ++i) {
    if (this._freeConnections[i] === connection) {
      this._freeConnections.splice(i, 1);
      break;
    }
  }

  connection.end     = connection._realEnd;
  connection.destroy = connection._realDestroy;
  this.releaseConnection(connection);
};
<|endoftext|>


<commit_before>var util = require('util')
  , EventEmitter = require('events').EventEmitter
  , domain = require('domain')
  , Date = global.Date
  , setTimeout = global.setTimeout
  , clearTimeout = global.clearTimeout

/**
 * Contains a test function
 * A test can be a beforeEach/All, afterEach/All, or an actual test
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 * @param {String} [type=Test.TYPE.NORMAL] The type of test
 *
 * @constructor
 */
var Test = function (title, func, type) {
    Test.super_.call(this)

    this.id = undefined
    this.title = title
    this.func = func
    this.exclusivity = undefined
    this.parent = undefined
    this.async = func && func.length
    this.state = Test.STATE.WAITING
    this.result = undefined
    this.error = undefined
    this.type = type || Test.TYPE.NORMAL

    this._skip = !func
    this._only = false
    this._timeout = undefined

    this.priorTests = []
    this.nextTests = []
    this._completedPriors = 0
    this._totalPriors = 0

    this.dependencies = []

    this.setMaxListeners(0)
}

util.inherits(Test, EventEmitter)
module.exports = Test

//Constants for test types
module.exports.TYPE = {
    NORMAL: 'normal'
  , BEFORE_EACH: 'beforeEach'
  , AFTER_EACH: 'afterEach'
  , BEFORE_ALL: 'beforeAll'
  , AFTER_ALL: 'afterAll'
  , ROOT: 'root'
}

//Constants for test run states
module.exports.STATE = {
    WAITING: 0
  , RUNNING: 1
  , COMPLETED: 2
}

//Constants for test results
module.exports.RESULT = {
    SUCCESS: 1
  , FAILURE: 2
  , TIMEOUT: 3
  , HOOK_FAILURE: 4
  , SKIPPED: 5
}

//Constants for test exclusivity
module.exports.EXCLUSIVITY = {
    NONE: 0
  , LOCAL: 1
  , GLOBAL: 2
}

/**
 * Gets the full title of this test
 *
 * @param {String} [separator=' '] A separator to use between parents
 *
 * @returns {String} The full title
 */
Test.prototype.fullTitle = function (separator) {
    separator = separator || ' '
    var title = (this.parent) ? this.parent.fullTitle(separator) + separator : ''
    title += (this.title) ? this.title : ''
    return title
}

/**
 * Getter/setter timeout in milliseconds
 *
 * @param {Number} ms Number of milliseconds to wait before marking timing out
 *
 * @returns {Test|Number} This for chaining if setting, the timeout in ms if getting
 */
Test.prototype.timeout = function (ms) {
    if (!arguments.length) {
        return this._timeout
    }

    this._timeout = ms
    return this
}

/**
 * Getter/setter marks this test to be skipped
 *
 * @returns {Test|Boolean} This for chaining if setting, whether or not this is test will be skipped if getting
 */
Test.prototype.skip = function (skip) {
    if (!arguments.length) {
        return this._skip
    }

    this._skip = skip
    return this
}

/**
 * Getter/setter Marks this test as the only thing that should run
 * All suites/tests marked as only will run, everything else will show as skipped
 *
 * @returns {Test|Boolean} This for chaining if setting, whether or not this is an only test if getting
 */
Test.prototype.only = function (only) {
    if (!arguments.length) {
        return this._only
    }

    this._only = only
    return this
}

/**
 * No other tests can run during a globally exclusive test
 *
 * @returns {Test} This for chaining
 */
Test.prototype.globallyExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.GLOBAL
    return this
}

/**
 * No sibling tests can run during a locally exclusive test
 *
 * @returns {Test} This for chaining
 */
Test.prototype.locallyExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.LOCAL
    return this
}

/**
 * Any other sibling test that is also a non exclusive test can run together
 *
 * @returns {Test} This for chaining
 */
Test.prototype.nonExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.NONE
    return this
}

/**
 * Adds other tests as a prior for this test to run
 *
 * @param {Array.<Test>} priors An array of tests
 */
Test.prototype.addPriorTests = function (priors) {
    var self = this

    if (!priors) {
        return
    }

    priors = (Array.isArray(priors)) ? priors : [priors]

    priors.forEach(function (test) {
        if (self.priorTests.hasOwnProperty(test.id)) {
            return
        }

        test.nextTests.push(self)
        self.priorTests[test.id] = test
        self._totalPriors++

        test.once('finish', function () {
            self._completedPriors++
            self.run()
        })
    })
}

/**
 * Sets an array of tests as dependencies for this test to succeed
 * Used to cascade hook failures
 *
 * @param {Array.<Test>} dependencies An array of tests
 */
Test.prototype.addDependencies = function (dependencies) {
    if (!dependencies) {
        return
    }

    dependencies = (Array.isArray(dependencies)) ? dependencies : [dependencies]
    this.dependencies = this.dependencies.concat(dependencies)
}

/**
 * Clears the timeout timer
 */
Test.prototype.clearTimeout = function () {
    clearTimeout(this.timer)
}

/**
 * Resets the timeout timer
 */
Test.prototype.resetTimeout = function () {
    var self = this

    self.clearTimeout()
    if (this._timeout) {
        this.timer = setTimeout(function () {
            self.complete(new Error('Timeout of ' + self._timeout + 'ms exceeded'), Test.RESULT.TIMEOUT)
        }, self._timeout)
    }
}

/**
 * Clones the test
 * Title, function, timeout, skip, only, exclusivity, and parent is shallow copied over
 *
 * @param {Suite} [parent] A parent to use instead of the origins parent
 *
 * @returns {Test} The cloned test
 */
Test.prototype.clone = function (parent) {
    var test = new Test(this.title, this.func)

    test.timeout(this.timeout())
    test.skip(this._skip)
    test.only(this._only)
    test.exclusivity = this.exclusivity
    test.parent = parent || this.parent
    test.type = this.type

    return test
}

/**
 * Executes the test if all prior tests have completed
 */
Test.prototype.run = function () {
    if (this._completedPriors < this._totalPriors) {
        return
    }

    var self = this
      , context = self.parent.context()

    self.dependencies.some(function (test) {
        if (test.result === Test.RESULT.FAILURE || test.result === Test.RESULT.TIMEOUT) {
            //TODO: Better errors, which hook type?
            self.complete(new Error(test.fullTitle('/') + ' dependency failed'), Test.RESULT.HOOK_FAILURE)
            return true
        }
    })

    if (self.state !== Test.STATE.WAITING) {
        return
    }

    self.domain = domain.create()
    self.startTime = Date.now()

    if (self._skip) {
        return self.complete(undefined, Test.RESULT.SKIPPED)
    }

    self.domain.on('error', function (error) {
        self.complete(error)
    })

    self.emit('start')

    self.domain.run(function () {
        process.nextTick(function () {
            self.resetTimeout()
            self.state = Test.STATE.RUNNING

            try {
                var returnValue = self.func.call(
                    context
                  , function (error) {
                        self.complete(error)
                    }
                )
            } catch (error) {
                return self.complete(error)
            }

            if (self._handlePromise(returnValue)) {
                return
            }

            if (returnValue instanceof Error) {
                return self.complete(returnValue)
            } else if (!self.async) {
                return self.complete()
            }
        })
    })
}

/**
 * Completes the test
 *
 * @param {*} [error] An error, if there was one, to record for the test
 * @param {Number} [result] An override to the normal result value. Useful for timeout, skip, etc
 */
Test.prototype.complete = function (error, result) {
    var self = this

    if (self.state === Test.STATE.COMPLETED) {
        if (self.result === Test.RESULT.TIMEOUT) {
            return
        }

        //TODO: If we had an error already probably don't want to wipe it out
        self.error = error || new Error('done() called multiple times')
        self.result = Test.RESULT.FAILURE
        return
    }

    if (error) {
        if (error instanceof Error === false) {
            error = new Error('done() invoked with non-Error: ' + JSON.stringify(error))
        }

        self.error = error
        result = result || Test.RESULT.FAILURE
    } else {
        result = result || Test.RESULT.SUCCESS
    }

    self.result = result
    self.state = Test.STATE.COMPLETED
    self.clearTimeout()
    self.duration = (new Date - this.startTime) || 0

    if (self.domain) {
        self.domain.dispose()
    }

    self.emit('finish')
}

/**
 * Checks the return value from running the test for a promise and handles it if necessary
 *
 * @param {*} returnValue The return value of the test function
 *
 * @returns {boolean} True if the tests return value was a promise, false if not
 *
 * @private
 */
Test.prototype._handlePromise = function (returnValue) {
    var self = this

    if (typeof returnValue !== 'object' || returnValue === null || typeof returnValue.then !== 'function') {
        return false
    }

    returnValue.then(
        function () {
            self.complete()
        }
      , function (reason) {
            if (reason === null || reason === undefined) {
                reason = new Error('Promise rejected with no rejection reason.')
            }

            self.complete(reason)
        }
    )

    return true
}

/**
 * Helper to create a Root test
 * Root tests are used to tie in multiple tests
 *
 * @param {String} title Test title
 *
 * @returns {Test} The Root test
 */
Test.newRoot = function (title) {
    return new Test(title, function () {}, Test.TYPE.ROOT)
}

/**
 * Helper to create a beforeEach
 * beforeEachs are run before every test within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the beforeEach test
 */
Test.newBeforeEach = function (title, func) {
    return new Test(title, func, Test.TYPE.BEFORE_EACH)
}

/**
 * Helper to create a beforeAll
 * beforeAll are run before all tests within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the beforeAll test
 */
Test.newBeforeAll = function (title, func) {
    return new Test(title, func, Test.TYPE.BEFORE_ALL)
}

/**
 * Helper to create a afterEach
 * afterEach are run after every test within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the afterEach test
 */
Test.newAfterEach = function (title, func) {
    return new Test(title, func, Test.TYPE.AFTER_EACH)
}

/**
 * Helper to create an afterAll
 * afteralls are run after all tests within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the afterAll test
 */
Test.newAfterAll = function (title, func) {
    return new Test(title, func, Test.TYPE.AFTER_ALL)
}
<commit_msg>Fixing nextTick issue in node 0.8 when using domains<commit_after>var util = require('util')
  , EventEmitter = require('events').EventEmitter
  , domain = require('domain')
  , Date = global.Date
  , setTimeout = global.setTimeout
  , clearTimeout = global.clearTimeout

/**
 * Contains a test function
 * A test can be a beforeEach/All, afterEach/All, or an actual test
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 * @param {String} [type=Test.TYPE.NORMAL] The type of test
 *
 * @constructor
 */
var Test = function (title, func, type) {
    Test.super_.call(this)

    this.id = undefined
    this.title = title
    this.func = func
    this.exclusivity = undefined
    this.parent = undefined
    this.async = func && func.length
    this.state = Test.STATE.WAITING
    this.result = undefined
    this.error = undefined
    this.type = type || Test.TYPE.NORMAL

    this._skip = !func
    this._only = false
    this._timeout = undefined

    this.priorTests = []
    this.nextTests = []
    this._completedPriors = 0
    this._totalPriors = 0

    this.dependencies = []

    this.setMaxListeners(0)
}

util.inherits(Test, EventEmitter)
module.exports = Test

//Constants for test types
module.exports.TYPE = {
    NORMAL: 'normal'
  , BEFORE_EACH: 'beforeEach'
  , AFTER_EACH: 'afterEach'
  , BEFORE_ALL: 'beforeAll'
  , AFTER_ALL: 'afterAll'
  , ROOT: 'root'
}

//Constants for test run states
module.exports.STATE = {
    WAITING: 0
  , RUNNING: 1
  , COMPLETED: 2
}

//Constants for test results
module.exports.RESULT = {
    SUCCESS: 1
  , FAILURE: 2
  , TIMEOUT: 3
  , HOOK_FAILURE: 4
  , SKIPPED: 5
}

//Constants for test exclusivity
module.exports.EXCLUSIVITY = {
    NONE: 0
  , LOCAL: 1
  , GLOBAL: 2
}

/**
 * Gets the full title of this test
 *
 * @param {String} [separator=' '] A separator to use between parents
 *
 * @returns {String} The full title
 */
Test.prototype.fullTitle = function (separator) {
    separator = separator || ' '
    var title = (this.parent) ? this.parent.fullTitle(separator) + separator : ''
    title += (this.title) ? this.title : ''
    return title
}

/**
 * Getter/setter timeout in milliseconds
 *
 * @param {Number} ms Number of milliseconds to wait before marking timing out
 *
 * @returns {Test|Number} This for chaining if setting, the timeout in ms if getting
 */
Test.prototype.timeout = function (ms) {
    if (!arguments.length) {
        return this._timeout
    }

    this._timeout = ms
    return this
}

/**
 * Getter/setter marks this test to be skipped
 *
 * @returns {Test|Boolean} This for chaining if setting, whether or not this is test will be skipped if getting
 */
Test.prototype.skip = function (skip) {
    if (!arguments.length) {
        return this._skip
    }

    this._skip = skip
    return this
}

/**
 * Getter/setter Marks this test as the only thing that should run
 * All suites/tests marked as only will run, everything else will show as skipped
 *
 * @returns {Test|Boolean} This for chaining if setting, whether or not this is an only test if getting
 */
Test.prototype.only = function (only) {
    if (!arguments.length) {
        return this._only
    }

    this._only = only
    return this
}

/**
 * No other tests can run during a globally exclusive test
 *
 * @returns {Test} This for chaining
 */
Test.prototype.globallyExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.GLOBAL
    return this
}

/**
 * No sibling tests can run during a locally exclusive test
 *
 * @returns {Test} This for chaining
 */
Test.prototype.locallyExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.LOCAL
    return this
}

/**
 * Any other sibling test that is also a non exclusive test can run together
 *
 * @returns {Test} This for chaining
 */
Test.prototype.nonExclusive = function () {
    this.exclusivity = Test.EXCLUSIVITY.NONE
    return this
}

/**
 * Adds other tests as a prior for this test to run
 *
 * @param {Array.<Test>} priors An array of tests
 */
Test.prototype.addPriorTests = function (priors) {
    var self = this

    if (!priors) {
        return
    }

    priors = (Array.isArray(priors)) ? priors : [priors]

    priors.forEach(function (test) {
        if (self.priorTests.hasOwnProperty(test.id)) {
            return
        }

        test.nextTests.push(self)
        self.priorTests[test.id] = test
        self._totalPriors++

        test.once('finish', function () {
            self._completedPriors++
            self.run()
        })
    })
}

/**
 * Sets an array of tests as dependencies for this test to succeed
 * Used to cascade hook failures
 *
 * @param {Array.<Test>} dependencies An array of tests
 */
Test.prototype.addDependencies = function (dependencies) {
    if (!dependencies) {
        return
    }

    dependencies = (Array.isArray(dependencies)) ? dependencies : [dependencies]
    this.dependencies = this.dependencies.concat(dependencies)
}

/**
 * Clears the timeout timer
 */
Test.prototype.clearTimeout = function () {
    clearTimeout(this.timer)
}

/**
 * Resets the timeout timer
 */
Test.prototype.resetTimeout = function () {
    var self = this

    self.clearTimeout()
    if (this._timeout) {
        this.timer = setTimeout(function () {
            self.complete(new Error('Timeout of ' + self._timeout + 'ms exceeded'), Test.RESULT.TIMEOUT)
        }, self._timeout)
    }
}

/**
 * Clones the test
 * Title, function, timeout, skip, only, exclusivity, and parent is shallow copied over
 *
 * @param {Suite} [parent] A parent to use instead of the origins parent
 *
 * @returns {Test} The cloned test
 */
Test.prototype.clone = function (parent) {
    var test = new Test(this.title, this.func)

    test.timeout(this.timeout())
    test.skip(this._skip)
    test.only(this._only)
    test.exclusivity = this.exclusivity
    test.parent = parent || this.parent
    test.type = this.type

    return test
}

/**
 * Executes the test if all prior tests have completed
 */
Test.prototype.run = function () {
    if (this._completedPriors < this._totalPriors) {
        return
    }

    var self = this
      , context = self.parent.context()

    self.dependencies.some(function (test) {
        if (test.result === Test.RESULT.FAILURE || test.result === Test.RESULT.TIMEOUT) {
            //TODO: Better errors, which hook type?
            self.complete(new Error(test.fullTitle('/') + ' dependency failed'), Test.RESULT.HOOK_FAILURE)
            return true
        }
    })

    if (self.state !== Test.STATE.WAITING) {
        return
    }

    self.domain = domain.create()
    self.startTime = Date.now()

    if (self._skip) {
        return self.complete(undefined, Test.RESULT.SKIPPED)
    }

    self.domain.on('error', function (error) {
        self.complete(error)
    })

    self.emit('start')

    self.domain.run(function () {
        process.nextTick(function () {
            self.resetTimeout()
            self.state = Test.STATE.RUNNING

            try {
                var returnValue = self.func.call(
                    context
                  , function (error) {
                        self.complete(error)
                    }
                )
            } catch (error) {
                return self.complete(error)
            }

            if (self._handlePromise(returnValue)) {
                return
            }

            if (returnValue instanceof Error) {
                return self.complete(returnValue)
            } else if (!self.async) {
                return self.complete()
            }
        })
    })
}

/**
 * Completes the test
 *
 * @param {*} [error] An error, if there was one, to record for the test
 * @param {Number} [result] An override to the normal result value. Useful for timeout, skip, etc
 */
Test.prototype.complete = function (error, result) {
    var self = this

    if (self.state === Test.STATE.COMPLETED) {
        if (self.result === Test.RESULT.TIMEOUT) {
            return
        }

        //TODO: If we had an error already probably don't want to wipe it out
        self.error = error || new Error('done() called multiple times')
        self.result = Test.RESULT.FAILURE
        return
    }

    if (error) {
        if (error instanceof Error === false) {
            error = new Error('done() invoked with non-Error: ' + JSON.stringify(error))
        }

        self.error = error
        result = result || Test.RESULT.FAILURE
    } else {
        result = result || Test.RESULT.SUCCESS
    }

    self.result = result
    self.state = Test.STATE.COMPLETED
    self.clearTimeout()
    self.duration = (new Date - this.startTime) || 0

    //Disposing of the domain on the nextTick seems to fix some issues with nextTick in general
    //TODO: Figure out why this fixes nextTick issues in node 0.8 with domains
    process.nextTick(function () {
        if (self.domain) {
            self.domain.dispose()
        }

        self.emit('finish')
    })
}

/**
 * Checks the return value from running the test for a promise and handles it if necessary
 *
 * @param {*} returnValue The return value of the test function
 *
 * @returns {boolean} True if the tests return value was a promise, false if not
 *
 * @private
 */
Test.prototype._handlePromise = function (returnValue) {
    var self = this

    if (typeof returnValue !== 'object' || returnValue === null || typeof returnValue.then !== 'function') {
        return false
    }

    returnValue.then(
        function () {
            self.complete()
        }
      , function (reason) {
            if (reason === null || reason === undefined) {
                reason = new Error('Promise rejected with no rejection reason.')
            }

            self.complete(reason)
        }
    )

    return true
}

/**
 * Helper to create a Root test
 * Root tests are used to tie in multiple tests
 *
 * @param {String} title Test title
 *
 * @returns {Test} The Root test
 */
Test.newRoot = function (title) {
    return new Test(title, function () {}, Test.TYPE.ROOT)
}

/**
 * Helper to create a beforeEach
 * beforeEachs are run before every test within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the beforeEach test
 */
Test.newBeforeEach = function (title, func) {
    return new Test(title, func, Test.TYPE.BEFORE_EACH)
}

/**
 * Helper to create a beforeAll
 * beforeAll are run before all tests within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the beforeAll test
 */
Test.newBeforeAll = function (title, func) {
    return new Test(title, func, Test.TYPE.BEFORE_ALL)
}

/**
 * Helper to create a afterEach
 * afterEach are run after every test within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the afterEach test
 */
Test.newAfterEach = function (title, func) {
    return new Test(title, func, Test.TYPE.AFTER_EACH)
}

/**
 * Helper to create an afterAll
 * afteralls are run after all tests within a suites ancestry
 *
 * @param {String} title Title of the test
 * @param {function} func Function to execute for the test
 *
 * @returns {Test} the afterAll test
 */
Test.newAfterAll = function (title, func) {
    return new Test(title, func, Test.TYPE.AFTER_ALL)
}
<|endoftext|>


<commit_before>/*
 *  'nodejs + connectjs': HTTP communication with backend (remote web browser)
 */

(function get_extjs_remote_config(url){
var xhr = new XMLHttpRequest
    xhr.open(
        'GET'
        ,(url ? url : '') + '/app.config.extjs.json'
        ,true
    )
    xhr.onreadystatechange = function(res){
        var extjs_config
        if(4 == res.target.readyState){
            if(200 != res.target.status){
                throw new Error(xhr ? l10n.errload_config_read : l10n.extjsNotFound)
            } else {
                if(xhr){
                    extjs_config = JSON.parse(xhr.responseText)
                    if(url){// `nw` context
                        app.config.extjs.load = extjs_config.load
                        app.extjs_load(document, window)
                        return
                    }
                    // `browser` context
                    app.config = {
                        extjs: extjs_config,
                        backend:{// record start time
                            time: new Date,
                            msg: l10n.stsBackendXHR,
                            op: l10n.stsCheck
                        }
                    }
                    xhr.open(// check for network availability of ExtJS
                        'HEAD'
                       ,(url ? url : '') + app.config.extjs.path + 'ext-all-nw.js'
                       ,true
                    )
                    xhr.send()
                    xhr = null
                } else {
                    app.extjs_load(document, window)
                }
            }
        }
    }
    xhr.send()
})(app.config && app.config.backend.url)
<commit_msg>app_front_http: proper use of xhr var.; refactor handler<commit_after>/*
 *  'nodejs + connectjs': HTTP communication with backend (remote web browser)
 */

(function get_extjs_remote_config(url){
var xhr = new XMLHttpRequest
    xhr.open(
        'GET'
        ,(url ? url : '') + '/app.config.extjs.json'
        ,true
    )
    xhr.onreadystatechange = onreadystatechange
    xhr.send()
    return

    function onreadystatechange(res){
    var extjs_config, req = res.target
        if(4 == req.readyState){
            if(200 != req.status){
                throw new Error(xhr ? l10n.errload_config_read : l10n.extjsNotFound)
            } else {
                if(xhr){
                    xhr = null
                    extjs_config = JSON.parse(req.responseText)
                    if(url){// `nw` context
                        app.config.extjs.load = extjs_config.load
                        app.extjs_load(document, window)
                        return
                    }
                    // `browser` context
                    app.config = {
                        extjs: extjs_config,
                        backend:{// record start time
                            time: new Date,
                            msg: l10n.stsBackendXHR,
                            op: l10n.stsCheck
                        }
                    }
                    req.open(// check for network availability of ExtJS
                        'HEAD'
                       ,(url ? url : '') + app.config.extjs.path + 'ext-all-nw.js'
                       ,true
                    )
                    req.send()
                } else {
                    app.extjs_load(document, window)
                }
            }
        }
    }
})(app.config && app.config.backend.url)
<|endoftext|>


<commit_before>// Sample view

enyo.kind({
    name: "moon.sample.login.ContextualPopupSample",
    kind: "moon.Panel",
    layoutKind: "FittableColumnsLayout",
    titleAbove: "01",
    title: "Main Menu",
    components: [
        {kind: "enyo.Spotlight"},
        {
            name: "menuList",
            kind: "enyo.DataList",
            style: "width: 300px;",
            components: [
                {bindFrom: "menuItem", kind: "moon.Item", ontap: "changePanel"}
            ]
        },
        {
            name: "contentList",
            kind: "enyo.DataList",
            fit: true,
            components: [
                {kind: "moon.ImageItem", bindFrom: "imgSrc", bindTo: "source"}
            ]
        }
    ],
    bindings: [
        {from: ".controller.menus", to: "$.menuList.controller"},
        {from: ".controller.contents", to: "$.contentList.controller"}
    ]
 });

// Sample model

enyo.ready(function(){
    var sampleModel = new enyo.Model({
        menus: new enyo.Collection([
            {menuItem: "Browser Movies"},
            {menuItem: "Browser TV Shows"},
            {menuItem: "Queue"},
            {menuItem: "Search"}
        ]),
        contents: new enyo.Collection([
            {imgSrc: "assets/default-movie-vertical.png"},
            {imgSrc: "assets/default-movie-vertical.png"},
            {imgSrc: "assets/default-movie-vertical.png"},
            {imgSrc: "assets/default-movie-vertical.png"}
        ]),
    });

//  Application to render sample

    new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {
                    kind: "moon.sample.login.ContextualPopupSample",
                    controller: ".app.controllers.movieController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "movieController",
                kind: "enyo.ModelController",
                model: sampleModel,
                changePanel: function(inSender, inEvent) {
                    enyo.log("Item: " + inEvent.originator.parent.controller.model.get("menuItem"));
                }
            }
        ]
    });
});<commit_msg>GF-5509: Use video image, buttons.<commit_after>enyo.kind({
    name: "moon.MovieImageItem",
    classes: "moon-movie-list-item moon-3h moon-2v",
    spotlight: true,
    published: {
        option: {
            src: "",
            caption: ""
        }
    },
    handlers: {
        onSpotlightFocused: "focused",
        onSpotlightBlur: "released"
    },
    components: [
        {name: "caption", classes: "moon-movie-list-item-text"}
    ],
    create: function() {
        this.inherited(arguments);
        this.optionChanged();
    },
    getCaption: function() {
        return this.$.caption;
    },
    optionChanged: function(inOld) {
        this.applyStyle("background-image", 'url(' + this.option.src + ')');
        this.$.caption.setContent(this.option.caption);
    },
    focused: function() {
        this.$.caption.addClass("spotlight");
        return true;
    },
    released: function() {
        this.$.caption.removeClass("spotlight");
        return true;
    }
});

// Sample view

enyo.kind({
    name: "moon.sample.login.ContextualPopupSample",
    kind: "moon.Panel",
    layoutKind: "FittableColumnsLayout",
    titleAbove: "01",
    title: "Main Menu",
    components: [
        {kind: "enyo.Spotlight"},
        {
            name: "menuList",
            kind: "enyo.DataList",
            style: "width: 300px;",
            components: [
                {bindFrom: "menuItem", kind: "moon.Item", ontap: "changePanel"}
            ]
        },
        {
            fit: true,
            layoutkind: "FittableRowsLayout",
            components: [
            {
                name: "contentList",
                kind: "enyo.DataGridList",
                fit: true,
                components: [
                    {kind: "moon.MovieImageItem", bindFrom: "itemOption", bindTo: "option"}
                ]
            },
            {
                name: "buttonList",
                kind: "enyo.DataGridList",
                layoutkind: "FittableRowsLayout",
                components: [
                    {kind: "moon.IconButton", bindFrom: "iconSrc", bindTo: "src", bindFrom: "content"},
                    {kind: "moon.Button", bindFrom: "content", bindTo: "content"}
                ]
            }
        ]}

    ],
    bindings: [
        {from: ".controller.menus", to: "$.menuList.controller"},
        {from: ".controller.contents", to: "$.contentList.controller"},
        {from: ".controller.buttons", to: "$.buttonList.controller"}
    ]
 });

// Sample model

enyo.ready(function(){
    var sampleModel = new enyo.Model({
        menus: new enyo.Collection([
            {menuItem: "Browser Movies"},
            {menuItem: "Browser TV Shows"},
            {menuItem: "Queue"},
            {menuItem: "Search"}
        ]),
        contents: new enyo.Collection([
            {itemOption: {src: "assets/default-movie-vertical.png"}},
            {itemOption: {src: "assets/default-movie-vertical.png"}},
            {itemOption: {src: "assets/default-movie-vertical.png"}},
            {itemOption: {src: "assets/default-movie-vertical.png"}}
        ]),
        buttons: new enyo.Collection([
            {iconSrc: "assets/icon-like.png", content: "LIKE"},
            {iconSrc: "", content: "SHARE"}
        ])
    });

//  Application to render sample

    new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {
                    kind: "moon.sample.login.ContextualPopupSample",
                    controller: ".app.controllers.movieController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "movieController",
                kind: "enyo.ModelController",
                model: sampleModel,
                changePanel: function(inSender, inEvent) {
                    enyo.log("Item: " + inEvent.originator.parent.controller.model.get("menuItem"));
                }
            }
        ]
    });
});<|endoftext|>


<commit_before>/**
 * Created by Rain Summers on 06.11.2015.
 *
 * You can ask me on 'into.the.rainy.sky@gmail.com'
 * if you have any questions about this code.
 *
 */

/*  Pattern "Publish-Subscribe"
 API is identical to 'http://riotjs.com/api/observable/;

 el.on(event, callback);
 el.on(events, callback);
 el.one(event, callback)
 el.one(events, callback)
 el.off(events)
 el.off(events, fn)
 el.off(‘*’)
 el.off(‘*’, fn)
 el.trigger(events, arg1 … argN)
 el.trigger(event, arg1 … argN)
 */
var observable = (function () {
	"use strict";

	var eventContainer = {}, mixin;

	function observable( el ) {
		"use strict";
		for ( var key in mixin ) {
			//noinspection JSUnfilteredForInLoop
			el[key] = mixin[key];
		}
		return el;
	}

	mixin = {
		on      : observableOn,
		one     : observableOne,
		off     : observableOff,
		trigger : observableTrigger,
	};

	return observable;

	function observableOn( events, callback ) {
		"use strict";

		getEvents( events )
			.forEach( function ( event ) {
				eventContainer[event] || (eventContainer[event] = []);
				!~eventContainer[event].indexOf( callback ) && eventContainer[event].push( callback );
			} );

		return this;
	}

	function observableOne( events, callback ) {
		"use strict";
		var _callback = function () {
			callback.apply( this, arguments );
			observableOff( events, _callback );
		};

		observableOn( events, _callback );

		return this;
	}

	function observableOff( events, callback ) {
		"use strict";

		if ( events === '*' ) {
			if ( !callback ) {
				eventContainer = {};
				return this;
			}
			else {
				events = Object.keys( eventContainer ).join( ' ' );
			}
		}

		var args = [].slice.call( arguments, 1 ),
		    event;

		getEvents( events )
			.forEach( function ( event ) {
				if ( callback ) {
					var idx = eventContainer[event].indexOf( callback );
					~idx && eventContainer[event].splice( idx, 1 );
				}
				else {
					eventContainer[event] && delete eventContainer[event];
				}
			} );

		return this;
	}

	function observableTrigger( events ) {
		"use strict";
		var args = [].slice.call( arguments, 1 ),
		    event;

		events = events.split( /\W+/ );
		!events[events.length - 1] && (events.length--);

		events.forEach( function ( event ) {
			var eventHandlers = eventContainer[event];

			if ( !eventHandlers ) { return; }

			var idx = 0,
			    _args  = args.slice()
				;

			_args.unshift( event );

			while ( idx < eventHandlers.length ) {
				eventHandlers[idx].apply( null, _args );
				idx++;
			}
		} );

		return this;
	}

	function getEvents( events ) {
		events = events.split( /\W+/ );
		!events[events.length - 1] && (events.length--);
		return events;
	}
})();
<commit_msg>Pattern "Publish-Subscribe" updated<commit_after>/**
 * Created by Rain Summers on 06.11.2015.
 *
 * You can ask me on 'into.the.rainy.sky@gmail.com'
 * if you have any questions about this code.
 *
 */

/*  Pattern "Publish-Subscribe"
 API is identical to 'http://riotjs.com/api/observable/;

 el.on(event, callback);
 el.on(events, callback);
 el.one(event, callback)
 el.one(events, callback)
 el.off(events)
 el.off(events, fn)
 el.off(‘*’)
 el.off(‘*’, fn)
 el.trigger(events, arg1 … argN)
 el.trigger(event, arg1 … argN)
 */
var observable = (function () {
	"use strict";

	var eventContainer = {}, mixin;

	function observable( el ) {
		"use strict";
		for ( var key in mixin ) {
			//noinspection JSUnfilteredForInLoop
			el[key] = mixin[key];
		}
		return el;
	}

	mixin = {
		on      : observableOn,
		one     : observableOne,
		off     : observableOff,
		trigger : observableTrigger,
	};

	return observable;

	function observableOn( events, callback ) {
		"use strict";

		getEvents( events )
			.forEach( function ( event ) {
				eventContainer[event] || (eventContainer[event] = []);
				!~eventContainer[event].indexOf( callback ) && eventContainer[event].push( callback );
			} );

		return this;
	}

	function observableOne( events, callback ) {
		"use strict";
		var _callback = function () {
			callback.apply( this, arguments );
			observableOff( events, _callback );
		};

		observableOn( events, _callback );

		return this;
	}

	function observableOff( events, callback ) {
		"use strict";

		if ( events === '*' ) {
			if ( !callback ) {
				eventContainer = {};
				return this;
			}
			else {
				events = Object.keys( eventContainer ).join( ' ' );
			}
		}

		var args = [].slice.call( arguments, 1 ),
		    event;

		getEvents( events )
			.forEach( function ( event ) {
				if ( callback ) {
					var idx = eventContainer[event].indexOf( callback );
					~idx && eventContainer[event].splice( idx, 1 );
				}
				else {
					eventContainer[event] && delete eventContainer[event];
				}
			} );

		return this;
	}

	function observableTrigger( events ) {
		"use strict";
		var args = [].slice.call( arguments, 1 ),
		    event;

		getEvents( events )
			.forEach( function ( event ) {
				var eventHandlers = eventContainer[event];

				if ( !eventHandlers ) { return; }

				var idx   = 0,
				    _args = args.slice()
					;

				_args.unshift( event );

				while ( idx < eventHandlers.length ) {
					eventHandlers[idx].apply( null, _args );
					idx++;
				}
			} );

		return this;
	}

	function getEvents( events ) {
		events = events.split( /\W+/ );
		!events[events.length - 1] && (events.length--);
		return events;
	}
})();
<|endoftext|>


<commit_before>var builder = require('focus').component.builder;
var popinProperties = require('../mixin/popin-behaviour').mixin;
var type = require('focus').component.types;

/**
 * Popin mixin
 * @type {object}
 */
var popinMixin = {
  mixins: [popinProperties],
  /**
   * Display name.
   */
  displayName: 'popin',

  /** @inheritdoc */
  getDefaultProps: function () {
    return {
      type: 'full', // full, centered
      //  displaySelector: undefined, // Html selector of the element wich open/close the modal when click on it.
      contentLoadingFunction: undefined // Function wich returns the content of the modal.
    };
  },

  /** @inheritdoc */
  propTypes: {
    type: type('string'),
    contentLoadingFunction: type('string')
  },

  /** @inheritdoc */
  componentDidMount: function popinDidMount() {
    var source = document.querySelector(this.props.displaySelector);
    var currentView = this;
    if(source !== undefined && source !== null){
      source.onclick = function () {
        currentView.setState({open: !currentView.state.open});
      };
    }

  },

  /**
   * Open the modal.
   */
  openModal: function openModal() {
    this.setState({open: true});
  },
  /**
   * Close the modal
   */
  closeModal: function closeModal() {
    this.setState({open: false});
    document.querySelector('#modal-layer').classList.remove('popin-layer');
  },

  /**
   * Css class of modal.
   * @returns {string} css classes.
   * @private
   */
  _getModalCss: function () {
    var cssClass = 'popin animated ';
    switch (this.props.position) {
      case 'right':
        cssClass = `${cssClass} fadeInRight right btn-close-left`;
        break;
      case 'left':
        cssClass = `${cssClass} fadeInLeft left btn-close-right`;
        break;
      case 'down':
        cssClass = `${cssClass} fadeInDown down btn-close-left`;
        break;
      case 'up':
        cssClass = `${cssClass} fadeInUp up btn-close-left`;
        break;
    }
    if (this.props.style.className !== undefined && this.props.style.className !== null) {
      cssClass = `${cssClass}  ${this.props.style.className}`;
    }
    return cssClass;
  },

  /**
   * Css class of close btn.
   * @returns {string} - css classes.
   * @private
   */
  _getCloseBtnCss: function () {
    var cssClass = 'popin-close-btn';
    switch (this.props.position) {
      case 'right':
        cssClass += ' left';
        break;
      default :
        cssClass += ' right';
    }
    return cssClass;
  },
  /**
   * Content css class.
   * @returns {string} - css classes.
   * @private
   */
  _getModalContentCss: function () {
    var cssClass = 'modal-content';
    switch (this.props.type) {
      case 'full':
        cssClass += ' full';
        break;
      case 'centered':
        cssClass += ' centered';
        break;
    }
    return cssClass;
  },

  /**
   * Render the component.
   * @returns {JSX} - Html code.
   */
  render: function renderPopin() {
    if (!this.state.open) {
      return
;
    }
    return (

                    {this.renderPopinHeader(this)}

                        {this.renderContent(this)}

                    {this.renderPopinFooter(this)}

    );
  }
};

module.exports = builder(popinMixin);
<commit_msg>update popin css.<commit_after>var builder = require('focus').component.builder;
var popinProperties = require('../mixin/popin-behaviour').mixin;
var type = require('focus').component.types;

/**
 * Popin mixin
 * @type {object}
 */
var popinMixin = {
  mixins: [popinProperties],
  /**
   * Display name.
   */
  displayName: 'popin',

  /** @inheritdoc */
  getDefaultProps: function () {
    return {
      type: 'full', // full, centered
      //  displaySelector: undefined, // Html selector of the element wich open/close the modal when click on it.
      contentLoadingFunction: undefined // Function wich returns the content of the modal.
    };
  },

  /** @inheritdoc */
  propTypes: {
    type: type('string'),
    contentLoadingFunction: type('string')
  },

  /** @inheritdoc */
  componentDidMount: function popinDidMount() {
    var source = document.querySelector(this.props.displaySelector);
    var currentView = this;
    if(source !== undefined && source !== null){
      source.onclick = function () {
        currentView.setState({open: !currentView.state.open});
      };
    }

  },

  /**
   * Open the modal.
   */
  openModal: function openModal() {
    this.setState({open: true});
  },
  /**
   * Close the modal
   */
  closeModal: function closeModal() {
    this.setState({open: false});
    document.querySelector('#modal-layer').classList.remove('popin-layer');
  },

  /**
   * Css class of modal.
   * @returns {string} css classes.
   * @private
   */
  _getModalCss: function () {
    var cssClass = 'popin animated ';
    switch (this.props.position) {
      case 'right':
        cssClass = `${cssClass} fadeInRightBig right btn-close-left`;
        break;
      case 'left':
        cssClass = `${cssClass} fadeInLeftBig left btn-close-right`;
        break;
      case 'down':
        cssClass = `${cssClass} fadeInDownBig down btn-close-left`;
        break;
      case 'up':
        cssClass = `${cssClass} fadeInUpBig up btn-close-left`;
        break;
    }
    if (this.props.style.className !== undefined && this.props.style.className !== null) {
      cssClass = `${cssClass}  ${this.props.style.className}`;
    }
    return cssClass;
  },

  /**
   * Css class of close btn.
   * @returns {string} - css classes.
   * @private
   */
  _getCloseBtnCss: function () {
    var cssClass = 'popin-close-btn';
    switch (this.props.position) {
      case 'right':
        cssClass += ' left';
        break;
      default :
        cssClass += ' right';
    }
    return cssClass;
  },
  /**
   * Content css class.
   * @returns {string} - css classes.
   * @private
   */
  _getModalContentCss: function () {
    var cssClass = 'modal-content';
    switch (this.props.type) {
      case 'full':
        cssClass += ' full';
        break;
      case 'centered':
        cssClass += ' centered';
        break;
    }
    return cssClass;
  },

  /**
   * Render the component.
   * @returns {JSX} - Html code.
   */
  render: function renderPopin() {
    if (!this.state.open) {
      return
;
    }
    return (

                    {this.renderPopinHeader(this)}

                        {this.renderContent(this)}

                    {this.renderPopinFooter(this)}

    );
  }
};

module.exports = builder(popinMixin);
<|endoftext|>