<commit_before>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.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.
 */

var argv = require('minimist')(process.argv.slice(2));
var BBPromise = require('bluebird');
var chalk = require('chalk');
var fs = require('fs-extra');
var gulp = require('gulp-help')(require('gulp'));
var markdownLinkCheck = BBPromise.promisify(require('markdown-link-check'));
var path = require('path');
var util = require('gulp-util');

/**
 * Parses the list of files in argv and checks for dead links.
 *
 * @return {Promise} Used to wait until all async link checkers finish.
 */
function checkLinks() {
  var files = argv.files;
  if (!files) {
    util.log(util.colors.red(
        'Error: A list of markdown files must be specified via --files'));
    process.exit(1);
  }

  var markdownFiles = files.split(',');
  var linkCheckers = markdownFiles.map(function(markdownFile) {
    return runLinkChecker(markdownFile);
  });
  return BBPromise.all(linkCheckers)
  .then(function(allResults) {
    var deadLinksFound = false;
    var filesWithDeadLinks = [];
    allResults.map(function(results, index) {
      var deadLinksFoundInFile = false;
      util.log(
          'Checking links in',
          util.colors.magenta(markdownFiles[index]), '...');
      results.forEach(function (result) {
        if(result.status === 'dead') {
          deadLinksFound = true;
          deadLinksFoundInFile = true;
          util.log('[%s] %s', chalk.red('✖'), result.link);
        } else {
          util.log('[%s] %s', chalk.green('✓'), result.link);
        }
      });
      if(deadLinksFoundInFile) {
        filesWithDeadLinks.push(markdownFiles[index]);
        util.log(
            util.colors.yellow('WARNING'),
            'Possible dead link(s) found in',
            util.colors.magenta(markdownFiles[index]),
            '(please update if necessary).');
      } else {
        util.log(
            util.colors.green('SUCCESS'),
            'All links in',
            util.colors.magenta(markdownFiles[index]), 'are alive.');
      }
    });
    if (deadLinksFound) {
        util.log(
            util.colors.yellow('WARNING'),
            'Possible dead link(s) found. Please update',
            util.colors.magenta(filesWithDeadLinks.join(',')),
            'if necessary.');
    } else {
        util.log(
            util.colors.green('SUCCESS'),
            'All links in all markdown files in this PR are alive.');
    }
  });
}

/**
 * Filters out markdown elements that contain localhost links.
 * TODO(rsimha-amp): Simplify this into a single regex.
 *
 * @param {string} markdown Original markdown.
 * @return {string} Markdown after filtering out localhost links.
 */
function filterLocalhostLinks(markdown) {
  var localhostPattern = 'http:\/\/localhost:8000';
  var parenLinks = new RegExp('\\('+ localhostPattern + '[^\\)]*\\)', 'g');
  var bracketLinks = new RegExp('\\['+ localhostPattern + '[^\\]]*\\]', 'g');
  var rawLinks = new RegExp(localhostPattern, 'g');

  var filteredMarkdown = markdown;
  filteredMarkdown = filteredMarkdown.replace(parenLinks, '');
  filteredMarkdown = filteredMarkdown.replace(bracketLinks, '');
  filteredMarkdown = filteredMarkdown.replace(rawLinks, '');
  return filteredMarkdown;
}

/**
 * Reads the raw contents in the given markdown file, filters out localhost
 * links (because they do not resolve on Travis), and checks for dead links.
 *
 * @param {string} markdownFile Path of markdown file, relative to src root.
 * @return {Promise} Used to wait until the async link checker is done.
 */
function runLinkChecker(markdownFile) {
  var markdown = fs.readFileSync(markdownFile).toString();
  var filteredMarkdown = filterLocalhostLinks(markdown);
  var opts = {
    baseUrl : 'file://' + path.dirname(path.resolve((markdownFile)))
  };
  return markdownLinkCheck(filteredMarkdown, opts);
}

gulp.task(
    'check-links',
    'Detects dead links in markdown files',
    checkLinks, {
    options: {
      'files': '  CSV list of files in which to check links'
    }
});
<commit_msg>Hide checks for working links from Travis logs (#9344)<commit_after>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.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.
 */

var argv = require('minimist')(process.argv.slice(2));
var BBPromise = require('bluebird');
var chalk = require('chalk');
var fs = require('fs-extra');
var gulp = require('gulp-help')(require('gulp'));
var markdownLinkCheck = BBPromise.promisify(require('markdown-link-check'));
var path = require('path');
var util = require('gulp-util');

/**
 * Parses the list of files in argv and checks for dead links.
 *
 * @return {Promise} Used to wait until all async link checkers finish.
 */
function checkLinks() {
  var files = argv.files;
  if (!files) {
    util.log(util.colors.red(
        'Error: A list of markdown files must be specified via --files'));
    process.exit(1);
  }

  var markdownFiles = files.split(',');
  var linkCheckers = markdownFiles.map(function(markdownFile) {
    return runLinkChecker(markdownFile);
  });
  return BBPromise.all(linkCheckers)
  .then(function(allResults) {
    var deadLinksFound = false;
    var filesWithDeadLinks = [];
    allResults.map(function(results, index) {
      var deadLinksFoundInFile = false;
      util.log(
          'Checking links in',
          util.colors.magenta(markdownFiles[index]), '...');
      results.forEach(function (result) {
        if(result.status === 'dead') {
          deadLinksFound = true;
          deadLinksFoundInFile = true;
          util.log('[%s] %s', chalk.red('✖'), result.link);
        }
      });
      if(deadLinksFoundInFile) {
        filesWithDeadLinks.push(markdownFiles[index]);
        util.log(
            util.colors.yellow('WARNING'),
            'Possible dead link(s) found in',
            util.colors.magenta(markdownFiles[index]),
            '(please update if necessary).');
      } else {
        util.log(
            util.colors.green('SUCCESS'),
            'All links in',
            util.colors.magenta(markdownFiles[index]), 'are alive.');
      }
    });
    if (deadLinksFound) {
        util.log(
            util.colors.yellow('WARNING'),
            'Possible dead link(s) found. Please update',
            util.colors.magenta(filesWithDeadLinks.join(',')),
            'if necessary.');
    } else {
        util.log(
            util.colors.green('SUCCESS'),
            'All links in all markdown files in this PR are alive.');
    }
  });
}

/**
 * Filters out markdown elements that contain localhost links.
 * TODO(rsimha-amp): Simplify this into a single regex.
 *
 * @param {string} markdown Original markdown.
 * @return {string} Markdown after filtering out localhost links.
 */
function filterLocalhostLinks(markdown) {
  var localhostPattern = 'http:\/\/localhost:8000';
  var parenLinks = new RegExp('\\('+ localhostPattern + '[^\\)]*\\)', 'g');
  var bracketLinks = new RegExp('\\['+ localhostPattern + '[^\\]]*\\]', 'g');
  var rawLinks = new RegExp(localhostPattern, 'g');

  var filteredMarkdown = markdown;
  filteredMarkdown = filteredMarkdown.replace(parenLinks, '');
  filteredMarkdown = filteredMarkdown.replace(bracketLinks, '');
  filteredMarkdown = filteredMarkdown.replace(rawLinks, '');
  return filteredMarkdown;
}

/**
 * Reads the raw contents in the given markdown file, filters out localhost
 * links (because they do not resolve on Travis), and checks for dead links.
 *
 * @param {string} markdownFile Path of markdown file, relative to src root.
 * @return {Promise} Used to wait until the async link checker is done.
 */
function runLinkChecker(markdownFile) {
  var markdown = fs.readFileSync(markdownFile).toString();
  var filteredMarkdown = filterLocalhostLinks(markdown);
  var opts = {
    baseUrl : 'file://' + path.dirname(path.resolve((markdownFile)))
  };
  return markdownLinkCheck(filteredMarkdown, opts);
}

gulp.task(
    'check-links',
    'Detects dead links in markdown files',
    checkLinks, {
    options: {
      'files': '  CSV list of files in which to check links'
    }
});
<|endoftext|>


<commit_before>import Component from '@ember/component';
import { scheduleOnce } from '@ember/runloop';
import { computed } from '@ember/object';
import { typeOf } from '@ember/utils';
import layout from '../templates/components/ember-kalendae';

export default Component.extend({
  layout,

  mode: 'single',
  months: '1',
  weekStart: '0',
  direction: 'any',
  directionScrolling: true,
  blackout: null,
  viewStartDate: null,
  dateClassMap: {},
  dayOutOfMonthClickable: false,
  dayHeaderClickable: false,
  useYearNav: true,
  side: 'bottom',
  columnHeaderFormat: 'dd',
  titleFormat: 'MMMM, YYYY',
  dayNumberFormat: 'D',
  dayAttributeFormat: 'YYYY-MM-DD',
  parseSplitDelimiter: /,\s*|\s*-\s*/,
  rangeDelimiter: ' - ',
  multipleDelimiter: ', ',
  selected: new Date(),

  /**
   * Returns current kalendae instance
   *
   * @property kalendae
   * @type Object | undefined Returns undefined if kalendae not found
   */
  kalendae: computed({
    get() {
      return this.$().data('kalendae');
    }
  }).volatile(),

  init() {
    this._super(...arguments);

    scheduleOnce('afterRender', this, 'initKalendae')
  },

  /**
   * Setups and initialises kalendae
   *
   * @private
   * @method initKalendae
   */
  initKalendae() {
    this.$().kalendae(
      this.getProperties(
        'mode',
        'months',
        'weekStart',
        'direction',
        'directionScrolling',
        'blackout',
        'viewStartDate',
        'dateClassMap',
        'dayOutOfMonthClickable',
        'dayHeaderClickable',
        'useYearNav',
        'side',
        'columnHeaderFormat',
        'titleFormat',
        'dayNumberFormat',
        'dayAttributeFormat',
        'parseSplitDelimiter',
        'rangeDelimiter',
        'multipleDelimiter',
        'selected'
      )
    );

    this.buildSubscriptions(this.get('kalendae'));
  },

  /**
   * Registers event callbacks with Kalendae
   *
   * @method buildSubscriptions
   * @param Object kalendae Kalendae instance
   */
  buildSubscriptions(kalendae) {
    kalendae.subscribe('change', this.didChange.bind(this));
    kalendae.subscribe('view-changed', this.didViewChanged.bind(this));
    kalendae.subscribe('date-clicked', this.didDateClicked.bind(this));
  },

  /**
   * callback for `date-clicked` event
   *
   * @method didDateClicked
   * @param Object date
   */
  didDateClicked(date) {
    const onDateClicked = this.get('onDateClicked');

    if (typeOf(onDateClicked) === 'function') {
      onDateClicked(date);
    }
  },

  /**
   * callback for `change` event
   *
   * @method didChange
   * @param Object date
   */
  didChange(date) {
    const onDidChange = this.get('onDidChange');

    if (typeOf(onDidChange) === 'function') {
      onDidChange(date, this.get('kalendae').getSelectedRaw());
    }
  },

  /**
   * callback for `view-changed` event
   *
   * @method didViewChanged
   * @param Object date
   */
  didViewChanged(view) {
    const onDidViewChanged = this.get('onDidViewChanged');

    if (typeOf(onDidViewChanged) === 'function') {
      return onDidViewChanged(view);
    }
  },

  /**
   * Removes kalendae subscriptions
   *
   * @protected
   * @method willDestroyElement
   */
  willDestroyElement() {
    this.tearDownSubscriptions(this.get('kalendae'));

    this._super(...arguments);
  },

  /**
   * Removes event callbacks previously registered with Kalendae
   *
   * @method tearDownSubscriptions
   * @param Object kalendae Kalendae instance
   */
  tearDownSubscriptions(kalendae) {
    kalendae.unsubscribe('change', this.didChange.bind(this));
    kalendae.unsubscribe('view-changed', this.didViewChanged.bind(this));
    kalendae.unsubscribe('date-clicked', this.didDateClicked.bind(this));
  }
});
<commit_msg>exposing kalendae instance<commit_after>import Component from '@ember/component';
import { scheduleOnce } from '@ember/runloop';
import { computed } from '@ember/object';
import { typeOf } from '@ember/utils';
import layout from '../templates/components/ember-kalendae';

export default Component.extend({
  layout,

  mode: 'single',
  months: '1',
  weekStart: '0',
  direction: 'any',
  directionScrolling: true,
  blackout: null,
  viewStartDate: null,
  dateClassMap: {},
  dayOutOfMonthClickable: false,
  dayHeaderClickable: false,
  useYearNav: true,
  side: 'bottom',
  columnHeaderFormat: 'dd',
  titleFormat: 'MMMM, YYYY',
  dayNumberFormat: 'D',
  dayAttributeFormat: 'YYYY-MM-DD',
  parseSplitDelimiter: /,\s*|\s*-\s*/,
  rangeDelimiter: ' - ',
  multipleDelimiter: ', ',
  selected: new Date(),

  /**
   * Returns current kalendae instance
   *
   * @property kalendae
   * @type Object | undefined Returns undefined if kalendae not found
   */
  kalendae: computed({
    get() {
      return this.$().data('kalendae');
    }
  }).volatile(),

  init() {
    this._super(...arguments);

    scheduleOnce('afterRender', this, 'initKalendae')
  },

  /**
   * Setups and initialises kalendae
   *
   * @private
   * @method initKalendae
   */
  initKalendae() {
    this.$().kalendae(
      this.getProperties(
        'mode',
        'months',
        'weekStart',
        'direction',
        'directionScrolling',
        'blackout',
        'viewStartDate',
        'dateClassMap',
        'dayOutOfMonthClickable',
        'dayHeaderClickable',
        'useYearNav',
        'side',
        'columnHeaderFormat',
        'titleFormat',
        'dayNumberFormat',
        'dayAttributeFormat',
        'parseSplitDelimiter',
        'rangeDelimiter',
        'multipleDelimiter',
        'selected'
      )
    );

    this.buildSubscriptions(this.get('kalendae'));
  },

  /**
   * Registers event callbacks with Kalendae
   *
   * @method buildSubscriptions
   * @param Object kalendae Kalendae instance
   */
  buildSubscriptions(kalendae) {
    kalendae.subscribe('change', this.didChange.bind(this));
    kalendae.subscribe('view-changed', this.didViewChanged.bind(this));
    kalendae.subscribe('date-clicked', this.didDateClicked.bind(this));
    this.pushInstanceAPI(kalendae);
  },

  /**
   * callback for `date-clicked` event
   *
   * @method didDateClicked
   * @param Object date
   */
  didDateClicked(date) {
    const onDateClicked = this.get('onDateClicked');

    if (typeOf(onDateClicked) === 'function') {
      onDateClicked(date);
    }
  },

  /**
   * callback for `change` event
   *
   * @method didChange
   * @param Object date
   */
  didChange(date) {
    const onDidChange = this.get('onDidChange');

    if (typeOf(onDidChange) === 'function') {
      onDidChange(date, this.get('kalendae').getSelectedRaw());
    }
  },

  /**
   * callback for `view-changed` event
   *
   * @method didViewChanged
   * @param Object date
   */
  didViewChanged(view) {
    const onDidViewChanged = this.get('onDidViewChanged');

    if (typeOf(onDidViewChanged) === 'function') {
      return onDidViewChanged(view);
    }
  },

  /**
   * Pushes current instance of kalendae thorough instance API
   *
   * @method pushInstanceAPI
   * @param Object kalendae
   */
  pushInstanceAPI(kalendae) {
    const instanceAPI = this.get('instanceAPI');

    if (typeOf(instanceAPI) === 'function') {
      instanceAPI(kalendae);
    }
  },

  /**
   * Removes kalendae subscriptions
   *
   * @protected
   * @method willDestroyElement
   */
  willDestroyElement() {
    this.tearDownSubscriptions(this.get('kalendae'));

    this._super(...arguments);

    const instanceAPI = this.get('instanceAPI');

    if (typeOf(instanceAPI) === 'function') {
      instanceAPI(null);
    }
  },

  /**
   * Removes event callbacks previously registered with Kalendae
   *
   * @method tearDownSubscriptions
   * @param Object kalendae Kalendae instance
   */
  tearDownSubscriptions(kalendae) {
    kalendae.unsubscribe('change', this.didChange.bind(this));
    kalendae.unsubscribe('view-changed', this.didViewChanged.bind(this));
    kalendae.unsubscribe('date-clicked', this.didDateClicked.bind(this));
  }
});
<|endoftext|>


<commit_before>/*global location */
sap.ui.define([
		"com/sap/sapmentors/sitreg/registration/controller/BaseController",
		"sap/ui/model/json/JSONModel",
		"com/sap/sapmentors/sitreg/registration/model/formatter"
	], function (BaseController, JSONModel, formatter) {
		"use strict";

		return BaseController.extend("com.sap.sapmentors.sitreg.registration.controller.Detail", {

			formatter: formatter,

			/* =========================================================== */
			/* lifecycle methods                                           */
			/* =========================================================== */

			onInit : function () {
				// Model used to manipulate control states. The chosen values make sure,
				// detail page is busy indication immediately so there is no break in
				// between the busy indication for loading the view's meta data
				var oViewModel = new JSONModel({
					busy : false,
					delay : 0,
					lineItemListTitle : this.getResourceBundle().getText("detailLineItemTableHeading")
				});

				this.getRouter().getRoute("object").attachPatternMatched(this._onObjectMatched, this);

				this.setModel(oViewModel, "detailView");

				this.getOwnerComponent().getModel().metadataLoaded().then(this._onMetadataLoaded.bind(this));
			},

			/* =========================================================== */
			/* event handlers                                              */
			/* =========================================================== */
			/**
			 * Event handler when the Export to Calendar button has been clicked
			 */
			onCalendarExport: function(){
				var iId = this.getView().getBindingContext().getProperty("ID");
				// get uri to service
				var sExportService = this.getOwnerComponent().getManifestEntry("sap.app").dataSources.mainService.uri;
				sExportService = sExportService.replace("service.xsodata/", "ExportCalendar.xsjs?ID=" + iId);
				window.open(sExportService, '_blank');
			},

			/**
			 * Event handler when the Register button has been clicked
			 * @param {object} oEvent an event containing the total number of items in the list
			 */
			onRegister: function(oEvent){
				//this.getRouter().navTo("register");
				var sObjectPath = this.getView().getElementBinding().getPath() + "/Participant";
				this.getRouter().getTargets().display("register", {
					mode: "create",
					objectId: this.getView().getBindingContext().getProperty("ID"),
					objectPath: sObjectPath
				});

			},

			/**
			 * Event handler in case of edit the participant registration.
			 * @param {object} oEvent contain the selected item
			 * @public
			 */
			onEdit : function (oEvent) {

				//this.getModel("appView").setProperty("/addEnabled", false);
				//var table = this.getView().byId("lineItemsList");
				//var rowItems = table.getSelectedItems();
				//var item  = oEvent.getParameter("item");
				//var asnnum = rowItems[0].mAggregations.cells[1].getProperty("ParticipantID");

				this.getModel("appView").setProperty("/addEnabled", false);
				var sObjectPath = this.getView().getElementBinding().getPath() + "/Participant";
				this.getRouter().getTargets().display("register", {
					mode: "update",
					objectId: this.getView().getBindingContext().getProperty("ID"),
					objectPath: sObjectPath
				});
			},

			/**
			 * Event handler when the share by E-Mail button has been clicked
			 * @public
			 */
			onShareEmailPress : function () {
				var oViewModel = this.getModel("detailView");

				sap.m.URLHelper.triggerEmail(
					null,
					oViewModel.getProperty("/shareSendEmailSubject"),
					oViewModel.getProperty("/shareSendEmailMessage")
				);
			},

			/**
			 * Updates the item count within the line item table's header
			 * @param {object} oEvent an event containing the total number of items in the list
			 * @private
			 */
			onListUpdateFinished : function (oEvent) {
				var sTitle,
					iTotalItems = oEvent.getParameter("total"),
					oViewModel = this.getModel("detailView");

				// only update the counter if the length is final
				if (this.byId("lineItemsList").getBinding("items").isLengthFinal()) {
					if (iTotalItems) {
						sTitle = this.getResourceBundle().getText("detailLineItemTableHeadingCount", [iTotalItems]);
					} else {
						//Display 'Line Items' instead of 'Line items (0)'
						sTitle = this.getResourceBundle().getText("detailLineItemTableHeading");
					}
					oViewModel.setProperty("/lineItemListTitle", sTitle);
				}
			},

			/**
			 * Event handler  for navigating back.
			 * It there is a history entry we go one step back in the browser history
			 * If not, it will replace the current entry of the browser history with the master route.
			 * @public
			 */
			onNavBack : function() {
				var sPreviousHash = this.History.getInstance().getPreviousHash();

				if (sPreviousHash !== undefined) {
					history.go(-1);
				} else {
					this.getRouter().navTo("master", {}, true);
				}
			},

			/* =========================================================== */
			/* begin: internal methods                                     */
			/* =========================================================== */

			/**
			 * Binds the view to the object path and expands the aggregated line items.
			 * @function
			 * @param {sap.ui.base.Event} oEvent pattern match event in route 'object'
			 * @private
			 */
			_onObjectMatched : function (oEvent) {
				var sObjectId =  oEvent.getParameter("arguments").objectId;
				this.getModel().metadataLoaded().then( function() {
					var sObjectPath = this.getModel().createKey("Events", {
						ID :  sObjectId
					});
					this._bindView("/" + sObjectPath, {expand: "Participant"} );
				}.bind(this));
			},

			/**
			 * Binds the view to the object path. Makes sure that detail view displays
			 * a busy indicator while data for the corresponding element binding is loaded.
			 * @function
			 * @param {string} sObjectPath path to the object to be bound to the view.
			 * @private
			 */
			_bindView : function (sObjectPath) {
				// Set busy indicator during view binding
				var oViewModel = this.getModel("detailView");

				// If the view was not bound yet its not busy, only if the binding requests data it is set to busy again
				oViewModel.setProperty("/busy", false);

				this.getView().bindElement({
					path : sObjectPath,
					events: {
						change : this._onBindingChange.bind(this),
						dataRequested : function () {
							oViewModel.setProperty("/busy", true);
						},
						dataReceived: function () {
							oViewModel.setProperty("/busy", false);
						}
					}
				});
			},

			_onBindingChange : function () {
				var oView = this.getView(),
					oElementBinding = oView.getElementBinding();

				// No data for the binding
				if (!oElementBinding.getBoundContext()) {
					this.getRouter().getTargets().display("detailObjectNotFound");
					// if object could not be found, the selection in the master list
					// does not make sense anymore.
					this.getOwnerComponent().oListSelector.clearMasterListSelection();
					return;
				}

				var sPath = oElementBinding.getPath(),
					oResourceBundle = this.getResourceBundle(),
					oObject = oView.getModel().getObject(sPath),
					sObjectLocation = oObject.Location,
					sObjectEventDate = oObject.EventDate,
					// sObjectParticipantID = oObject.Participant.ID,
					oViewModel = this.getModel("detailView");

				this.getOwnerComponent().oListSelector.selectAListItem(sPath);

				oViewModel.setProperty("/shareSendEmailSubject",
					oResourceBundle.getText("shareSendEmailObjectSubject", [sObjectEventDate, sObjectLocation]));
				oViewModel.setProperty("/shareSendEmailMessage",
					oResourceBundle.getText("shareSendEmailObjectMessage", [sObjectLocation, location.href]));
			},

			_onMetadataLoaded : function () {
				// Store original busy indicator delay for the detail view
				var iOriginalViewBusyDelay = this.getView().getBusyIndicatorDelay(),
					oViewModel = this.getModel("detailView"),
					oLineItemTable = this.byId("lineItemsList"),
					iOriginalLineItemTableBusyDelay = oLineItemTable.getBusyIndicatorDelay();

				// Make sure busy indicator is displayed immediately when
				// detail view is displayed for the first time
				oViewModel.setProperty("/delay", 0);
				oViewModel.setProperty("/lineItemTableDelay", 0);

				oLineItemTable.attachEventOnce("updateFinished", function() {
					// Restore original busy indicator delay for line item table
					oViewModel.setProperty("/lineItemTableDelay", iOriginalLineItemTableBusyDelay);
				});

				// Binding the view will set it to not busy - so the view is always busy if it is not bound
				oViewModel.setProperty("/busy", true);
				// Restore original busy indicator delay for the detail view
				oViewModel.setProperty("/delay", iOriginalViewBusyDelay);
			}

		});

	}
);<commit_msg>Fix #28<commit_after>/*global location */
sap.ui.define([
		"com/sap/sapmentors/sitreg/registration/controller/BaseController",
		"sap/ui/model/json/JSONModel",
		"com/sap/sapmentors/sitreg/registration/model/formatter"
	], function (BaseController, JSONModel, formatter) {
		"use strict";

		return BaseController.extend("com.sap.sapmentors.sitreg.registration.controller.Detail", {

			formatter: formatter,

			/* =========================================================== */
			/* lifecycle methods                                           */
			/* =========================================================== */

			onInit : function () {
				// Model used to manipulate control states. The chosen values make sure,
				// detail page is busy indication immediately so there is no break in
				// between the busy indication for loading the view's meta data
				var oViewModel = new JSONModel({
					busy : false,
					delay : 0,
					lineItemListTitle : this.getResourceBundle().getText("detailLineItemTableHeading")
				});

				this.getRouter().getRoute("object").attachPatternMatched(this._onObjectMatched, this);

				this.setModel(oViewModel, "detailView");

				this.getOwnerComponent().getModel().metadataLoaded().then(this._onMetadataLoaded.bind(this));
			},

			/* =========================================================== */
			/* event handlers                                              */
			/* =========================================================== */
			/**
			 * Event handler when the Export to Calendar button has been clicked
			 */
			onCalendarExport: function(){
				var iId = this.getView().getBindingContext().getProperty("ID");
				// get uri to service
				var sExportService = this.getOwnerComponent().getManifestEntry("sap.app").dataSources.mainService.uri;
				sExportService = sExportService.replace("service.xsodata/", "ExportCalendar.xsjs?ID=" + iId);
				window.open(sExportService, '_blank');
			},

			/**
			 * Event handler when the Register button has been clicked
			 * @param {object} oEvent an event containing the total number of items in the list
			 */
			onRegister: function(oEvent){
				//this.getRouter().navTo("register");
				var sObjectPath = this.getView().getElementBinding().getPath() + "/Participant";
				this.getRouter().getTargets().display("register", {
					mode: "create",
					objectId: this.getView().getBindingContext().getProperty("ID"),
					objectPath: sObjectPath
				});

			},

			/**
			 * Event handler in case of edit the participant registration.
			 * @param {object} oEvent contain the selected item
			 * @public
			 */
			onEdit : function (oEvent) {

				//this.getModel("appView").setProperty("/addEnabled", false);
				//var table = this.getView().byId("lineItemsList");
				//var rowItems = table.getSelectedItems();
				//var item  = oEvent.getParameter("item");
				//var asnnum = rowItems[0].mAggregations.cells[1].getProperty("ParticipantID");

				this.getModel("appView").setProperty("/addEnabled", false);
				var sObjectPath = this.getView().getElementBinding().getPath() + "/Participant";
				this.getRouter().getTargets().display("register", {
					mode: "update",
					objectId: this.getView().getBindingContext().getProperty("ID"),
					objectPath: sObjectPath
				});
			},

			/**
			 * Event handler when the share by E-Mail button has been clicked
			 * @public
			 */
			onShareEmailPress : function () {
				var oViewModel = this.getModel("detailView");

				sap.m.URLHelper.triggerEmail(
					null,
					oViewModel.getProperty("/shareSendEmailSubject"),
					oViewModel.getProperty("/shareSendEmailMessage")
				);
			},

			/**
			 * Updates the item count within the line item table's header
			 * @param {object} oEvent an event containing the total number of items in the list
			 * @private
			 */
			onListUpdateFinished : function (oEvent) {
				var sTitle,
					iTotalItems = oEvent.getParameter("total"),
					oViewModel = this.getModel("detailView");

				// only update the counter if the length is final
				if (this.byId("lineItemsList").getBinding("items").isLengthFinal()) {
					if (iTotalItems) {
						sTitle = this.getResourceBundle().getText("detailLineItemTableHeadingCount", [iTotalItems]);
					} else {
						//Display 'Line Items' instead of 'Line items (0)'
						sTitle = this.getResourceBundle().getText("detailLineItemTableHeading");
					}
					oViewModel.setProperty("/lineItemListTitle", sTitle);
				}
			},

			/**
			 * Event handler  for navigating back.
			 * It there is a history entry we go one step back in the browser history
			 * If not, it will replace the current entry of the browser history with the master route.
			 * @public
			 */
			onNavBack : function() {
				var sPreviousHash = this.History.getInstance().getPreviousHash();

				if (sPreviousHash !== undefined) {
					history.go(-1);
				} else {
					this.getRouter().navTo("master", {}, true);
				}
			},

			/* =========================================================== */
			/* begin: internal methods                                     */
			/* =========================================================== */

			/**
			 * Binds the view to the object path and expands the aggregated line items.
			 * @function
			 * @param {sap.ui.base.Event} oEvent pattern match event in route 'object'
			 * @private
			 */
			_onObjectMatched : function (oEvent) {
				var sObjectId =  oEvent.getParameter("arguments").objectId;
				this.getModel().metadataLoaded().then( function() {
					var sObjectPath = this.getModel().createKey("Events", {
						ID :  sObjectId
					});
					this._bindView("/" + sObjectPath, {expand: "Participant"} );
				}.bind(this));
			},

			/**
			 * Binds the view to the object path. Makes sure that detail view displays
			 * a busy indicator while data for the corresponding element binding is loaded.
			 * @function
			 * @param {string} sObjectPath path to the object to be bound to the view.
			 * @private
			 */
			_bindView : function (sObjectPath, oParameters) {
				// Set busy indicator during view binding
				var oViewModel = this.getModel("detailView");

				// If the view was not bound yet its not busy, only if the binding requests data it is set to busy again
				oViewModel.setProperty("/busy", false);

				this.getView().bindElement({
					path : sObjectPath,
					parameters: oParameters,
					events: {
						change : this._onBindingChange.bind(this),
						dataRequested : function () {
							oViewModel.setProperty("/busy", true);
						},
						dataReceived: function () {
							oViewModel.setProperty("/busy", false);
						}
					}
				});
			},

			_onBindingChange : function () {
				var oView = this.getView(),
					oElementBinding = oView.getElementBinding();

				// No data for the binding
				if (!oElementBinding.getBoundContext()) {
					this.getRouter().getTargets().display("detailObjectNotFound");
					// if object could not be found, the selection in the master list
					// does not make sense anymore.
					this.getOwnerComponent().oListSelector.clearMasterListSelection();
					return;
				}

				var sPath = oElementBinding.getPath(),
					oResourceBundle = this.getResourceBundle(),
					oObject = oView.getModel().getObject(sPath),
					sObjectLocation = oObject.Location,
					sObjectEventDate = oObject.EventDate,
					// sObjectParticipantID = oObject.Participant.ID,
					oViewModel = this.getModel("detailView");

				this.getOwnerComponent().oListSelector.selectAListItem(sPath);

				oViewModel.setProperty("/shareSendEmailSubject",
					oResourceBundle.getText("shareSendEmailObjectSubject", [sObjectEventDate, sObjectLocation]));
				oViewModel.setProperty("/shareSendEmailMessage",
					oResourceBundle.getText("shareSendEmailObjectMessage", [sObjectLocation, location.href]));
			},

			_onMetadataLoaded : function () {
				// Store original busy indicator delay for the detail view
				var iOriginalViewBusyDelay = this.getView().getBusyIndicatorDelay(),
					oViewModel = this.getModel("detailView"),
					oLineItemTable = this.byId("lineItemsList"),
					iOriginalLineItemTableBusyDelay = oLineItemTable.getBusyIndicatorDelay();

				// Make sure busy indicator is displayed immediately when
				// detail view is displayed for the first time
				oViewModel.setProperty("/delay", 0);
				oViewModel.setProperty("/lineItemTableDelay", 0);

				oLineItemTable.attachEventOnce("updateFinished", function() {
					// Restore original busy indicator delay for line item table
					oViewModel.setProperty("/lineItemTableDelay", iOriginalLineItemTableBusyDelay);
				});

				// Binding the view will set it to not busy - so the view is always busy if it is not bound
				oViewModel.setProperty("/busy", true);
				// Restore original busy indicator delay for the detail view
				oViewModel.setProperty("/delay", iOriginalViewBusyDelay);
			}

		});

	}
);<|endoftext|>


<commit_before>var
  _ = require('lodash'),
  q = require('q'),
  ResponseObject = require('./models/responseObject'),
  RequestObject = require('./models/requestObject'),
  async = require('async');

/**
 * This internal service can either be invoked directly by
 * Kuzzle internal components, or through the internal
 * broker. This second way of communication is used
 * by workers to notify rooms about their work.
 */
module.exports = function NotifierController (kuzzle) {
  this.init = function () {
    kuzzle.services.list.broker.listen(kuzzle.config.queues.coreNotifierTaskQueue, workerNotification.bind(kuzzle));
  };

  /**
   * Notify rooms with a data object. Invoked directly by kuzzle
   * core components
   *
   * @param rooms
   * @param {Object} response
   * @param {string} connectionId
   * @returns {boolean}
   */
  this.notify = function (rooms, response, connectionId) {
    if (!rooms) {
      return false;
    }

    if (!Array.isArray(rooms)) {
      rooms = [rooms];
    }

    async.each(rooms, function (roomName) {
      send.call(kuzzle, roomName, response, connectionId);
    });
  };

  /**
   * Notify subscribed users with a realtime message
   *
   * @param requestObject
   */
  this.publish = function (requestObject) {
    var
      deferred = q.defer(),
      response = new ResponseObject(requestObject, {_source: requestObject.data.body});

    kuzzle.dsl.testFilters(response)
      .then(rooms => {
        if (!_.isEmpty(rooms)) {
          if (response.controller === 'write') {
            if (response.action === 'publish') {
              response.state = 'done';
              response.scope = 'in';
            }
            else if (response.action === 'create' || response.action === 'createOrReplace' || response.action === 'replace') {
              /*
               since we have the complete document, we use the cache to avoid performing another testFilter when
               notifying about document creations
               */
              kuzzle.services.list.notificationCache.add(response.requestId, rooms)
                .then(() => kuzzle.services.list.notificationCache.expire(response.requestId, 10));
            }
          }

          this.notify(rooms, response.toJson());
        }
        deferred.resolve(response);
      })
      .catch(error => deferred.reject(error));

    return deferred.promise;
  };
};

/**
 * Notify by sending the message data on all corresponding channels
 * If no connection ID is provided, broadcasts the message on all users connected to the channels
 *
 * @param {String} room
 * @param {object} responseObject
 * @param {String} [connectionId] - ID of the connection to send the message
 */
function send (room, responseObject, connectionId) {
  var
    data = {
      payload: responseObject
    },
    eventName;

  if (connectionId) {
    data.id = connectionId;
    eventName = 'protocol:notify';
  } else {
    eventName = 'protocol:broadcast';
  }

  this.hotelClerk.getChannels(room, responseObject).forEach(channel => {
    data.channel = channel;
    this.pluginsManager.trigger(eventName, data);
    this.services.list.mqBroker.addExchange(channel, responseObject);
  });
}

/**
 * Listens to the internal broker for notification messages from workers.
 * Notify rooms on a document creation/update/deletion
 *
 * @param {Object} serializedResponseObject
 */
function workerNotification (serializedResponseObject) {
  var responseObject = ResponseObject.prototype.unserialize(serializedResponseObject);
  var action = {
    create: notifyDocumentCreate,
    createOrReplace: (responseObject.data && responseObject.data.created) ? notifyDocumentCreate : notifyDocumentUpdate,
    update: notifyDocumentUpdate,
    replace: notifyDocumentReplace,
    delete: notifyDocumentDelete,
    deleteByQuery: notifyDocumentDelete
  };

  if (action[responseObject.action]) {
    action[responseObject.action].call(this, responseObject)
      .catch(function (error) {
        this.pluginsManager.trigger('log:error', error);
      }.bind(this));
  }
}

/**
 * Notify rooms that a newly created document entered their scope
 *
 * @param {ResponseObject} responseObject object describing the response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentCreate (responseObject) {
  var
    deferred = q.defer();

  this.services.list.notificationCache.search(responseObject.requestId)
    .then(rooms => {
      var response = responseObject.toJson();
      response.action = 'create';
      response.scope = 'in';
      this.notifier.notify(rooms, response);
      return this.services.list.notificationCache.add(responseObject.data.body._id, rooms);
    })
    .then(() => deferred.resolve({}))
    .catch(error => deferred.reject(error));

  return deferred.promise;
}

/**
 * Notify rooms that, either :
 *    - an updated document is now in their scope
 *    - a document they listened to left their scope
 *
 * @param {ResponseObject} responseObject object describing the document response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentReplace (responseObject) {
  var
    matchedRooms,
    deferred = q.defer();

  this.services.list.notificationCache.search(responseObject.requestId)
    .then((rooms) => {
      var jsonResponseObject;
      matchedRooms = rooms;
      jsonResponseObject = responseObject.toJson();
      jsonResponseObject.scope = 'in';
      jsonResponseObject.action = 'update';

      this.notifier.notify(matchedRooms, jsonResponseObject);

      return this.services.list.notificationCache.search(responseObject.data.body._id);
    })
    .then((cachedRooms) => {
      var
        stopListening = _.difference(cachedRooms, matchedRooms),
        leftScopeResponse = responseObject.toJson(['body', '_source']);
      leftScopeResponse.scope = 'out';
      leftScopeResponse.action = 'update';

      this.notifier.notify(stopListening, leftScopeResponse);

      return this.services.list.notificationCache.remove(responseObject.data.body._id, stopListening);
    })
    .then(() => {
      return this.services.list.notificationCache.add(responseObject.data.body._id, matchedRooms);
    })
    .then(() => {
      deferred.resolve({});
    })
    .catch((error) => {
      deferred.reject(error);
    });

  return deferred.promise;
}

/**
 * Notify rooms that, either :
 *    - an updated document is now in their scope
 *    - a document they listened to left their scope
 *
 * @param {ResponseObject} responseObject object describing the document response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentUpdate (responseObject) {
  var
    matchedRooms,
    updateResponseObject,
    request,
    deferred = q.defer();

  request = {
    action: 'update',
    controller: 'write',
    collection: responseObject.collection,
    index: responseObject.index,
    _id: responseObject.data.body._id,
    requestId: responseObject.requestId,
    metadata: responseObject.metadata
  };

  this.services.list.readEngine.get(new RequestObject(request))
    .then((result) => {
      updateResponseObject = result;
      updateResponseObject.scope = 'in';
      return this.dsl.testFilters(updateResponseObject);
    })
    .then((rooms) => {
      matchedRooms = rooms;
      this.notifier.notify(matchedRooms, updateResponseObject.toJson());

      return this.services.list.notificationCache.search(responseObject.data.body._id);
    })
    .then((cachedRooms) => {
      var
        stopListening = _.difference(cachedRooms, matchedRooms),
        leftScopeResponse = updateResponseObject.toJson(['body', '_source']);
      leftScopeResponse.scope = 'out';
      this.notifier.notify(stopListening, leftScopeResponse);

      return this.services.list.notificationCache.remove(updateResponseObject.data.body._id, stopListening);
    })
    .then(() => {
      return this.services.list.notificationCache.add(updateResponseObject.data.body._id, matchedRooms);
    })
    .then(() => {
      deferred.resolve({});
    })
    .catch((error) => {
      deferred.reject(error);
    });

  return deferred.promise;
}

/**
 * Notify rooms that a document they listened to has been deleted
 *
 * @param {ResponseObject} responseObject object containing the document ID (or an array of IDs)
 * @return {Promise} number of notified rooms
 */
function notifyDocumentDelete (responseObject) {
  var
    deferred = q.defer(),
    idList = [],
    self = this;

  if (responseObject.action === 'deleteByQuery') {
    idList = responseObject.data.body.ids;
    responseObject.action = 'delete';
  }
  else if (responseObject.data.body._id) {
    idList = [responseObject.data.body._id];
  }

  responseObject.scope = 'out';

  async.each(idList, function (id, callback) {
    self.services.list.notificationCache.search(id)
      .then(function (cachedRooms) {
        responseObject.data.body._id = id;
        self.notifier.notify(cachedRooms, responseObject.toJson(['body']));

        return self.services.list.notificationCache.remove(id);
      })
      .then(function () {
        callback();
      })
      .catch(function (error) {
        callback(error);
      });
  }, function (error) {
    if (error) {
      deferred.reject(error);
      return false;
    }
    deferred.resolve({});
  });

  return deferred.promise;
}
<commit_msg>fix problem with multi scope on same app<commit_after>var
  _ = require('lodash'),
  q = require('q'),
  ResponseObject = require('./models/responseObject'),
  RequestObject = require('./models/requestObject'),
  async = require('async');

/**
 * This internal service can either be invoked directly by
 * Kuzzle internal components, or through the internal
 * broker. This second way of communication is used
 * by workers to notify rooms about their work.
 */
module.exports = function NotifierController (kuzzle) {
  this.init = function () {
    kuzzle.services.list.broker.listen(kuzzle.config.queues.coreNotifierTaskQueue, workerNotification.bind(kuzzle));
  };

  /**
   * Notify rooms with a data object. Invoked directly by kuzzle
   * core components
   *
   * @param rooms
   * @param {Object} response
   * @param {string} connectionId
   * @returns {boolean}
   */
  this.notify = function (rooms, response, connectionId) {
    if (!rooms) {
      return false;
    }

    if (!Array.isArray(rooms)) {
      rooms = [rooms];
    }

    async.each(rooms, function (roomName) {
      send.call(kuzzle, roomName, response, connectionId);
    });
  };

  /**
   * Notify subscribed users with a realtime message
   *
   * @param requestObject
   */
  this.publish = function (requestObject) {
    var
      deferred = q.defer(),
      response = new ResponseObject(requestObject, {_source: requestObject.data.body});

    kuzzle.dsl.testFilters(response)
      .then(rooms => {
        if (!_.isEmpty(rooms)) {
          if (response.controller === 'write') {
            if (response.action === 'publish') {
              response.state = 'done';
              response.scope = 'in';
            }
            else if (response.action === 'create' || response.action === 'createOrReplace' || response.action === 'replace') {
              /*
               since we have the complete document, we use the cache to avoid performing another testFilter when
               notifying about document creations
               */
              kuzzle.services.list.notificationCache.add(response.requestId, rooms)
                .then(() => kuzzle.services.list.notificationCache.expire(response.requestId, 10));
            }
          }

          this.notify(rooms, response.toJson());
        }
        deferred.resolve(response);
      })
      .catch(error => deferred.reject(error));

    return deferred.promise;
  };
};

/**
 * Notify by sending the message data on all corresponding channels
 * If no connection ID is provided, broadcasts the message on all users connected to the channels
 *
 * @param {String} room
 * @param {object} responseObject
 * @param {String} [connectionId] - ID of the connection to send the message
 */
function send (room, responseObject, connectionId) {
  var
    eventName;

  if (connectionId) {
    eventName = 'protocol:notify';
  } else {
    eventName = 'protocol:broadcast';
  }

  this.hotelClerk.getChannels(room, responseObject).forEach(channel => {
    var data = {payload: responseObject, channel};

    if (connectionId) {
      data.id = connectionId;
    }

    this.pluginsManager.trigger(eventName, data);
    this.services.list.mqBroker.addExchange(channel, responseObject);
  });
}

/**
 * Listens to the internal broker for notification messages from workers.
 * Notify rooms on a document creation/update/deletion
 *
 * @param {Object} serializedResponseObject
 */
function workerNotification (serializedResponseObject) {
  var responseObject = ResponseObject.prototype.unserialize(serializedResponseObject);
  var action = {
    create: notifyDocumentCreate,
    createOrReplace: (responseObject.data && responseObject.data.created) ? notifyDocumentCreate : notifyDocumentUpdate,
    update: notifyDocumentUpdate,
    replace: notifyDocumentReplace,
    delete: notifyDocumentDelete,
    deleteByQuery: notifyDocumentDelete
  };

  if (action[responseObject.action]) {
    action[responseObject.action].call(this, responseObject)
      .catch(function (error) {
        this.pluginsManager.trigger('log:error', error);
      }.bind(this));
  }
}

/**
 * Notify rooms that a newly created document entered their scope
 *
 * @param {ResponseObject} responseObject object describing the response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentCreate (responseObject) {
  var
    deferred = q.defer();

  this.services.list.notificationCache.search(responseObject.requestId)
    .then(rooms => {
      var response = responseObject.toJson();
      response.action = 'create';
      response.scope = 'in';
      this.notifier.notify(rooms, response);
      return this.services.list.notificationCache.add(responseObject.data.body._id, rooms);
    })
    .then(() => deferred.resolve({}))
    .catch(error => deferred.reject(error));

  return deferred.promise;
}

/**
 * Notify rooms that, either :
 *    - an updated document is now in their scope
 *    - a document they listened to left their scope
 *
 * @param {ResponseObject} responseObject object describing the document response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentReplace (responseObject) {
  var
    matchedRooms,
    deferred = q.defer();

  this.services.list.notificationCache.search(responseObject.requestId)
    .then((rooms) => {
      var jsonResponseObject;
      matchedRooms = rooms;
      jsonResponseObject = responseObject.toJson();
      jsonResponseObject.scope = 'in';
      jsonResponseObject.action = 'update';

      this.notifier.notify(matchedRooms, jsonResponseObject);

      return this.services.list.notificationCache.search(responseObject.data.body._id);
    })
    .then((cachedRooms) => {
      var
        stopListening = _.difference(cachedRooms, matchedRooms),
        leftScopeResponse = responseObject.toJson(['body', '_source']);
      leftScopeResponse.scope = 'out';
      leftScopeResponse.action = 'update';

      this.notifier.notify(stopListening, leftScopeResponse);

      return this.services.list.notificationCache.remove(responseObject.data.body._id, stopListening);
    })
    .then(() => {
      return this.services.list.notificationCache.add(responseObject.data.body._id, matchedRooms);
    })
    .then(() => {
      deferred.resolve({});
    })
    .catch((error) => {
      deferred.reject(error);
    });

  return deferred.promise;
}

/**
 * Notify rooms that, either :
 *    - an updated document is now in their scope
 *    - a document they listened to left their scope
 *
 * @param {ResponseObject} responseObject object describing the document response from writeEngine
 * @return {Promise} number of notified rooms
 */
function notifyDocumentUpdate (responseObject) {
  var
    matchedRooms,
    updateResponseObject,
    request,
    deferred = q.defer();

  request = {
    action: 'update',
    controller: 'write',
    collection: responseObject.collection,
    index: responseObject.index,
    _id: responseObject.data.body._id,
    requestId: responseObject.requestId,
    metadata: responseObject.metadata
  };

  this.services.list.readEngine.get(new RequestObject(request))
    .then((result) => {
      updateResponseObject = result;
      updateResponseObject.scope = 'in';
      return this.dsl.testFilters(updateResponseObject);
    })
    .then((rooms) => {
      matchedRooms = rooms;
      this.notifier.notify(matchedRooms, updateResponseObject.toJson());

      return this.services.list.notificationCache.search(responseObject.data.body._id);
    })
    .then((cachedRooms) => {
      var
        stopListening = _.difference(cachedRooms, matchedRooms),
        leftScopeResponse = updateResponseObject.toJson(['body', '_source']);
      leftScopeResponse.scope = 'out';
      this.notifier.notify(stopListening, leftScopeResponse);

      return this.services.list.notificationCache.remove(updateResponseObject.data.body._id, stopListening);
    })
    .then(() => {
      return this.services.list.notificationCache.add(updateResponseObject.data.body._id, matchedRooms);
    })
    .then(() => {
      deferred.resolve({});
    })
    .catch((error) => {
      deferred.reject(error);
    });

  return deferred.promise;
}

/**
 * Notify rooms that a document they listened to has been deleted
 *
 * @param {ResponseObject} responseObject object containing the document ID (or an array of IDs)
 * @return {Promise} number of notified rooms
 */
function notifyDocumentDelete (responseObject) {
  var
    deferred = q.defer(),
    idList = [],
    self = this;

  if (responseObject.action === 'deleteByQuery') {
    idList = responseObject.data.body.ids;
    responseObject.action = 'delete';
  }
  else if (responseObject.data.body._id) {
    idList = [responseObject.data.body._id];
  }

  responseObject.scope = 'out';

  async.each(idList, function (id, callback) {
    self.services.list.notificationCache.search(id)
      .then(function (cachedRooms) {
        responseObject.data.body._id = id;
        self.notifier.notify(cachedRooms, responseObject.toJson(['body']));

        return self.services.list.notificationCache.remove(id);
      })
      .then(function () {
        callback();
      })
      .catch(function (error) {
        callback(error);
      });
  }, function (error) {
    if (error) {
      deferred.reject(error);
      return false;
    }
    deferred.resolve({});
  });

  return deferred.promise;
}
<|endoftext|>


<commit_before>// app.js
var express = require('express');

// express middleware
var compression    = require('compression');
var logger         = require('morgan');
var cookieParser   = require('cookie-parser');
var bodyParser     = require('body-parser');
var methodOverride = require('method-override');

// routes
var router       = require('./router.js');
var staticRouter = require('./static.js');

// circle-blvd modules
var auth   = require('circle-blvd/auth-local');
var errors = require('circle-blvd/errors');

var firstRun    = require('circle-blvd/first-run');
var socketSetup = require('circle-blvd/socket-setup');
var session     = require('circle-blvd/session');

var payment    = require('circle-blvd/payment')();
var settings   = require('circle-blvd/settings');

var canonicalDomain = require('circle-blvd/canonical-domain')(settings);
var defaultSettings = require('./back-end/settings');
var corsIonic       = require('circle-blvd/cors-ionic');

var app        = express();
var io         = require('socket.io')();
var webServer  = require('./web-server.js')(app, io);
var forceHttps = require('circle-blvd/force-https')(webServer.https);

// configure Express
var init = function (config, callback) {
    // Default config
    if (!config) {
        config = {
            isDebugging: false,
            httpPort: 3000,
            httpsPort: 4000
        }
    }
    if (!callback) {
        callback = function () {};
    }

    var isDebugging = config.isDebugging || false;

    webServer.setPort(config.httpPort || 3000);
    webServer.https.setPort(config.httpsPort || 4000);

    // Views and view engines
    app.set('views', __dirname + '/front-end/views');
    app.set('view engine', 'jade');

    // For index.ejs. On its way out.
    app.engine('ejs', require('ejs').__express);

    // TODO: canonicalDomain will not work for the first request
    // after the settings are changed.
    //
    // Canonical domain needs to be before https, otherwise
    // a browser will try to use the canonical https certificate
    // to connect to the non-canonical domain
    app.use(canonicalDomain);
    app.use(forceHttps);

    app.use(compression());

    if (isDebugging) {
        app.use(corsIonic);
    }

    app.use(staticRouter(isDebugging));

    app.use(logger('dev'));
    app.use(cookieParser()); // TODO: Signed cookies?
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(bodyParser.json());
    app.use(methodOverride()); // TODO: What do we use this for?

    settings.addListener(onSettingsUpdate);

    settings.init(defaultSettings, function (err, settingsTable) {
        if (err) {
            callback(err);
            return;
        }

        processSettings(settingsTable);
        callback();
    });

    function processSettings (settingsTable) {

        var stripeApiKey = settingsTable['stripe-secret-key'];
        if (stripeApiKey) {
            payment.setApiKey(stripeApiKey.value);
        }

        // Sessions
        var sessionSecret     = settingsTable['session-secret'].value;
        var sessionMiddleware = session.middleware(sessionSecret);
        app.use(sessionMiddleware);

        // Authentication
        app.use(auth.middleware);

        // Settings cache
        app.use(settings.middleware);

        // Is this the first run of the system?
        app.use(firstRun);

        // Real-time engine
        app.use(socketSetup(io, sessionMiddleware));

        // Routes
        app.use("/", router);

        // Catch errors
        app.use(errors.middleware);
    };
};

function onSettingsUpdate (setting) {
    if (setting.name === 'ssl-key-path' || setting.name === 'ssl-cert-path') {
        webServer.https.restart();
    }

    if (setting.name === 'stripe-secret-key') {
        payment.setApiKey(setting.value);
    }
};

exports.express = app;
exports.init = init;
exports.startServer = webServer.start;<commit_msg>Refactor: Extract ensure default config<commit_after>// app.js
var express = require('express');

// express middleware
var compression    = require('compression');
var logger         = require('morgan');
var cookieParser   = require('cookie-parser');
var bodyParser     = require('body-parser');
var methodOverride = require('method-override');

// routes
var router       = require('./router.js');
var staticRouter = require('./static.js');

// circle-blvd modules
var auth   = require('circle-blvd/auth-local');
var errors = require('circle-blvd/errors');

var firstRun    = require('circle-blvd/first-run');
var socketSetup = require('circle-blvd/socket-setup');
var session     = require('circle-blvd/session');

var payment    = require('circle-blvd/payment')();
var settings   = require('circle-blvd/settings');

var canonicalDomain = require('circle-blvd/canonical-domain')(settings);
var defaultSettings = require('./back-end/settings');
var corsIonic       = require('circle-blvd/cors-ionic');

var app        = express();
var io         = require('socket.io')();
var webServer  = require('./web-server.js')(app, io);
var forceHttps = require('circle-blvd/force-https')(webServer.https);

// configure Express
var init = function (config, callback) {
    config = ensureConfig(config);
    callback = ensureCallback(callback);

    var isDebugging = config.isDebugging;

    webServer.setPort(config.httpPort);
    webServer.https.setPort(config.httpsPort);

    // Views and view engines
    app.set('views', __dirname + '/front-end/views');
    app.set('view engine', 'jade');

    // For index.ejs. On its way out.
    app.engine('ejs', require('ejs').__express);

    // Middleware stack
    //
    // TODO: canonicalDomain will not work for the first request
    // after the settings are changed.
    //
    // Canonical domain needs to be before https, otherwise
    // a browser will try to use the canonical https certificate
    // to connect to the non-canonical domain
    app.use(canonicalDomain);
    app.use(forceHttps);

    app.use(compression());

    if (isDebugging) {
        app.use(corsIonic);
    }

    // HTML, CSS, JavaScript files
    app.use(staticRouter(isDebugging));

    app.use(logger('dev'));
    app.use(cookieParser()); // TODO: Signed cookies?
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(bodyParser.json());
    app.use(methodOverride()); // TODO: What do we use this for?

    settings.addListener(onSettingsUpdate);

    settings.init(defaultSettings, function (err, settingsTable) {
        if (err) {
            callback(err);
            return;
        }

        processSettings(settingsTable);
        callback();
    });

    function processSettings (settingsTable) {

        var stripeApiKey = settingsTable['stripe-secret-key'];
        if (stripeApiKey) {
            payment.setApiKey(stripeApiKey.value);
        }

        // Sessions
        var sessionSecret     = settingsTable['session-secret'].value;
        var sessionMiddleware = session.middleware(sessionSecret);
        app.use(sessionMiddleware);

        // Authentication
        app.use(auth.middleware);

        // Settings cache
        app.use(settings.middleware);

        // Is this the first run of the system?
        app.use(firstRun);

        // Real-time engine
        app.use(socketSetup(io, sessionMiddleware));

        // Routes
        app.use("/", router);

        // Catch errors
        app.use(errors.middleware);
    };
};

function onSettingsUpdate (setting) {
    if (setting.name === 'ssl-key-path' || setting.name === 'ssl-cert-path') {
        webServer.https.restart();
    }

    if (setting.name === 'stripe-secret-key') {
        payment.setApiKey(setting.value);
    }
};

function ensureConfig (config) {
    var defaults = {
        isDebugging: false,
        httpPort: 3000,
        httpsPort: 4000
    };

    config = config || defaults;

    config.isDebugging = config.isDebugging || defaults.isDebugging;
    config.httpPort    = config.httpPort || defaults.httpPort;
    config.httpsPort   = config.httpsPort || defaults.httpsPort;

    return config;
}

function ensureCallback (callback) {
    if (!callback) {
        return function() {};
    }
    return callback;
}

exports.express = app;
exports.init = init;
exports.startServer = webServer.start;<|endoftext|>


<commit_before>angular.module('generatorApp')
.controller('generatorController', ['$scope', '$location', '$timeout', 'GeneratorService',
function($scope, $location, $timeout, GeneratorService) {

  $scope.p = {};
  $scope.p.alg = 0;
  $scope.p.fb = 0;
  var i;
  for (i = 0; i < 4; i++) {
    $scope.p['op' + i + 'ar'] = 0;
    $scope.p['op' + i + 'dr'] = 0;
    $scope.p['op' + i + 'sr'] = 0;
    $scope.p['op' + i + 'rr'] = 0;
    $scope.p['op' + i + 'sl'] = 0;
    $scope.p['op' + i + 'tl'] = 0;
    $scope.p['op' + i + 'ks'] = 0;
    $scope.p['op' + i + 'mul'] = 0;
    $scope.p['op' + i + 'dt1'] = 0;
    $scope.p['op' + i + 'dt2'] = 0;
    $scope.p['op' + i + 'ams'] = 0;
  }

  function createRadios(min, max, step) {
    var radios = [];
    var i;
    var o;
    for (i = min; i < max + 1; i += step) {
      o = {id:i, name:"" + i};
      radios.push(o);
    }
    return radios;
  }
  $scope.algRadios = createRadios(0, 7, 1);
  $scope.fbRadios = createRadios(0, 7, 1);
  $scope.arRadios = createRadios(0, 31, 4);
  $scope.drRadios = createRadios(0, 31, 4);
  $scope.srRadios = createRadios(0, 31, 4);
  $scope.rrRadios = createRadios(0, 15, 3);
  $scope.slRadios = createRadios(0, 15, 3);
  $scope.tlRadios = createRadios(0, 127, 16);
  $scope.ksRadios = createRadios(0, 3, 1);
  $scope.mulRadios = createRadios(0, 15, 1);
  $scope.dt1Radios = createRadios(0, 7, 1);
  $scope.dt2Radios = createRadios(0, 3, 1);
  $scope.amsRadios = createRadios(0, 3, 1);

  $scope.generate = function() {
    $scope.generatedMml = GeneratorService.generate($scope.p);
    console.log($scope.generatedMml);
    SIOPM.compile($scope.generatedMml);
  };

  SIOPM.onLoad = function() {
  return;
  //TODO 追々やるつもり
    if (angular.isString($scope.p.inputText)) {
      $timeout(function() {
        $scope.generate();
      }, 0);
    }
  };

  SIOPM.onCompileComplete = function() {
    SIOPM.play();
  };

  SIOPM.initialize(); // [前提] SIOPMのプロパティへ各functionを代入し終わっていること
  $timeout(function() {
//TODO 追々やるつもり    setParamsFromUrl(); // [前提] $scopeのプロパティへ各functionを代入し終わっていること
  }, 0);

}]);
<commit_msg>初期値を、どこかクリックすれば音が鳴るものにしたつもり<commit_after>angular.module('generatorApp')
.controller('generatorController', ['$scope', '$location', '$timeout', 'GeneratorService',
function($scope, $location, $timeout, GeneratorService) {

  $scope.p = {};
  $scope.p.alg = 0;
  $scope.p.fb = 0;
  var i;
  for (i = 0; i < 4; i++) {
    $scope.p['op' + i + 'ar'] = 9;
    $scope.p['op' + i + 'dr'] = 1;
    $scope.p['op' + i + 'sr'] = 1;
    $scope.p['op' + i + 'rr'] = 1;
    $scope.p['op' + i + 'sl'] = 0;
    $scope.p['op' + i + 'tl'] = 0;
    $scope.p['op' + i + 'ks'] = 0;
    $scope.p['op' + i + 'mul'] = 1;
    $scope.p['op' + i + 'dt1'] = 4;
    $scope.p['op' + i + 'dt2'] = 0;
    $scope.p['op' + i + 'ams'] = 0;
  }

  function createRadios(min, max, step) {
    var radios = [];
    var i;
    var o;
    for (i = min; i < max + 1; i += step) {
      o = {id:i, name:"" + i};
      radios.push(o);
    }
    return radios;
  }
  $scope.algRadios = createRadios(0, 7, 1);
  $scope.fbRadios = createRadios(0, 7, 1);
  $scope.arRadios = createRadios(0, 31, 4);
  $scope.drRadios = createRadios(0, 31, 4);
  $scope.srRadios = createRadios(0, 31, 4);
  $scope.rrRadios = createRadios(0, 15, 3);
  $scope.slRadios = createRadios(0, 15, 3);
  $scope.tlRadios = createRadios(0, 127, 16);
  $scope.ksRadios = createRadios(0, 3, 1);
  $scope.mulRadios = createRadios(0, 15, 1);
  $scope.dt1Radios = createRadios(0, 7, 1);
  $scope.dt2Radios = createRadios(0, 3, 1);
  $scope.amsRadios = createRadios(0, 3, 1);

  $scope.generate = function() {
    $scope.generatedMml = GeneratorService.generate($scope.p);
    console.log($scope.generatedMml);
    SIOPM.compile($scope.generatedMml);
  };

  SIOPM.onLoad = function() {
  return;
  //TODO 追々やるつもり
    if (angular.isString($scope.p.inputText)) {
      $timeout(function() {
        $scope.generate();
      }, 0);
    }
  };

  SIOPM.onCompileComplete = function() {
    SIOPM.play();
  };

  SIOPM.initialize(); // [前提] SIOPMのプロパティへ各functionを代入し終わっていること
  $timeout(function() {
//TODO 追々やるつもり    setParamsFromUrl(); // [前提] $scopeのプロパティへ各functionを代入し終わっていること
  }, 0);

}]);
<|endoftext|>


<commit_before>/*
    The Conflict of Interest (COI) module of Kuali Research
    Copyright © 2015 Kuali, Inc.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

import healthReport from './controllers/healthReport';
import * as ConfigController from './controllers/ConfigController';
import * as DisclosureController from './controllers/DisclosureController';
import * as TravelLogController from './controllers/TravelLogController';
import * as ProjectController from './controllers/ProjectController';
import * as FileController from './controllers/FileController';
import * as PIController from './controllers/PIController';
import * as UserController from './controllers/UserController';
import * as AdditionalReviewerController from './controllers/AdditionalReviewerController';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import authentication from './middleware/authentication';
import apiAuthentication from './middleware/apiAthentication';
import renderView from './middleware/renderView';
import Log from './Log';
import methodChecker from './middleware/methodChecker';
import ErrorLogger from './middleware/ErrorLogger';
import {COIConstants} from '../COIConstants';
import {NOT_FOUND} from '../HTTPStatusCodes';
import adminRoleCheck from './middleware/adminRoleCheck';
import unauthorized from './middleware/unauthorized';
import scheduleExpirationCheck from './expirationCheck';

const DEFAULT_PORT = 8090;

function conditionallyLogRequests(app) {
  if (process.env.LOG_LEVEL <= COIConstants.LOG_LEVEL.INFO) {
    app.use((req, res, next) => {
      const startTime = new Date().getTime();
      res.on('finish', () => {
        const elapsedTime = new Date().getTime() - startTime;
        Log.info(`${req.originalUrl} - ${elapsedTime}ms`);
      });
      next();
    });
  }
}

function configureProxy(app) {
  const TRUST_PROXY = process.env.TRUST_PROXY;
  if (TRUST_PROXY) {
    Log.info(`Using TRUST_PROXY value of ${TRUST_PROXY}`);

    if (TRUST_PROXY.toLowerCase() === 'true') {
      app.set('trust proxy', true);
    }
    else if (TRUST_PROXY.toLowerCase() === 'false') {
      app.set('trust proxy', false);
    }
    else {
      app.set('trust proxy', TRUST_PROXY);
    }
  }
}

export function run() {
  const app = express();
  app.disable('x-powered-by');
  app.set('view engine', 'jade');
  app.set('views', './views');
  configureProxy(app);

  let config;
  try {
    const extensions = require('research-extensions').default;
    extensions.express(app);
    config = extensions.config;
  } catch (e) {
    Log.info('extensions not found');
  }

  conditionallyLogRequests(app);

  app.use('/coi', express.static('client'));
  app.use('/coi/build', (req, res) => { res.sendStatus(NOT_FOUND); }); // Static files that weren't found

  app.use(methodChecker);
  app.use(cookieParser());

  app.use('/coi/auth', renderView('auth'));
  app.get('/coi/health', healthReport);
  app.use('/api', apiAuthentication);
  app.use('/coi', authentication);
  app.use('/coi$', renderView('index'));
  app.use('/coi/$', renderView('index'));
  app.use('/coi/archiveview', renderView('index'));
  app.use('/coi/dashboard', renderView('index'));
  app.use('/coi/disclosure', renderView('index'));
  app.use('/coi/travelLog', renderView('index'));
  app.use('/coi/revise', renderView('index'));
  app.use('/coi/about', renderView('about'));

  app.use('/coi/admin', adminRoleCheck, renderView('admin/admin'));
  app.use('/coi/config', adminRoleCheck, renderView('admin/config'));
  app.use('/coi', unauthorized);

  app.use(bodyParser.json());
  ConfigController.init(app);
  DisclosureController.init(app);
  TravelLogController.init(app);
  ProjectController.init(app);
  FileController.init(app);
  PIController.init(app);
  UserController.init(app);
  AdditionalReviewerController.init(app);
  app.use(ErrorLogger);

  app.set('portNumber', config ? config.port : process.env.COI_PORT || DEFAULT_PORT);

  scheduleExpirationCheck();

  return app;
}
<commit_msg>Fixing intentional break<commit_after>/*
    The Conflict of Interest (COI) module of Kuali Research
    Copyright © 2015 Kuali, Inc.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

import healthReport from './controllers/healthReport';
import * as ConfigController from './controllers/ConfigController';
import * as DisclosureController from './controllers/DisclosureController';
import * as TravelLogController from './controllers/TravelLogController';
import * as ProjectController from './controllers/ProjectController';
import * as FileController from './controllers/FileController';
import * as PIController from './controllers/PIController';
import * as UserController from './controllers/UserController';
import * as AdditionalReviewerController from './controllers/AdditionalReviewerController';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import authentication from './middleware/authentication';
import apiAuthentication from './middleware/apiAuthentication';
import renderView from './middleware/renderView';
import Log from './Log';
import methodChecker from './middleware/methodChecker';
import ErrorLogger from './middleware/ErrorLogger';
import {COIConstants} from '../COIConstants';
import {NOT_FOUND} from '../HTTPStatusCodes';
import adminRoleCheck from './middleware/adminRoleCheck';
import unauthorized from './middleware/unauthorized';
import scheduleExpirationCheck from './expirationCheck';

const DEFAULT_PORT = 8090;

function conditionallyLogRequests(app) {
  if (process.env.LOG_LEVEL <= COIConstants.LOG_LEVEL.INFO) {
    app.use((req, res, next) => {
      const startTime = new Date().getTime();
      res.on('finish', () => {
        const elapsedTime = new Date().getTime() - startTime;
        Log.info(`${req.originalUrl} - ${elapsedTime}ms`);
      });
      next();
    });
  }
}

function configureProxy(app) {
  const TRUST_PROXY = process.env.TRUST_PROXY;
  if (TRUST_PROXY) {
    Log.info(`Using TRUST_PROXY value of ${TRUST_PROXY}`);

    if (TRUST_PROXY.toLowerCase() === 'true') {
      app.set('trust proxy', true);
    }
    else if (TRUST_PROXY.toLowerCase() === 'false') {
      app.set('trust proxy', false);
    }
    else {
      app.set('trust proxy', TRUST_PROXY);
    }
  }
}

export function run() {
  const app = express();
  app.disable('x-powered-by');
  app.set('view engine', 'jade');
  app.set('views', './views');
  configureProxy(app);

  let config;
  try {
    const extensions = require('research-extensions').default;
    extensions.express(app);
    config = extensions.config;
  } catch (e) {
    Log.info('extensions not found');
  }

  conditionallyLogRequests(app);

  app.use('/coi', express.static('client'));
  app.use('/coi/build', (req, res) => { res.sendStatus(NOT_FOUND); }); // Static files that weren't found

  app.use(methodChecker);
  app.use(cookieParser());

  app.use('/coi/auth', renderView('auth'));
  app.get('/coi/health', healthReport);
  app.use('/api', apiAuthentication);
  app.use('/coi', authentication);
  app.use('/coi$', renderView('index'));
  app.use('/coi/$', renderView('index'));
  app.use('/coi/archiveview', renderView('index'));
  app.use('/coi/dashboard', renderView('index'));
  app.use('/coi/disclosure', renderView('index'));
  app.use('/coi/travelLog', renderView('index'));
  app.use('/coi/revise', renderView('index'));
  app.use('/coi/about', renderView('about'));

  app.use('/coi/admin', adminRoleCheck, renderView('admin/admin'));
  app.use('/coi/config', adminRoleCheck, renderView('admin/config'));
  app.use('/coi', unauthorized);

  app.use(bodyParser.json());
  ConfigController.init(app);
  DisclosureController.init(app);
  TravelLogController.init(app);
  ProjectController.init(app);
  FileController.init(app);
  PIController.init(app);
  UserController.init(app);
  AdditionalReviewerController.init(app);
  app.use(ErrorLogger);

  app.set('portNumber', config ? config.port : process.env.COI_PORT || DEFAULT_PORT);

  scheduleExpirationCheck();

  return app;
}
<|endoftext|>


<commit_before>import {
  initializeEnv
} from './config/helpers'

if (
  process.env.NODE_ENV === 'development' ||
  process.env.NODE_ENV === 'production'
) {
  initializeEnv()
}

export default async function startWebpack(env) {
  console.log('startWebpack... Env is: ', env)
  const configs = {
    development: async function startDevelopmentWebpack() {
      console.log('Starting `development` webpack.')
      return await require('./config/development.webpack.config.babel').default()
    },
    production: async function startProductionWebpack() {
      console.log('Starting `production` webpack.')
      return await require('./config/production.webpack.config.babel').default()
    },
    vendors: async function startVendorsWebpack() {
      console.log('Starting `vendors` webpack.')
      return await require('./config/vendors.webpack.config.babel').default()
    },
  }

  return configs[env] ? configs[env]() : configs['development']()
}
<commit_msg>Trying this for travis-ci (hard-coded env in the build log).<commit_after>import { initializeEnv } from './config/helpers'

if (process.env.NODE_ENV === 'development') {
  initializeEnv()
}

export default async function startWebpack(env) {
  console.log('startWebpack... Env is: ', env)
  const configs = {
    development: async function startDevelopmentWebpack() {
      console.log('Starting `development` webpack.')
      return await require('./config/development.webpack.config.babel').default()
    },
    production: async function startProductionWebpack() {
      console.log('Starting `production` webpack.')
      return await require('./config/production.webpack.config.babel').default()
    },
    vendors: async function startVendorsWebpack() {
      console.log('Starting `vendors` webpack.')
      return await require('./config/vendors.webpack.config.babel').default()
    },
  }

  return configs[env] ? configs[env]() : configs['development']()
}
<|endoftext|>


<commit_before>var hasJquery = (window.jQuery !== undefined);
var vv = require('valentine');
var bean = require('bean');
var ender = hasJquery ? $.noConflict() : $;

var PolyClay = function(){};

//----------- Model

PolyClay.Model = function() { };

PolyClay.Model.extend = function(options, methods)
{
	var sub = function()
	{
		this.construct();
		if (this.initialize !== undefined)
			this.initialize.apply(this, arguments);
	};

	sub.prototype = new PolyClay.Model();
	sub.prototype.constructor = sub;
	sub.prototype.__urlroot = options.urlroot;
	sub.prototype.__template = options.template;
	sub.prototype.__properties = [];
	sub.prototype.__calculated = [];
	sub.prototype.__types = {};
	sub.prototype.__collections = {};

	var props = options.properties;
	vv.each(props, function(k)
	{
		PolyClay.Model.addProperty(sub, k, props[k]);
	});
	props = options.calculated;
	vv.each(props, function(k)
	{
		PolyClay.Model.addCalculatedProperty(sub, k);
	});
	props = options.collections;
	vv.each(props, function(k)
	{
		PolyClay.Model.addCollection(sub, k, props[k]);
	});

	vv.each(PolyClay.Common.prototype, function(k)
	{
		sub.prototype[k] = PolyClay.Common.prototype[k];
	});

	vv.each(methods, function(k)
	{
		sub.prototype[k] = methods[k];
	});

	return sub;
};

PolyClay.Model.prototype.construct = function()
{
	this.id = undefined;
	this.attributes = {};
	this.attributesPrev = {};
	this.isNew =  true;
	this.dirty = false;

	var self = this;
	vv.each(this.__collections, function(k)
	{
		self[k] = new self.__collections[k]();
	});
};

PolyClay.validTypes = ['string', 'array', 'number', 'boolean', 'date', 'hash'];
PolyClay.validate = {
	'string': vv.is.str,
	'array': vv.is.arr,
	'number': vv.is.num,
	'boolean': vv.is.bool,
	'date': vv.is.dat,
	'object': vv.is.obj
};
PolyClay.defaults = function(type)
{
	switch(type)
	{
		case 'string': return '';
		case 'array': return [];
		case 'number': return 0;
		case 'boolean': return false;
		case 'date': return new Date();
		case 'object': return {};
	}
	return undefined;
};

PolyClay.Model.prototype.valid = function()
{
	var valid = true;
	var props = this.prototype.__properties ;
	var types = this.prototype.__types;
	this.errors = {};
	for (var i=0, len=props.length; i<len; i++)
	{
		var p = props[i];
		if (!PolyClay.validate[types[p]](this.attributes[p]))
		{
			this.errors[p] = true;
			valid = false;
		}
	}
	return valid;
};

PolyClay.Model.addProperty = function(obj, name, type)
{
	if (PolyClay.validTypes.indexOf(type) < 0)
		throw('type '+type+' invalid; see documentation for types');

	obj.prototype.__properties.push(name);
	obj.prototype.__types[name] = type;
	PolyClay.Model.makeGetterSetter(obj, name);
};

// prop() is getter
// prop(newval) is setter
PolyClay.Model.makeGetterSetter = function(obj, propname)
{
	var getterSetter = function()
	{
		// getter
		if (arguments.length === 0)
		{
			if (this.attributes[propname] === undefined)
				return PolyClay.defaults(obj.prototype.__types[propname]);
			else
				return this.attributes[propname];
		}
		// setter
		var newval = arguments["0"];
		if ((obj.prototype.__types[propname] == 'date') && vv.is.str(newval))
			newval = new Date(newval);
		if (!PolyClay.validate[obj.prototype.__types[propname]](newval))
		{
			console.error(propname+': type of '+newval+' not '+obj.prototype.__types[propname]);
			return;
		}
		this.attributesPrev[propname] = this.attributes[propname];
		this.attributes[propname] = newval;
		var silent = ( (arguments.length > 1) ? arguments["1"] : false);
		this.dirty = true;
		if (!silent)
		{
			this.fire('change:'+propname);
			this.fire('change');
		}
	};
	obj.prototype[propname] = getterSetter;
};

PolyClay.Model.addCalculatedProperty = function(obj, name)
{
	obj.prototype.__calculated.push(name);
	PolyClay.Model.makeCalculatedGetterSetter(obj, name);
};

PolyClay.Model.makeCalculatedGetterSetter = function(obj, propname)
{
	var result = function()
	{
		// getter
		if (arguments.length === 0)
			return this.attributes[propname];
		// setter
		this.attributes[propname] = arguments["0"];
		var silent = ( (arguments.length > 1) ? arguments["1"] : false);
		if (!silent)
		{
			this.fire('change:'+propname);
			this.fire('change');
		}
	};
	obj.prototype[propname] = result;
}

PolyClay.Model.addCollection = function(obj, name, constructor)
{
	obj.prototype.__collections[name] = constructor;
};

PolyClay.Model.prototype.template = function()
{
	if (arguments.length === 1)
		this.__template = arguments["0"];
	else
		return this.__template;
};

PolyClay.Model.prototype.render = function(element, tmpl, append)
{
	var self = this;
	var template = tmpl ? tmpl : this.template();
	var destination = element ? element : this.element();
	if (!template || !destination)
		return;

	beam.render(template, this.toJSON(), function(rendered)
	{
		if (!append) $(destination).empty();
		$(destination).append(rendered);
		self.fire('render');
	});
};

PolyClay.Model.prototype.renderToString = function(tmpl)
{
	var template = tmpl ? tmpl : this.template();
	if (!template) return '';
	return beam[template](this.toJSON());
};

PolyClay.Model.prototype.toJSON = function()
{
	return this.attributes;
};

PolyClay.Model.prototype.urlroot = function()
{
	if (arguments.length === 1)
		this.__urlroot = arguments["0"];
	else
		return this.__urlroot;
};

PolyClay.Model.prototype.constructURL = function()
{
	if (this.id === undefined)
		return this.urlroot();

	return this.urlroot() + '/' + this.id;
};

PolyClay.Model.prototype.update = function(attr, silent)
{
	// Note: doesn't handle calculated properties. Special handling for id is hacky.
	var self = this;
	var events = ['change'];
	for (var k in attr)
	{
		if (k === 'id')
			self.id = attr[k];
		else if (attr.hasOwnProperty(k) && (self[k] !== undefined))
		{
			self[k].call(self, attr[k], true);
			events.push('change:'+k);
		}
	}
	if (!silent)
	{
		vv.each(events, function(e)
		{
			self.fire(e);
		});
	}
};

PolyClay.Model.prototype.save = function()
{
	var self = this;
	var failure, success, proplist;

	if (arguments.length === 1)
		proplist = arguments["0"];
	else
		proplist = self.__properties;

	var props = {};
	for (var i=0, len=proplist.length; i<len; i++)
		props[proplist[i]] = self.attributes[proplist[i]];

	var method = (self.id === undefined) ? 'POST' : 'PUT';

	var winning = function(req)
	{
		self.dirty = false;
		self.attributesPrev = {};
		self.update(req);
		self.fire('save');
	};

	var losing = function(jqXHR, textStatus, errorThrown)
	{
		// TODO
	};

	$.ajax(
	{
		url: self.constructURL(),
		method: method,
		type: 'json',
		data: JSON.stringify(props),
		success: winning,
		error: losing
	});
};

PolyClay.Model.prototype.destroy = function(success, failure)
{
	var self = this;
	$.ajax(
	{
		url: this.constructURL(),
		method: 'DELETE',
		type: 'json',
		success: function(data, textStatus, jqXHR)
		{
			self.fire('destroy');
		}
	});
};

PolyClay.Model.prototype.load = function(success, failure)
{
	if (this.id === undefined)
		throw('cannot load object without an id');
	var self = this;
	$.ajax(
	{
		url: this.constructURL() + '.json',
		type: 'json',
		success: function(req)
		{
			self.update(req);
			self.fire('load');
		}
	});
};

//----------- Collection

PolyClay.Collection = function(){};

PolyClay.Collection.extend = function(options, methods)
{
	var sub = function()
	{
		this.construct();
		if (this.initialize !== undefined)
			this.initialize.apply(this, arguments);
	};

	sub.prototype = new PolyClay.Collection();
	sub.prototype.constructor = sub;
	sub.prototype.__urlroot = options.urlroot;
	sub.prototype.__model = options.model;

	vv.each(PolyClay.Common.prototype, function(k)
	{
		sub.prototype[k] = PolyClay.Common.prototype[k];
	});
	vv.each(methods, function(k)
	{
		sub.prototype[k] = methods[k];
	});

	return sub;
};

PolyClay.Collection.prototype.construct = function()
{
	this.items = [];
};

PolyClay.Collection.prototype.push = function(item, silent)
{
	this.items.push(item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.unshift = function(item, silent)
{
	this.items.unshift(item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.reset = function(data, silent)
{
	var item;
	this.items.length = 0;
	for (var i=0,len=data.length; i<len; i++)
	{
		item = new this.__model();
		item.update(data[i]);
		this.items.push(item);
	}
	if (!silent) this.fire('reset');
};

PolyClay.Collection.prototype.remove = function(item, silent)
{
	var idx = this.items.indexOf(item);
	this.items.splice(idx, 1);
	if (!silent) this.fire('remove');
};

PolyClay.Collection.prototype.insert = function(item, index, silent)
{
	this.items.splice(index, 0, item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.url = function()
{
	if (arguments.length === 1)
		this.__url = arguments["0"];
	else
		return this.__url;
};

PolyClay.Collection.prototype.fetch = function(success, failure)
{
	var url = this.url();
	if (url === undefined)
		return;
	var self = this;

	var winning = function(data, textStatus, jqXHR)
	{
		self.reset(data);
	};
	var losing = function(jqXHR, textStatus, errorThrown)
	{
		// TODO
	};
	$.ajax(
	{
		url: this.url(),
		method: 'GET',
		type: 'json',
		success: winning,
		error: losing
	});
};

PolyClay.Collection.prototype.render = function()
{
	var into = this.element();
	if (into === undefined) return;
	var el = $(into);
	el.empty();
	for (var i=0, len=this.items.length; i<len; i++)
	{
		this.items[i].render(into, this.items[i].template(), true);
	}
};

//-----------  methods both prototypes have in common

PolyClay.Common = function(){};

PolyClay.Common.prototype.watch = function(event, callback)
{
	bean.add(this, event, callback);
};
PolyClay.Common.prototype.unwatch = function(event)
{
	bean.remove(this, event);
};
PolyClay.Common.prototype.fire = function(event)
{
	bean.fire(this, event);
};
PolyClay.Common.prototype.element = function()
{
	if (arguments.length === 1)
		this.__element = arguments['0'];
	else
		return this.__element;
};
PolyClay.Common.prototype.$ = function(child)
{
	if (child)
		return $(this.element() + ' '+ child);
	else
		return $(this.element());
};
<commit_msg>Collections can now be heterogeneous.<commit_after>var hasJquery = (window.jQuery !== undefined);
var vv = require('valentine');
var bean = require('bean');
var ender = hasJquery ? $.noConflict() : $;

var PolyClay = function(){};

//----------- Model

PolyClay.Model = function() { };

PolyClay.Model.extend = function(options, methods)
{
	var sub = function()
	{
		this.construct();
		if (this.initialize !== undefined)
			this.initialize.apply(this, arguments);
	};

	sub.prototype = new PolyClay.Model();
	sub.prototype.constructor = sub;
	sub.prototype.__urlroot = options.urlroot;
	sub.prototype.__template = options.template;
	sub.prototype.__properties = [];
	sub.prototype.__calculated = [];
	sub.prototype.__types = {};
	sub.prototype.__collections = {};

	var props = options.properties;
	vv.each(props, function(k)
	{
		PolyClay.Model.addProperty(sub, k, props[k]);
	});
	props = options.calculated;
	vv.each(props, function(k)
	{
		PolyClay.Model.addCalculatedProperty(sub, k);
	});
	props = options.collections;
	vv.each(props, function(k)
	{
		PolyClay.Model.addCollection(sub, k, props[k]);
	});

	vv.each(PolyClay.Common.prototype, function(k)
	{
		sub.prototype[k] = PolyClay.Common.prototype[k];
	});

	vv.each(methods, function(k)
	{
		sub.prototype[k] = methods[k];
	});

	return sub;
};

PolyClay.Model.prototype.construct = function()
{
	this.id = undefined;
	this.attributes = {};
	this.attributesPrev = {};
	this.isNew =  true;
	this.dirty = false;

	var self = this;
	vv.each(this.__collections, function(k)
	{
		self[k] = new self.__collections[k]();
	});
};

PolyClay.validTypes = ['string', 'array', 'number', 'boolean', 'date', 'hash'];
PolyClay.validate = {
	'string': vv.is.str,
	'array': vv.is.arr,
	'number': vv.is.num,
	'boolean': vv.is.bool,
	'date': vv.is.dat,
	'cromag': vv.is.dat,
	'object': vv.is.obj,
};
PolyClay.defaults = function(type)
{
	switch(type)
	{
		case 'string': return '';
		case 'array': return [];
		case 'number': return 0;
		case 'boolean': return false;
		case 'date': return new Date();
		case 'cromag': return new Cromag();
		case 'object': return {};
	}
	return undefined;
};

PolyClay.Model.prototype.valid = function()
{
	var valid = true;
	var props = this.prototype.__properties ;
	var types = this.prototype.__types;
	this.errors = {};
	for (var i=0, len=props.length; i<len; i++)
	{
		var p = props[i];
		if (!PolyClay.validate[types[p]](this.attributes[p]))
		{
			this.errors[p] = true;
			valid = false;
		}
	}
	return valid;
};

PolyClay.Model.addProperty = function(obj, name, type)
{
	if (PolyClay.validTypes.indexOf(type) < 0)
		throw('type '+type+' invalid; see documentation for types');

	obj.prototype.__properties.push(name);
	obj.prototype.__types[name] = type;
	PolyClay.Model.makeGetterSetter(obj, name);
};

// prop() is getter
// prop(newval) is setter
PolyClay.Model.makeGetterSetter = function(obj, propname)
{
	var getterSetter = function()
	{
		// getter
		if (arguments.length === 0)
		{
			if (this.attributes[propname] === undefined)
				return PolyClay.defaults(obj.prototype.__types[propname]);
			else
				return this.attributes[propname];
		}
		// setter
		var newval = arguments["0"];
		if ((obj.prototype.__types[propname] == 'date') && vv.is.str(newval))
			newval = new Date(newval);
		if ((obj.prototype.__types[propname] == 'cromag') && vv.is.str(newval))
			newval = new Cromag(newval);
		if (!PolyClay.validate[obj.prototype.__types[propname]](newval))
		{
			console.error(propname+': type of '+newval+' not '+obj.prototype.__types[propname]);
			return;
		}
		this.attributesPrev[propname] = this.attributes[propname];
		this.attributes[propname] = newval;
		var silent = ( (arguments.length > 1) ? arguments["1"] : false);
		this.dirty = true;
		if (!silent)
		{
			this.fire('change:'+propname);
			this.fire('change');
		}
	};
	obj.prototype[propname] = getterSetter;
};

PolyClay.Model.addCalculatedProperty = function(obj, name)
{
	obj.prototype.__calculated.push(name);
	PolyClay.Model.makeCalculatedGetterSetter(obj, name);
};

PolyClay.Model.makeCalculatedGetterSetter = function(obj, propname)
{
	var result = function()
	{
		// getter
		if (arguments.length === 0)
			return this.attributes[propname];
		// setter
		this.attributes[propname] = arguments["0"];
		var silent = ( (arguments.length > 1) ? arguments["1"] : false);
		if (!silent)
		{
			this.fire('change:'+propname);
			this.fire('change');
		}
	};
	obj.prototype[propname] = result;
}

PolyClay.Model.addCollection = function(obj, name, constructor)
{
	obj.prototype.__collections[name] = constructor;
};

PolyClay.Model.prototype.template = function()
{
	if (arguments.length === 1)
		this.__template = arguments["0"];
	else
		return this.__template;
};

PolyClay.Model.prototype.render = function(element, tmpl, append)
{
	var self = this;
	var template = tmpl ? tmpl : this.template();
	var destination = element ? element : this.element();
	if (!template || !destination)
		return;

	beam.render(template, this.toJSON(), function(rendered)
	{
		if (!append) $(destination).empty();
		$(destination).append(rendered);
		self.fire('render');
	});
};

PolyClay.Model.prototype.renderToString = function(tmpl)
{
	var template = tmpl ? tmpl : this.template();
	if (!template) return '';
	return beam[template](this.toJSON());
};

PolyClay.Model.prototype.toJSON = function()
{
	return this.attributes;
};

PolyClay.Model.prototype.urlroot = function()
{
	if (arguments.length === 1)
		this.__urlroot = arguments["0"];
	else
		return this.__urlroot;
};

PolyClay.Model.prototype.constructURL = function()
{
	if (this.id === undefined)
		return this.urlroot();

	return this.urlroot() + '/' + this.id;
};

PolyClay.Model.prototype.update = function(attr, silent)
{
	// Note: doesn't handle calculated properties on purpose.
	// TODO: Special handling for id is hacky.
	var self = this;
	var events = ['change'];
	for (var k in attr)
	{
		if (k === 'id')
			self.id = attr[k];
		else if (attr.hasOwnProperty(k) && (self[k] !== undefined))
		{
			self[k].call(self, attr[k], true);
			events.push('change:'+k);
		}
	}
	if (!silent)
	{
		vv.each(events, function(e)
		{
			self.fire(e);
		});
	}
};

PolyClay.Model.prototype.save = function()
{
	var self = this;
	var failure, success, proplist;

	if (arguments.length === 1)
		proplist = arguments["0"];
	else
		proplist = self.__properties;

	var props = {};
	for (var i=0, len=proplist.length; i<len; i++)
		props[proplist[i]] = self.attributes[proplist[i]];

	var method = (self.id === undefined) ? 'POST' : 'PUT';

	var winning = function(req)
	{
		self.dirty = false;
		self.attributesPrev = {};
		self.update(req);
		self.fire('save');
	};

	$.ajax(
	{
		url: self.constructURL(),
		method: method,
		type: 'json',
		data: JSON.stringify(props),
		success: winning,
	});
};

PolyClay.Model.prototype.destroy = function(success, failure)
{
	var self = this;
	$.ajax(
	{
		url: this.constructURL(),
		method: 'DELETE',
		type: 'json',
		success: function(data, textStatus, jqXHR)
		{
			self.fire('destroy');
		}
	});
};

PolyClay.Model.prototype.load = function(success, failure)
{
	if (this.id === undefined)
		throw('cannot load object without an id');
	var self = this;
	$.ajax(
	{
		url: this.constructURL() + '.json',
		type: 'json',
		success: function(req)
		{
			self.update(req);
			self.fire('load');
		}
	});
};

//----------- Collection

PolyClay.Collection = function(){};

PolyClay.Collection.extend = function(options, methods)
{
	var sub = function()
	{
		this.construct();
		if (this.initialize !== undefined)
			this.initialize.apply(this, arguments);
	};

	sub.prototype = new PolyClay.Collection();
	sub.prototype.constructor = sub;
	sub.prototype.__urlroot = options.urlroot;
	if (vv.is.fun(options.model))
	{
		sub.prototype.__model = options.model;
	}
	else if (vv.is.arr(options.model))
	{
		// list of possible types: name, constructor pairs
		sub.prototype.__model = {};
		for (var i=0, len=options.model.length; i<len; i++)
		{
			var item = options.model[i];
			sub.prototype.__model[item[0]] = item[1];
		}
	}

	vv.each(PolyClay.Common.prototype, function(k)
	{
		sub.prototype[k] = PolyClay.Common.prototype[k];
	});
	vv.each(methods, function(k)
	{
		sub.prototype[k] = methods[k];
	});

	return sub;
};

PolyClay.Collection.prototype.construct = function()
{
	this.items = [];
};

PolyClay.Collection.prototype.push = function(item, silent)
{
	this.items.push(item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.unshift = function(item, silent)
{
	this.items.unshift(item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.reset = function(data, silent)
{
	var isHomogenous = vv.is.fun(this.__model);
	var item;
	this.items.length = 0;
	for (var i=0,len=data.length; i<len; i++)
	{
		if (isHomogenous)
			item = new this.__model();
		else
		{
			var type = data[i].type;
			if (!vv.is.fun(this.__model[type]))
			{
				console.error('collection does not know how to construct '+type);
				continue;
			}
			item = new this.__model[type]();
			delete data[i]['type'];
		}
		item.update(data[i]);
		this.items.push(item);
	}
	if (!silent) this.fire('reset');
};

PolyClay.Collection.prototype.remove = function(item, silent)
{
	var idx = this.items.indexOf(item);
	this.items.splice(idx, 1);
	if (!silent) this.fire('remove');
};

PolyClay.Collection.prototype.insert = function(item, index, silent)
{
	this.items.splice(index, 0, item);
	if (!silent) this.fire('add');
};

PolyClay.Collection.prototype.url = function()
{
	if (arguments.length === 1)
		this.__url = arguments["0"];
	else
		return this.__url;
};

PolyClay.Collection.prototype.fetch = function(success, failure)
{
	var url = this.url();
	if (url === undefined)
		return;
	var self = this;

	var winning = function(data, textStatus, jqXHR)
	{
		self.reset(data);
	};
	var losing = function(jqXHR, textStatus, errorThrown)
	{
		// TODO
	};
	$.ajax(
	{
		url: this.url(),
		method: 'GET',
		type: 'json',
		success: winning,
		error: losing
	});
};

PolyClay.Collection.prototype.render = function()
{
	var into = this.element();
	if (into === undefined) return;
	var el = $(into);
	el.empty();
	for (var i=0, len=this.items.length; i<len; i++)
	{
		this.items[i].render(into, this.items[i].template(), true);
	}
};

//-----------  methods both prototypes have in common

PolyClay.Common = function(){};

PolyClay.Common.prototype.watch = function(event, callback)
{
	bean.add(this, event, callback);
};
PolyClay.Common.prototype.unwatch = function(event)
{
	bean.remove(this, event);
};
PolyClay.Common.prototype.fire = function(event)
{
	bean.fire(this, event);
};
PolyClay.Common.prototype.element = function()
{
	if (arguments.length === 1)
		this.__element = arguments['0'];
	else
		return this.__element;
};
PolyClay.Common.prototype.$ = function(child)
{
	if (child)
		return $(this.element() + ' '+ child);
	else
		return $(this.element());
};
<|endoftext|>