<commit_before>(function() {

  var app = angular.module('facility_view', []);

  // Capitalises the first letter (adapted from http://codepen.io/WinterJoey/pen/sfFaK)
  app.filter('capitalise', function() {
      return function(input, all) {
        return (!!input) ? input.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}) : '';
      }
  });

  // Filter to produce nice file size formatting (adapted from https://gist.github.com/yrezgui/5653591)
  app.filter('filesize', function () {
    var units = [
      'bytes',
      'KB',
      'MB',
      'GB',
      'TB',
      'PB'
    ];

    return function( bytes, precision ) {
      if ( isNaN( parseFloat( bytes )) || ! isFinite( bytes ) ) {
        return '?';
      }

      var unit = 0;

      while ( bytes >= 1024 ) {
        bytes /= 1024;
        unit ++;
      }

      return bytes.toFixed( + precision ) + ' ' + units[ unit ];
    };
  });

  app.controller('FacilityCtrl', function($scope, $http, $interval, $log, $filter) {

    // Whether to show the "no data" alert
    $scope.showDataUnvailableAlert = function() {
      if ($scope.loading) {
        return false;
      } else if (typeof $scope.datasets !== 'undefined') {
        return $scope.datasets.length === 0;
      } else {
        return true;
      }
    }

    // Whether to show the facility selector
    $scope.showFacilitySelector = function() {
      return ($scope.facilities.length > 1);
    }
    // Toggle the facility selector
    $scope.selectFacility = function(id, name) {
      $scope.selectedFacility = id;
      $scope.selectedFacilityName = name;
      $scope.currentFetchLimit = $scope.defaultFetchLimit;
      $scope.fetchFacilityData(0, $scope.currentFetchLimit);
    };
    // Check which facility is selected
    $scope.isFacilitySelected = function(id) {
      return $scope.selectedFacility === id;
    }

    // Toggle data view selector
    $scope.selectDataView = function(index) {
      $scope.selectedDataView = index;
    }
    // Check which data view is selected
    $scope.isDataViewSelected = function(index) {
      return $scope.selectedDataView === index;
    }

    // Toggle file list visibility
    $scope.toggleFileList = function(id) {
       if ($scope.visibleFileList === id) {
         delete $scope.visibleFileList;
       } else {
         $scope.visibleFileList = id;
       }
    }
    // Check if file list is visible
    $scope.isFileListVisible = function(id) {
      return $scope.visibleFileList === id;
    }
    $scope.unsetFileListVisibility = function() {
      delete $scope.visibleFileList;
    }

    // Reset filter form
    $scope.filterFormReset = function() {
       delete $scope.search_owner;
       delete $scope.search_experiment;
       delete $scope.search_instrument;
    }
    // Check if filters are active
    $scope.filtersActive = function() {
      if (typeof $scope.search_owner !== 'undefined' && $scope.search_owner.owner.name) {
        return true;
      } else if (typeof $scope.search_experiment !== 'undefined' && $scope.search_experiment.parent_experiment.title) {
        return true;
      } else if (typeof $scope.search_instrument !== 'undefined' && $scope.search_instrument.instrument.name) {
        return true;
      }
    }

    // Load more entries
    $scope.loadMoreEntries = function(increment) {
      if ($scope.currentFetchLimit >= $scope.totalDatasets) {
        return;
      }
      if ($scope.currentFetchLimit + increment > $scope.totalDatasets) {
        $scope.currentFetchLimit = $scope.totalDatasets;
      } else {
        $scope.currentFetchLimit += increment;
      }
      $scope.fetchFacilityData(0, $scope.currentFetchLimit);
    }

    // Fetch the list of facilities available to the user and facilities data
    function initialiseFacilitiesData() {
      $http.get('/facility/fetch_facilities_list/').success(function(data) {
        $scope.facilities = data;
        if ($scope.facilities.length > 0) { // If the user is allowed to manage any facilities...
          $scope.selectedFacility = $scope.facilities[0].id;
          $scope.selectedFacilityName = $scope.facilities[0].name;
          $scope.fetchFacilityData(0, $scope.defaultFetchLimit);
        }
      });
    }

    // Fetch data for facility
    $scope.fetchFacilityData = function(startIndex, endIndex) {
      $scope.loading = true;
      $http.get('/facility/fetch_data/'+$scope.selectedFacility+'/count/').success(function(data) {
        $scope.totalDatasets = data.facility_data_count;
        if ($scope.currentFetchLimit > $scope.totalDatasets) {
          $scope.defaultFetchLimit = $scope.totalDatasets;
          $scope.currentFetchLimit = $scope.defaultFetchLimit;
        }
      });
      $http.get('/facility/fetch_data/'+$scope.selectedFacility+'/'+startIndex+'/'+endIndex+'/').success(function(data) {
        $scope.loading = false;
        $scope.datasets = data;
        if ($scope.datasets.length > 0) {
          $scope.dataByUser = groupByUser($scope.datasets);
          $scope.dataByInstrument = groupByInstrument($scope.datasets);
        } else {
          $scope.dataByUser = [];
          $scope.dataByInstrument = [];
        }
      });
    }

    // Group facilities data by user
    function groupByUser(data) {
      // Sort by user ID
      data.sort(function(a,b) {a.owner.id - b.owner.id});

      var result = [];
      var tmp = {"owner":data[0].owner};
      tmp['datasets']=[];
      for (var i = 0; i < data.length; i++) {
        if (tmp.owner.id !== data[i].owner.id) {
          result.push(tmp);
          tmp = {"owner":data[i].owner};
          tmp['datasets'] = [];
        }
        var dataset = {};
        for (var key in data[i]) {
           if (key !== "owner") {
             dataset[key] = data[i][key];
           }
        }
        tmp['datasets'].push(dataset);
      }
      result.push(tmp);
      return result;
    }

    // Group facilities data by user
    function groupByInstrument(data) {
      // Sort by user ID
      data.sort(function(a,b) {a.instrument.id - b.instrument.id});

      var result = [];
      var tmp = {"instrument":data[0].instrument};
      tmp['datasets']=[];
      for (var i = 0; i < data.length; i++) {
        if (tmp.instrument.id !== data[i].instrument.id) {
          result.push(tmp);
          tmp = {"instrument":data[i].instrument};
          tmp['datasets'] = [];
        }
        var dataset = {};
        for (var key in data[i]) {
           if (key !== "instrument") {
             dataset[key] = data[i][key];
           }
        }
        tmp['datasets'].push(dataset);
      }
      result.push(tmp);
      return result;
    }

    // Refresh polling timer
    $interval(function() {
      if ($scope.refreshCountdown > 0 && $scope.refreshInterval > 0) {
        $scope.refreshCountdown--;
      } else if ($scope.refreshInterval > 0) {
        $scope.fetchFacilityData(0, $scope.currentFetchLimit);
        $scope.refreshCountdown = $scope.refreshInterval;
      }
    }, 1000);

    // Set the update interval
    $scope.setRefreshInterval = function(interval) {
      $scope.refreshInterval = interval;
      $scope.refreshCountdown = interval;
    }

    // Format the countdown for the view (mm:ss)
    $scope.refreshCountdownFmt = function() {
      var minutes = Math.floor($scope.refreshCountdown / 60);
      var seconds = $scope.refreshCountdown - minutes * 60;
      if (minutes < 10) {
        var strMins = "0"+minutes;
      } else {
        var strMins = minutes;
      }
      if (seconds < 10) {
        var strSecs = "0"+seconds;
      } else {
        var strSecs = seconds;
      }
      return strMins + ":" + strSecs;
    }

    // Set default settings
    $scope.defaultFetchLimit = 50;
    $scope.currentFetchLimit = $scope.defaultFetchLimit;
    $scope.facilities = [];
    $scope.selectedDataView = 1;
    $scope.refreshInterval = 0;
    $scope.refreshCountdown = 0;

    // Do initial data fetch
    initialiseFacilitiesData();

  });

})();
<commit_msg>"load more" button added<commit_after>(function() {

  var app = angular.module('facility_view', []);

  // Capitalises the first letter (adapted from http://codepen.io/WinterJoey/pen/sfFaK)
  app.filter('capitalise', function() {
      return function(input, all) {
        return (!!input) ? input.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}) : '';
      }
  });

  // Filter to produce nice file size formatting (adapted from https://gist.github.com/yrezgui/5653591)
  app.filter('filesize', function () {
    var units = [
      'bytes',
      'KB',
      'MB',
      'GB',
      'TB',
      'PB'
    ];

    return function( bytes, precision ) {
      if ( isNaN( parseFloat( bytes )) || ! isFinite( bytes ) ) {
        return '?';
      }

      var unit = 0;

      while ( bytes >= 1024 ) {
        bytes /= 1024;
        unit ++;
      }

      return bytes.toFixed( + precision ) + ' ' + units[ unit ];
    };
  });

  app.controller('FacilityCtrl', function($scope, $http, $interval, $log, $filter) {

    // Whether to show the "no data" alert
    $scope.showDataUnvailableAlert = function() {
      if ($scope.loading) {
        return false;
      } else if (typeof $scope.datasets !== 'undefined') {
        return $scope.datasets.length === 0;
      } else {
        return true;
      }
    }

    // Whether to show the facility selector
    $scope.showFacilitySelector = function() {
      return ($scope.facilities.length > 1);
    }
    // Toggle the facility selector
    $scope.selectFacility = function(id, name) {
      $scope.selectedFacility = id;
      $scope.selectedFacilityName = name;
      $scope.currentFetchLimit = $scope.defaultFetchLimit;
      $scope.fetchFacilityData(0, $scope.currentFetchLimit);
    };
    // Check which facility is selected
    $scope.isFacilitySelected = function(id) {
      return $scope.selectedFacility === id;
    }

    // Toggle data view selector
    $scope.selectDataView = function(index) {
      $scope.selectedDataView = index;
    }
    // Check which data view is selected
    $scope.isDataViewSelected = function(index) {
      return $scope.selectedDataView === index;
    }

    // Toggle file list visibility
    $scope.toggleFileList = function(id) {
       if ($scope.visibleFileList === id) {
         delete $scope.visibleFileList;
       } else {
         $scope.visibleFileList = id;
       }
    }
    // Check if file list is visible
    $scope.isFileListVisible = function(id) {
      return $scope.visibleFileList === id;
    }
    $scope.unsetFileListVisibility = function() {
      delete $scope.visibleFileList;
    }

    // Reset filter form
    $scope.filterFormReset = function() {
       delete $scope.search_owner;
       delete $scope.search_experiment;
       delete $scope.search_instrument;
    }
    // Check if filters are active
    $scope.filtersActive = function() {
      if (typeof $scope.search_owner !== 'undefined' && $scope.search_owner.owner.name) {
        return true;
      } else if (typeof $scope.search_experiment !== 'undefined' && $scope.search_experiment.parent_experiment.title) {
        return true;
      } else if (typeof $scope.search_instrument !== 'undefined' && $scope.search_instrument.instrument.name) {
        return true;
      }
    }

    // Load more entries
    $scope.loadMoreEntries = function(increment) {
      if ($scope.currentFetchLimit >= $scope.totalDatasets) {
        return;
      }
      if ($scope.currentFetchLimit + increment > $scope.totalDatasets) {
        $scope.currentFetchLimit = $scope.totalDatasets;
      } else {
        $scope.currentFetchLimit += increment;
      }
      $scope.fetchFacilityData(0, $scope.currentFetchLimit);
    }

    // Fetch the list of facilities available to the user and facilities data
    function initialiseFacilitiesData() {
      $http.get('/facility/fetch_facilities_list/').success(function(data) {
        $scope.facilities = data;
        if ($scope.facilities.length > 0) { // If the user is allowed to manage any facilities...
          $scope.selectedFacility = $scope.facilities[0].id;
          $scope.selectedFacilityName = $scope.facilities[0].name;
          $scope.fetchFacilityData(0, $scope.defaultFetchLimit);
        }
      });
    }

    // Fetch data for facility
    $scope.fetchFacilityData = function(startIndex, endIndex) {
      $scope.loading = true;
      $http.get('/facility/fetch_data/'+$scope.selectedFacility+'/count/').success(function(data) {
        $scope.totalDatasets = data.facility_data_count;
        if ($scope.currentFetchLimit > $scope.totalDatasets) {
          $scope.currentFetchLimit = $scope.totalDatasets;
        }
      });
      $http.get('/facility/fetch_data/'+$scope.selectedFacility+'/'+startIndex+'/'+endIndex+'/').success(function(data) {
        $scope.loading = false;
        $scope.datasets = data;
        if ($scope.datasets.length > 0) {
          $scope.dataByUser = groupByUser($scope.datasets);
          $scope.dataByInstrument = groupByInstrument($scope.datasets);
        } else {
          $scope.dataByUser = [];
          $scope.dataByInstrument = [];
        }
      });
    }

    // Group facilities data by user
    function groupByUser(data) {
      // Sort by user ID
      data.sort(function(a,b) {a.owner.id - b.owner.id});

      var result = [];
      var tmp = {"owner":data[0].owner};
      tmp['datasets']=[];
      for (var i = 0; i < data.length; i++) {
        if (tmp.owner.id !== data[i].owner.id) {
          result.push(tmp);
          tmp = {"owner":data[i].owner};
          tmp['datasets'] = [];
        }
        var dataset = {};
        for (var key in data[i]) {
           if (key !== "owner") {
             dataset[key] = data[i][key];
           }
        }
        tmp['datasets'].push(dataset);
      }
      result.push(tmp);
      return result;
    }

    // Group facilities data by user
    function groupByInstrument(data) {
      // Sort by user ID
      data.sort(function(a,b) {a.instrument.id - b.instrument.id});

      var result = [];
      var tmp = {"instrument":data[0].instrument};
      tmp['datasets']=[];
      for (var i = 0; i < data.length; i++) {
        if (tmp.instrument.id !== data[i].instrument.id) {
          result.push(tmp);
          tmp = {"instrument":data[i].instrument};
          tmp['datasets'] = [];
        }
        var dataset = {};
        for (var key in data[i]) {
           if (key !== "instrument") {
             dataset[key] = data[i][key];
           }
        }
        tmp['datasets'].push(dataset);
      }
      result.push(tmp);
      return result;
    }

    // Refresh polling timer
    $interval(function() {
      if ($scope.refreshCountdown > 0 && $scope.refreshInterval > 0) {
        $scope.refreshCountdown--;
      } else if ($scope.refreshInterval > 0) {
        $scope.fetchFacilityData(0, $scope.currentFetchLimit);
        $scope.refreshCountdown = $scope.refreshInterval;
      }
    }, 1000);

    // Set the update interval
    $scope.setRefreshInterval = function(interval) {
      $scope.refreshInterval = interval;
      $scope.refreshCountdown = interval;
    }

    // Format the countdown for the view (mm:ss)
    $scope.refreshCountdownFmt = function() {
      var minutes = Math.floor($scope.refreshCountdown / 60);
      var seconds = $scope.refreshCountdown - minutes * 60;
      if (minutes < 10) {
        var strMins = "0"+minutes;
      } else {
        var strMins = minutes;
      }
      if (seconds < 10) {
        var strSecs = "0"+seconds;
      } else {
        var strSecs = seconds;
      }
      return strMins + ":" + strSecs;
    }

    // Set default settings
    $scope.defaultFetchLimit = 50;
    $scope.currentFetchLimit = $scope.defaultFetchLimit;
    $scope.facilities = [];
    $scope.selectedDataView = 1;
    $scope.refreshInterval = 0;
    $scope.refreshCountdown = 0;

    // Do initial data fetch
    initialiseFacilitiesData();

  });

})();
<|endoftext|>


<commit_before>/* @flow weak */

/**
 * OfflineMobile Android
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import { View } from 'react-native';

import { generateUUID } from '../database';
import { Button } from '../widgets';
import globalStyles from '../globalStyles';
import { GenericTablePage } from './GenericTablePage';

/**
* Renders the page for displaying CustomerInvoices.
* @prop   {Realm}               database      App wide database.
* @prop   {func}                navigateTo    CallBack for navigation stack.
* @state  {Realm.Results}       transactions  Filtered to have only customer_invoice.
*/
export class CustomerInvoicesPage extends GenericTablePage {
  constructor(props) {
    super(props);
    this.state.transactions = props.database.objects('Transaction')
                                            .filtered('type == "customer_invoice"');
    this.state.sortBy = 'otherParty.name';
    this.columns = COLUMNS;
    this.getUpdatedData = this.getUpdatedData.bind(this);
    this.onNewInvoice = this.onNewInvoice.bind(this);
    this.onRowPress = this.onRowPress.bind(this);
    this.renderCell = this.renderCell.bind(this);
  }

  onNewInvoice() {
    let invoice;
    this.props.database.write(() => {
      invoice = this.props.database.create('Transaction', {
        id: generateUUID(),
        serialNumber: '1',
        entryDate: new Date(),
        type: 'customer_invoice',
        status: 'new',
        comment: 'Testing sync',
        otherParty: this.props.database.objects('Name')[0],
      });
    });
    this.props.navigateTo('customerInvoice', 'New Invoice', {
      invoice: invoice,
    });
  }

  onRowPress(invoice) {
    this.props.navigateTo('customerInvoice', `Invoice ${invoice.serialNumber}`, { invoice });
  }

  /**
   * Returns updated data according to searchTerm, sortBy and isAscending. Special
   * case for otherParty.name as realm does not allow sorting on object properties
   * properties.
   */
  getUpdatedData(searchTerm, sortBy, isAscending) {
    let data = this.state.transactions.filtered(`otherParty.name CONTAINS[c] "${searchTerm}"`);
    if (sortBy === 'otherParty.name') {
      // Convert to javascript array obj then sort with standard array functions.
      data = data.slice().sort((a, b) => a.otherParty.name.localeCompare(b.otherParty.name));
      if (!isAscending) data.reverse();
    } else {
      data = data.sorted(sortBy, !isAscending); // 2nd arg: reverse sort
    }
    return data;
  }

  renderCell(key, invoice) {
    switch (key) {
      default:
      case 'otherParty.name':
        return invoice.otherParty && invoice.otherParty.name;
      case 'id':
        return invoice.id;
      case 'status':
        return invoice.status;
      case 'entryDate':
        return invoice.entryDate.toDateString();
      case 'comment':
        return invoice.comment;
    }
  }

  render() {
    return (
      <View style={globalStyles.pageContentContainer}>
        <View style={globalStyles.container}>
          <View style={globalStyles.pageTopSectionContainer}>
            {this.renderSearchBar()}
            <Button
              style={globalStyles.button}
              textStyle={globalStyles.buttonText}
              text="New Invoice"
              onPress={this.onNewInvoice}
            />
          </View>
          {this.renderDataTable()}
        </View>
      </View>
    );
  }
}

CustomerInvoicesPage.propTypes = {
  database: React.PropTypes.object,
  navigateTo: React.PropTypes.func.isRequired,
};

const COLUMNS = [
  {
    key: 'otherParty.name',
    width: 4,
    title: 'CUSTOMER',
    sortable: true,
  },
  {
    key: 'id',
    width: 1,
    title: 'ID',
    sortable: true,
  },
  {
    key: 'status',
    width: 1,
    title: 'STATUS',
    sortable: true,
  },
  {
    key: 'entryDate',
    width: 2,
    title: 'ENTERED DATE',
    sortable: true,
  },
  {
    key: 'comment',
    width: 4,
    title: 'COMMENT',
  },
];
<commit_msg>Changed id column to serial number<commit_after>/* @flow weak */

/**
 * OfflineMobile Android
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React from 'react';
import { View } from 'react-native';

import { generateUUID } from '../database';
import { Button } from '../widgets';
import globalStyles from '../globalStyles';
import { GenericTablePage } from './GenericTablePage';

/**
* Renders the page for displaying CustomerInvoices.
* @prop   {Realm}               database      App wide database.
* @prop   {func}                navigateTo    CallBack for navigation stack.
* @state  {Realm.Results}       transactions  Filtered to have only customer_invoice.
*/
export class CustomerInvoicesPage extends GenericTablePage {
  constructor(props) {
    super(props);
    this.state.transactions = props.database.objects('Transaction')
                                            .filtered('type == "customer_invoice"');
    this.state.sortBy = 'otherParty.name';
    this.columns = COLUMNS;
    this.getUpdatedData = this.getUpdatedData.bind(this);
    this.onNewInvoice = this.onNewInvoice.bind(this);
    this.onRowPress = this.onRowPress.bind(this);
    this.renderCell = this.renderCell.bind(this);
  }

  onNewInvoice() {
    let invoice;
    this.props.database.write(() => {
      invoice = this.props.database.create('Transaction', {
        id: generateUUID(),
        serialNumber: '1',
        entryDate: new Date(),
        type: 'customer_invoice',
        status: 'new',
        comment: 'Testing sync',
        otherParty: this.props.database.objects('Name')[0],
      });
    });
    this.props.navigateTo('customerInvoice', 'New Invoice', {
      invoice: invoice,
    });
  }

  onRowPress(invoice) {
    this.props.navigateTo('customerInvoice', `Invoice ${invoice.serialNumber}`, { invoice });
  }

  /**
   * Returns updated data according to searchTerm, sortBy and isAscending. Special
   * case for otherParty.name as realm does not allow sorting on object properties
   * properties.
   */
  getUpdatedData(searchTerm, sortBy, isAscending) {
    let data = this.state.transactions.filtered(`otherParty.name CONTAINS[c] "${searchTerm}"`);
    if (sortBy === 'otherParty.name') {
      // Convert to javascript array obj then sort with standard array functions.
      data = data.slice().sort((a, b) => a.otherParty.name.localeCompare(b.otherParty.name));
      if (!isAscending) data.reverse();
    } else {
      data = data.sorted(sortBy, !isAscending); // 2nd arg: reverse sort
    }
    return data;
  }

  renderCell(key, invoice) {
    switch (key) {
      default:
      case 'otherParty.name':
        return invoice.otherParty && invoice.otherParty.name;
      case 'serialNumber':
        return invoice.serialNumber;
      case 'status':
        return invoice.status;
      case 'entryDate':
        return invoice.entryDate.toDateString();
      case 'comment':
        return invoice.comment;
    }
  }

  render() {
    return (
      <View style={globalStyles.pageContentContainer}>
        <View style={globalStyles.container}>
          <View style={globalStyles.pageTopSectionContainer}>
            {this.renderSearchBar()}
            <Button
              style={globalStyles.button}
              textStyle={globalStyles.buttonText}
              text="New Invoice"
              onPress={this.onNewInvoice}
            />
          </View>
          {this.renderDataTable()}
        </View>
      </View>
    );
  }
}

CustomerInvoicesPage.propTypes = {
  database: React.PropTypes.object,
  navigateTo: React.PropTypes.func.isRequired,
};

const COLUMNS = [
  {
    key: 'otherParty.name',
    width: 4,
    title: 'CUSTOMER',
    sortable: true,
  },
  {
    key: 'serialNumber',
    width: 1,
    title: 'INVOICE NO.',
    sortable: true,
  },
  {
    key: 'status',
    width: 1,
    title: 'STATUS',
    sortable: true,
  },
  {
    key: 'entryDate',
    width: 2,
    title: 'ENTERED DATE',
    sortable: true,
  },
  {
    key: 'comment',
    width: 4,
    title: 'COMMENT',
  },
];
<|endoftext|>


<commit_before>function Dashboard() {
	var self = this;
	this.tryCache = function(){
		if (self.Tickets.ticketArray) {
			localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
		}
	};
	this.createElement = function(parent, tag, attributes) {
		var element = parent.appendChild(document.createElement(tag));
		for (var attribute in attributes) {
			if (attributes.hasOwnProperty(attribute)) {
				element[attribute] = attributes[attribute];
			}
		}
		return element;
	};

	this.openClient = function(client) {
		ko.dataFor(app).showManageCustomer(client);
	};

	this.closeClient = function() {
		var buttons = document.getElementsByClassName('close'),
		i = buttons.length - 1;
		if (i >= 1) buttons[i - 1].click();
	};

	this.calendar = function(month, year, data) {
		if (typeof(month) == Object) data = month, month = null;
		this.current_date = new Date();
		this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
		this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
		this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
		this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
		this.draw = function(ele){
	  		if (this.month > 11) {
	  			this.month -= 12;
	  			this.year += 1;
	  		} else if (month < 0) {
	  			this.month += 12;
	  			this.year -= 1;
	  		}
	  		// get dates
	  		var startingDay = new Date(this.year, this.month, 1).getDay(),
	  		endingDate = new Date(this.year, this.month + 1, 0),
	  		monthLength = endingDate.getDate(),
	  		inLastRow = 6 - endingDate.getDay();

	  		// generate header
	  	  this.table = document.createElement('table');
	  		this.head = document.createElement('thead');
	  		this.prev = document.createElement('th');
	  		this.next = document.createElement('th');
	  		this.title = document.createElement('th');
	  		this.days = document.createElement('tr');
	  		this.body = document.createElement('tbody');
	  		this.cells = '';
	  		this.table.setAttribute('class', 'calendar');
	  		this.table.appendChild(this.head);
	  		this.head.appendChild(this.prev);
	  		this.prev.innerHTML = "«";
	  		this.prev.id = 'prev';
	  		this.head.appendChild(this.title);
	  		this.head.appendChild(this.next);
	  		this.next.innerHTML = "»";
	  		this.next.id = 'next';
	  		this.table.appendChild(this.days);
	  		this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
	  		this.title.setAttribute('colspan', '5');
	  		this.days.setAttribute('class', 'header');
	  		for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
	  		this.days.innerHTML = this.cells;

	  		//generate calendar
	  		var html = '
';
	  		if ( startingDay ) html += '	';
	  		for (var day = 1; day <= monthLength; day++) {
	  		html += '
' + day + '
';
	  		//if data argument present, append key value for current day
	  		if (data) {
	  			var today = (this.month+1)+'/'+day+'/'+this.year;
	  			if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
	  		}
	  		html += '	';
	  		if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
	  		}
	  		if ( inLastRow ) html += '	';
	  		html += '
';
	  		this.table.innerHTML += html;
	  		if (ele) {
	  			ele.innerHTML = this.table.outerHTML
	  		} else {
	  			return this.table.outerHTML
	  		}
	  		// save reference to dom node in object
	  		this.node = ele;
	  		// save reference to calendar object for enclosure scope
	  		var cal = this;
	      		(function(){
	  			// save calendar object reference for listeners
	  			var self = this;
	        		self.prev.addEventListener('click', function(){
	  				cal.month--;
	          			cal.draw(cal.node);
	        		});
	  			self.next.addEventListener('click', function(){
	          			cal.month++;
	  				cal.draw(cal.node);
	        		});
			      	document.onkeydown = function(e) {
			        	e = e || window.event;
			          	if (e.keyCode == '37') {
			        		// left arrow
			        		cal.month--;
			        		cal.draw(cal.node);
			          	} else if (e.keyCode == '39') {
			            		// right arrow
			            		cal.month++;
			        		cal.draw(cal.node);
			          	}
			        };
	      		})();
	  	};
	};
	this.HTML = {};
	this.HTML.dashboard = this.createElement(document.body, 'div', {
		className: 'navbar navbar-inverse navbar-fixed-bottom',
		id: 'dashboard',
	});
	this.HTML.style = this.createElement(this.HTML.dashboard, 'link', {
		rel: 'stylesheet',
		type: 'text/css',
		href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
	});
	this.HTML.nav = this.createElement(this.HTML.dashboard, 'ul', {
		className: 'nav navbar-nav',
	});
	this.HTML.tools = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tools',
		className: 'dropup',
	});
	this.HTML.toolsMenu = this.createElement(this.HTML.tools, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.tickets = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tickets',
		className: 'dropup',
	});
	this.HTML.ticketsMenu = this.createElement(this.HTML.tickets, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.ticketCalendar = this.createElement(this.HTML.ticketsMenu, 'li', {
		innerHTML: 'Calendar',
		onclick: function(){
			var node = bootbox.alert('none')[0].children[0].children[0].children[0],
			data = self.Tickets.sortTickets(),
			calendar = new self.calendar(data);
			calendar.draw(node);
		}
	});
	this.HTML.critical = this.createElement(this.HTML.nav, 'li', {
		innerHTML: (function(){
			if (test) {return 'Click to get Critical';}
			else {return self.Tickets.countCritical();}
		})(),
		id: 'critical',
		onclick: self.Tickets.countCritical(),
	});

	this.Tickets = {
		getTickets: function() {
			//get tickets
			if (typeof(ko.dataFor(app).contentViewModel().myTickets) === "function"){
				self.Tickets.ticketArray = ko.dataFor(app).contentViewModel().myTickets();
			} else if (localStorage.ticketCache) {
				self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
			}
			//return results
			if (self.Tickets.ticketArray) {
				localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
				return self.Tickets.ticketArray;
			} else {
				alert('Unable to access tickets at this time!');
				return [];
			}
		},
		getCritical: function() {
			var tickets = this.getTickets(),
			critical = [];
			for (var i = 0; i < tickets.length; i++) {
				if (tickets[i].ScheduledEndDate < new Date()) {
					critical.push(tickets[i]);
				}
			}
			if (tickets.length > 0){
				return critical;
			} else {
				return false;
			}
		},
		countCritical: function() {
			var criticals = self.Tickets.getCritical();
			if (criticals) {
				return 'Critical Tickets: ' + criticals.length + '';
			} else {
				return 'Error loading tickets!';
			}
		},
		sortTickets: function(tickets){
			if (! tickets){
				var tickets = this.getTickets();
			}
			var data = {};
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				date = new Date(ticket.ScheduledEndDate),
				day = date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear();
				if (! data[day]){ data[day] = []};
				data[day].push(ticket);
			}
			return data;
		},
		listTickets: function(tickets){
			var list = document.createElement('ul');
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				listItem = document.createElement('li'),
				text = ticket.CompanyName+': '+ticket.TicketTypeName;
				text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
				text += '/'+ticket.ScheduledEndDate.getDate()+'/';
				text += ticket.ScheduledEndDate.getFullYear()+')';
				listItem.innerHTML = text;
				list.appendChild(listItem);
			}
			return list;
		},
	}
	window.beforeunload = function(){
		this.tryCache();
	};
	window.onhashchange = function(){
		this.tryCache();
	};
	if (localStorage.ticketCache) self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
}
<commit_msg>Update dashboard.js<commit_after>function Dashboard() {
	var self = this;
	this.tryCache = function(){
		if (self.Tickets.ticketArray) {
			localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
		}
	};
	this.createElement = function(parent, tag, attributes) {
		var element = parent.appendChild(document.createElement(tag));
		for (var attribute in attributes) {
			if (attributes.hasOwnProperty(attribute)) {
				element[attribute] = attributes[attribute];
			}
		}
		return element;
	};
	this.openClient = function(client) {
		ko.dataFor(app).showManageCustomer(client);
	};
	this.closeClient = function() {
		var buttons = document.getElementsByClassName('close'),
		i = buttons.length - 1;
		if (i >= 1) buttons[i - 1].click();
	};
	this.calendar = function(month, year, data) {
		if (typeof(month) == Object) data = month, month = null;
		this.current_date = new Date();
		this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
		this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
		this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
		this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
		this.draw = function(ele){
	  		if (this.month > 11) {
	  			this.month -= 12;
	  			this.year += 1;
	  		} else if (month < 0) {
	  			this.month += 12;
	  			this.year -= 1;
	  		}
	  		// get dates
	  		var startingDay = new Date(this.year, this.month, 1).getDay(),
	  		endingDate = new Date(this.year, this.month + 1, 0),
	  		monthLength = endingDate.getDate(),
	  		inLastRow = 6 - endingDate.getDay();

	  		// generate header
	  	  this.table = document.createElement('table');
	  		this.head = document.createElement('thead');
	  		this.prev = document.createElement('th');
	  		this.next = document.createElement('th');
	  		this.title = document.createElement('th');
	  		this.days = document.createElement('tr');
	  		this.body = document.createElement('tbody');
	  		this.cells = '';
	  		this.table.setAttribute('class', 'calendar');
	  		this.table.appendChild(this.head);
	  		this.head.appendChild(this.prev);
	  		this.prev.innerHTML = "«";
	  		this.prev.id = 'prev';
	  		this.head.appendChild(this.title);
	  		this.head.appendChild(this.next);
	  		this.next.innerHTML = "»";
	  		this.next.id = 'next';
	  		this.table.appendChild(this.days);
	  		this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
	  		this.title.setAttribute('colspan', '5');
	  		this.days.setAttribute('class', 'header');
	  		for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
	  		this.days.innerHTML = this.cells;

	  		//generate calendar
	  		var html = '
';
	  		if ( startingDay ) html += '	';
	  		for (var day = 1; day <= monthLength; day++) {
	  		html += '
' + day + '
';
	  		//if data argument present, append key value for current day
	  		if (data) {
	  			var today = (this.month+1)+'/'+day+'/'+this.year;
	  			if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
	  		}
	  		html += '	';
	  		if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
	  		}
	  		if ( inLastRow ) html += '	';
	  		html += '
';
	  		this.table.innerHTML += html;
	  		if (ele) {
	  			ele.innerHTML = this.table.outerHTML
	  		} else {
	  			return this.table.outerHTML
	  		}
	  		// save reference to dom node in object
	  		this.node = ele;
	  		// save reference to calendar object for enclosure scope
	  		var cal = this;
	      		(function(){
	  			// save calendar object reference for listeners
	  			var self = this;
	        		self.prev.addEventListener('click', function(){
	  				cal.month--;
	          			cal.draw(cal.node);
	        		});
	  			self.next.addEventListener('click', function(){
	          			cal.month++;
	  				cal.draw(cal.node);
	        		});
			      	document.onkeydown = function(e) {
			        	e = e || window.event;
			          	if (e.keyCode == '37') {
			        		// left arrow
			        		cal.month--;
			        		cal.draw(cal.node);
			          	} else if (e.keyCode == '39') {
			            		// right arrow
			            		cal.month++;
			        		cal.draw(cal.node);
			          	}
			        };
	      		})();
	  	};
	};

	this.HTML = {};
	this.HTML.dashboard = this.createElement(document.body, 'div', {
		className: 'navbar navbar-inverse navbar-fixed-bottom',
		id: 'dashboard',
	});
	this.HTML.style = this.createElement(this.HTML.dashboard, 'link', {
		rel: 'stylesheet',
		type: 'text/css',
		href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
	});
	this.HTML.nav = this.createElement(this.HTML.dashboard, 'ul', {
		className: 'nav navbar-nav',
	});
	this.HTML.tools = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tools',
		className: 'dropup',
	});
	this.HTML.toolsMenu = this.createElement(this.HTML.tools, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.tickets = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tickets',
		className: 'dropup',
	});
	this.HTML.ticketsMenu = this.createElement(this.HTML.tickets, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.ticketCalendar = this.createElement(this.HTML.ticketsMenu, 'li', {
		innerHTML: 'Calendar',
		onclick: function(){
			var node = bootbox.alert('none')[0].children[0].children[0].children[0],
			data = self.Tickets.sortTickets(),
			calendar = new self.calendar(data);
			calendar.draw(node);
		}
	});
	this.HTML.critical = this.createElement(this.HTML.nav, 'li', {
		innerHTML: (function(){
			if (test) {return 'Click to get Critical';}
			else {return self.Tickets.countCritical();}
		})(),
		id: 'critical',
		onclick: self.Tickets.countCritical(),
	});

	this.Tickets = {
		getTickets: function() {
			//get tickets
			if (typeof(ko.dataFor(app).contentViewModel().myTickets) === "function"){
				self.Tickets.ticketArray = ko.dataFor(app).contentViewModel().myTickets();
			} else if (localStorage.ticketCache) {
				self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
			}
			//return results
			if (self.Tickets.ticketArray) {
				localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
				return self.Tickets.ticketArray;
			} else {
				alert('Unable to access tickets at this time!');
				return [];
			}
		},
		getCritical: function() {
			var tickets = this.getTickets(),
			critical = [];
			for (var i = 0; i < tickets.length; i++) {
				if (tickets[i].ScheduledEndDate < new Date()) {
					critical.push(tickets[i]);
				}
			}
			if (tickets.length > 0){
				return critical;
			} else {
				return false;
			}
		},
		countCritical: function() {
			var criticals = self.Tickets.getCritical();
			if (criticals) {
				return 'Critical Tickets: ' + criticals.length + '';
			} else {
				return 'Error loading tickets!';
			}
		},
		sortTickets: function(tickets){
			if (! tickets){
				var tickets = this.getTickets();
			}
			var data = {};
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				date = new Date(ticket.ScheduledEndDate),
				day = date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear();
				if (! data[day]){ data[day] = []};
				data[day].push(ticket);
			}
			return data;
		},
		listTickets: function(tickets){
			var list = document.createElement('ul');
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				listItem = document.createElement('li'),
				text = ticket.CompanyName+': '+ticket.TicketTypeName;
				text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
				text += '/'+ticket.ScheduledEndDate.getDate()+'/';
				text += ticket.ScheduledEndDate.getFullYear()+')';
				listItem.innerHTML = text;
				list.appendChild(listItem);
			}
			return list;
		},
	}
	window.beforeunload = function(){
		this.tryCache();
	};
	window.onhashchange = function(){
		this.tryCache();
	};
	if (localStorage.ticketCache) self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
}
<|endoftext|>


<commit_before>/* call using the following enclosure:
(function(){
  var url = 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.js';
  document.head.appendChild(document.createElement('script')).src=url;
  function initialize(){if (typeof(Dashboard) !== 'undefined') {var db = new Dashboard()} else {setTimeout(function(){initialize()}, 100);}}
  initialize();
})();
*/

function Dashboard() {
	var self = this;
	window.addEventListener("beforeunload", function(){
		if (this.Tickets.ticketArray) {
			localStorage.ticketCache = JSON.stringify(this.Tickets.ticketArray);
		}
	});
	if (localStorage.ticketCache) this.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
	this.createElement = function(parent, tag, attributes) {
		var element = parent.appendChild(document.createElement(tag));
		for (var attribute in attributes) {
			if (attributes.hasOwnProperty(attribute)) {
				element[attribute] = attributes[attribute];
			}
		}
		return element;
	};

	this.openClient = function(client) {
		ko.dataFor(app).showManageCustomer(client);
	};

	this.closeClient = function() {
		var buttons = document.getElementsByClassName('close'),
		i = buttons.length - 1;
		if (i >= 1) buttons[i - 1].click();
	};

	this.calendar = function(month, year, data) {
		if (typeof(month) == Object) data = month, month = null;
		this.current_date = new Date();
		this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
		this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
		this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
		this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
		this.draw = function(ele){
	  		if (this.month > 11) {
	  			this.month -= 12;
	  			this.year += 1;
	  		} else if (month < 0) {
	  			this.month += 12;
	  			this.year -= 1;
	  		}
	  		// get dates
	  		var startingDay = new Date(this.year, this.month, 1).getDay(),
	  		endingDate = new Date(this.year, this.month + 1, 0),
	  		monthLength = endingDate.getDate(),
	  		inLastRow = 6 - endingDate.getDay();

	  		// generate header
	  	  	this.table = document.createElement('table');
	  		this.head = document.createElement('thead');
	  		this.prev = document.createElement('th');
	  		this.next = document.createElement('th');
	  		this.title = document.createElement('th');
	  		this.days = document.createElement('tr');
	  		this.body = document.createElement('tbody');
	  		this.cells = '';
	  		this.table.setAttribute('class', 'calendar');
	  		this.table.appendChild(this.head);
	  		this.head.appendChild(this.prev);
	  		this.prev.innerHTML = "«";
	  		this.prev.id = 'prev';
	  		this.head.appendChild(this.title);
	  		this.head.appendChild(this.next);
	  		this.next.innerHTML = "»";
	  		this.next.id = 'next';
	  		this.table.appendChild(this.days);
	  		this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
	  		this.title.setAttribute('colspan', '5');
	  		this.days.setAttribute('class', 'header');
	  		for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
	  		this.days.innerHTML = this.cells;

	  		//generate calendar
	  		var html = '
';
	  		if ( startingDay ) html += '	';
	  		for (var day = 1; day <= monthLength; day++) {
	  		html += '
' + day + '
';
	  		//if data argument present, append key value for current day
	  		if (data) {
	  			var today = (this.month+1)+'/'+day+'/'+this.year;
	  			if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
	  		}
	  		html += '	';
	  		if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
	  		}
	  		if ( inLastRow ) html += '	';
	  		html += '
';
	  		this.table.innerHTML += html;
	  		if (ele) {
	  			ele.innerHTML = this.table.outerHTML
	  		} else {
	  			return this.table.outerHTML
	  		}
	  		// save reference to dom node in object
	  		this.node = ele;
	  		// save reference to calendar object for enclosure scope
	  		var cal = this;
	      		(function(){
	  			// save calendar object reference for listeners
	  			var self = this;
	        		self.prev.addEventListener('click', function(){
	  				cal.month--;
	          			cal.draw(cal.node);
	        		});
	  			self.next.addEventListener('click', function(){
	          			cal.month++;
	  				cal.draw(cal.node);
	        		});
			      	document.onkeydown = function(e) {
			        	e = e || window.event;
			          	if (e.keyCode == '37') {
			        		// left arrow
			        		cal.month--;
			        		cal.draw(cal.node);
			          	} else if (e.keyCode == '39') {
			            		// right arrow
			            		cal.month++;
			        		cal.draw(cal.node);
			          	}
			        };
	      		})();
	  	};
	};
	this.HTML = {};
	this.HTML.dashboard = this.createElement(document.body, 'div', {
		className: 'navbar navbar-inverse navbar-fixed-bottom',
		id: 'dashboard',
	});
	this.HTML.style = this.createElement(this.HTML.dashboard, 'link', {
		rel: 'stylesheet',
		type: 'text/css',
		href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
	});
	this.HTML.nav = this.createElement(this.HTML.dashboard, 'ul', {
		className: 'nav navbar-nav',
	});
	this.HTML.tools = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tools',
		className: 'dropup',
	});
	this.HTML.toolsMenu = this.createElement(this.HTML.tools, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.tickets = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tickets',
		className: 'dropup',
	});
	this.HTML.ticketsMenu = this.createElement(this.HTML.tickets, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.ticketCalendar = this.createElement(this.HTML.ticketsMenu, 'li', {
		innerHTML: 'Calendar',
		onclick: function(){
			var node = bootbox.alert('none')[0].children[0].children[0].children[0],
			data = self.Tickets.sortTickets(),
			calendar = new self.calendar(data);
			calendar.draw(node);
		}
	});
	this.HTML.critical = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Click to get Critical',
		id: 'critical',
		onclick: function() {
			this.innerHTML = 'Critical Tickets: ' + self.Tickets.getCritical().length + '';
		},
	});

	this.Tickets = {
		getTickets: function() {
			if (typeof(ko.dataFor(app).contentViewModel().myTickets) === "function"){
				self.Tickets.ticketArray = ko.dataFor(app).contentViewModel().myTickets();
			} else if (localStorage.ticketCache) {
				self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
			}
			if (self.ticketArray) {
				localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
				return self.Tickets.ticketArray;
			} else {
				return [];
			}
		},
		getCritical: function() {
			var tickets = this.getTickets(),
			critical = [];
			for (var i = 0; i < tickets.length; i++) {
				if (tickets[i].ScheduledEndDate < new Date()) {
					critical.push(tickets[i]);
				}
			}
			return critical;
		},
		sortTickets: function(tickets){
			if (! tickets){
				var tickets = this.getTickets();
			}
			var data = {};
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				date = new Date(ticket.ScheduledEndDate),
				day = date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear();
				if (! data[day]){ data[day] = []};
				data[day].push(ticket);
			}
			return data;
		},
		listTickets: function(tickets){
			var list = document.createElement('ul');
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				listItem = document.createElement('li'),
				text = ticket.CompanyName+': '+ticket.TicketTypeName;
				text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
				text += '/'+ticket.ScheduledEndDate.getDate()+'/';
				text += ticket.ScheduledEndDate.getFullYear()+')';
				listItem.innerHTML = text;
				list.appendChild(listItem);
			}
			return list;
		},
	}
}
<commit_msg>Update dashboard.js<commit_after>/* call using the following enclosure:
(function(){
  var url = 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.js';
  document.head.appendChild(document.createElement('script')).src=url;
  function initialize(){if (typeof(Dashboard) !== 'undefined') {var db = new Dashboard()} else {setTimeout(function(){initialize()}, 100);}}
  initialize();
})();
*/

function Dashboard() {
	var self = this;
	window.addEventListener("beforeunload", function(){
		if (this.Tickets.ticketArray) {
			localStorage.ticketCache = JSON.stringify(this.Tickets.ticketArray);
		}
	});
	if (localStorage.ticketCache) this.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
	this.createElement = function(parent, tag, attributes) {
		var element = parent.appendChild(document.createElement(tag));
		for (var attribute in attributes) {
			if (attributes.hasOwnProperty(attribute)) {
				element[attribute] = attributes[attribute];
			}
		}
		return element;
	};

	this.openClient = function(client) {
		ko.dataFor(app).showManageCustomer(client);
	};

	this.closeClient = function() {
		var buttons = document.getElementsByClassName('close'),
		i = buttons.length - 1;
		if (i >= 1) buttons[i - 1].click();
	};

	this.calendar = function(month, year, data) {
		if (typeof(month) == Object) data = month, month = null;
		this.current_date = new Date();
		this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
		this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
		this.month = (isNaN(month) || month === null) ? this.current_date.getMonth() : month;
		this.year = (isNaN(year) || year === null) ? this.current_date.getFullYear() : year;
		this.draw = function(ele){
	  		if (this.month > 11) {
	  			this.month -= 12;
	  			this.year += 1;
	  		} else if (month < 0) {
	  			this.month += 12;
	  			this.year -= 1;
	  		}
	  		// get dates
	  		var startingDay = new Date(this.year, this.month, 1).getDay(),
	  		endingDate = new Date(this.year, this.month + 1, 0),
	  		monthLength = endingDate.getDate(),
	  		inLastRow = 6 - endingDate.getDay();

	  		// generate header
	  	  	this.table = document.createElement('table');
	  		this.head = document.createElement('thead');
	  		this.prev = document.createElement('th');
	  		this.next = document.createElement('th');
	  		this.title = document.createElement('th');
	  		this.days = document.createElement('tr');
	  		this.body = document.createElement('tbody');
	  		this.cells = '';
	  		this.table.setAttribute('class', 'calendar');
	  		this.table.appendChild(this.head);
	  		this.head.appendChild(this.prev);
	  		this.prev.innerHTML = "«";
	  		this.prev.id = 'prev';
	  		this.head.appendChild(this.title);
	  		this.head.appendChild(this.next);
	  		this.next.innerHTML = "»";
	  		this.next.id = 'next';
	  		this.table.appendChild(this.days);
	  		this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
	  		this.title.setAttribute('colspan', '5');
	  		this.days.setAttribute('class', 'header');
	  		for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
	  		this.days.innerHTML = this.cells;

	  		//generate calendar
	  		var html = '
';
	  		if ( startingDay ) html += '	';
	  		for (var day = 1; day <= monthLength; day++) {
	  		html += '
' + day + '
';
	  		//if data argument present, append key value for current day
	  		if (data) {
	  			var today = (this.month+1)+'/'+day+'/'+this.year;
	  			if (data[today]) html += '
'+Object.keys(data[today]).length+' Clients
';
	  		}
	  		html += '	';
	  		if ( (day + startingDay) % 7 === 0 && day != monthLength ) html += '

';
	  		}
	  		if ( inLastRow ) html += '	';
	  		html += '
';
	  		this.table.innerHTML += html;
	  		if (ele) {
	  			ele.innerHTML = this.table.outerHTML
	  		} else {
	  			return this.table.outerHTML
	  		}
	  		// save reference to dom node in object
	  		this.node = ele;
	  		// save reference to calendar object for enclosure scope
	  		var cal = this;
	      		(function(){
	  			// save calendar object reference for listeners
	  			var self = this;
	        		self.prev.addEventListener('click', function(){
	  				cal.month--;
	          			cal.draw(cal.node);
	        		});
	  			self.next.addEventListener('click', function(){
	          			cal.month++;
	  				cal.draw(cal.node);
	        		});
			      	document.onkeydown = function(e) {
			        	e = e || window.event;
			          	if (e.keyCode == '37') {
			        		// left arrow
			        		cal.month--;
			        		cal.draw(cal.node);
			          	} else if (e.keyCode == '39') {
			            		// right arrow
			            		cal.month++;
			        		cal.draw(cal.node);
			          	}
			        };
	      		})();
	  	};
	};
	this.HTML = {};
	this.HTML.dashboard = this.createElement(document.body, 'div', {
		className: 'navbar navbar-inverse navbar-fixed-bottom',
		id: 'dashboard',
	});
	this.HTML.style = this.createElement(this.HTML.dashboard, 'link', {
		rel: 'stylesheet',
		type: 'text/css',
		href: 'https://rawgit.com/jamesona/SEO-Tools/master/am-dashboard/dashboard.css',
	});
	this.HTML.nav = this.createElement(this.HTML.dashboard, 'ul', {
		className: 'nav navbar-nav',
	});
	this.HTML.tools = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tools',
		className: 'dropup',
	});
	this.HTML.toolsMenu = this.createElement(this.HTML.tools, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.tickets = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Tickets',
		className: 'dropup',
	});
	this.HTML.ticketsMenu = this.createElement(this.HTML.tickets, 'ul', {
		className: 'dropdown-menu',
	});
	this.HTML.ticketCalendar = this.createElement(this.HTML.ticketsMenu, 'li', {
		innerHTML: 'Calendar',
		onclick: function(){
			var node = bootbox.alert('none')[0].children[0].children[0].children[0],
			data = self.Tickets.sortTickets(),
			calendar = new self.calendar(data);
			calendar.draw(node);
		}
	});
	this.HTML.critical = this.createElement(this.HTML.nav, 'li', {
		innerHTML: 'Click to get Critical',
		id: 'critical',
		onclick: function() {
			this.innerHTML = 'Critical Tickets: ' + self.Tickets.getCritical().length + '';
		},
	});

	this.Tickets = {
		getTickets: function() {
			if (typeof(ko.dataFor(app).contentViewModel().myTickets) === "function"){
				self.Tickets.ticketArray = ko.dataFor(app).contentViewModel().myTickets();
				alert('Able to access launchpad tickets');
			} else if (localStorage.ticketCache) {
				self.Tickets.ticketArray = JSON.parse(localStorage.ticketCache);
				alert('Able to load tickets from browser cache');
			}
			if (self.ticketArray) {
				localStorage.ticketCache = JSON.stringify(self.Tickets.ticketArray);
				alert('Updated browser ticket cache');
				return self.Tickets.ticketArray;
			} else {
				alert('Unable to access tickets at this time');
				return [];
			}
		},
		getCritical: function() {
			var tickets = this.getTickets(),
			critical = [];
			for (var i = 0; i < tickets.length; i++) {
				if (tickets[i].ScheduledEndDate < new Date()) {
					critical.push(tickets[i]);
				}
			}
			return critical;
		},
		sortTickets: function(tickets){
			if (! tickets){
				var tickets = this.getTickets();
			}
			var data = {};
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				date = new Date(ticket.ScheduledEndDate),
				day = date.getDate()+'/'+(date.getMonth()+1)+'/'+date.getFullYear();
				if (! data[day]){ data[day] = []};
				data[day].push(ticket);
			}
			return data;
		},
		listTickets: function(tickets){
			var list = document.createElement('ul');
			for (var i=0;i<tickets.length;i++){
				var ticket = tickets[i],
				listItem = document.createElement('li'),
				text = ticket.CompanyName+': '+ticket.TicketTypeName;
				text += ' (Due: '+(ticket.ScheduledEndDate.getMonth()+1);
				text += '/'+ticket.ScheduledEndDate.getDate()+'/';
				text += ticket.ScheduledEndDate.getFullYear()+')';
				listItem.innerHTML = text;
				list.appendChild(listItem);
			}
			return list;
		},
	}
}
<|endoftext|>


<commit_before><commit_msg>init routing module<commit_after>module.exports = function(router, route) {
	console.log(route);
}<|endoftext|>


<commit_before>var fs = require("fs");
var path = require("path");
var util = require("util");
var async = require("async");

var servlet = require("./servlet");

function SlingPost(grunt, task) {
    this.grunt = grunt;
    this.task = task;
}

exports.SlingPost = SlingPost;

SlingPost.prototype.run = function() {
    var self = this;

    var options = self.task.options({
        host: "localhost",
        port: 8080,
        user: "admin",
        pass: "admin"
    });

    self.grunt.verbose.writeln("Host: " + options.host);
    self.grunt.verbose.writeln("Port: " + options.port);
    self.grunt.verbose.writeln("User: " + options.user);

    var done = self.task.async();

    var postServlet = new servlet.Post(options);

    self.task.files.forEach(postRoots);

    function postRoots(file) {
        file.src.forEach(function (root) {
            if (self.grunt.file.exists(root) === false) {
                self.grunt.fatal(util.format("The directory %s does not exist.", root));
            }

            if (self.grunt.file.isDir(root) === false) {
                self.grunt.fatal(util.format("The path %s is not a directory.", root));
            }
        });

        var tasks = file.src.map(function (directory) {
            return createRootDirectoryTask(directory, file.dest);
        });

        async.parallel(tasks, done);
    }

    function createRootDirectoryTask(root, resource) {
        return function (done) {
            postRecursively(root, resource, done);
        };
    }

    function postRecursively(root, resource, done) {

        // Read content of the current directory

        var children = fs.readdirSync(root);

        // Filter out files

        var files = children.filter(function (child) {
            return self.grunt.file.isFile(root, child) && path.extname(child) !== ".json";
        });

        // Filter out directories

        var directories = children.filter(function (child) {
            return self.grunt.file.isDir(root, child);
        });

        // Filter out descriptors

        var descriptors = children.filter(function (child) {
            return self.grunt.file.isFile(root, child) && path.extname(child) === ".json";
        });

        // Create a descriptor map (base name -> json content)

        function toDescriptorMap(map, descriptor) {
            var base = path.basename(descriptor, ".json");
            var json = self.grunt.file.readJSON(path.join(root, descriptor));

            map[base] = json;

            return map;
        }

        var descriptorMap = descriptors.reduce(toDescriptorMap, {});

        // Return an object with properties for the selected file

        function propertiesFor(name, initial) {
            var base = path.basename(name, path.extname(name));

            var descriptor = descriptorMap[base] || {};

            function toProperties(map, name) {
                map[name] = descriptor[name];
                return map;
            }

            return Object.keys(descriptor).reduce(toProperties, initial || {});
        }

        // Wraps a function to report warnings at servlet failures

        function withWarnings(done) {
            return function (err, response, body) {
                if (err) {
                    return done(err);
                }

                var b = JSON.parse(body);

                if (response.statusCode < 200 || response.statusCode >= 300) {
                    self.grunt.warn(util.format("Error writing %s: %s: %s.", b.path, b.error.class, b.error.message));
                }

                done(err, response, body);
            };
        }

        // Creates a task to create/update a file

        function toFileTasks(file) {
            return function (done) {
                self.grunt.log.writeln("File: " + path.join(root, file));
                postServlet.createFile(resource, path.join(root, file), propertiesFor(file), withWarnings(done));
            };
        }

        var fileTasks = files.map(toFileTasks);

        function concat(parent, node) {
            return parent.slice(-1) === "/" ? parent + node : parent + "/" + node;
        }

        // Creates a task to create/update a folder

        function toDirectoryTask(directory) {
            return function (done) {
                self.grunt.log.writeln("Dir : " + path.join(root, directory));

                var properties = propertiesFor(directory, {
                    "jcr:primaryType": "sling:Folder"
                });

                postServlet.create(concat(resource, directory), properties, withWarnings(done));
            };
        }

        var directoryTasks = directories.map(toDirectoryTask);

        // Creates a task to create/update generic nodes

        function unusedDescriptor(name) {
            var usedForDirectories = directories.some(function (directory) {
                return directory === name;
            });

            var usedForFiles = files.some(function (file) {
                return path.basename(file, path.extname(file)) === name;
            });

            return !usedForFiles && !usedForDirectories;
        }

        function toNodeTask(name) {
            return function (done) {
                var jsonName = path.join(root, name + ".json");

                self.grunt.log.writeln("Node: " + jsonName);

                postServlet.create(concat(resource, name), propertiesFor(jsonName), withWarnings(done));
            };
        }

        var nodeTasks = Object.keys(descriptorMap).filter(unusedDescriptor).map(toNodeTask);

        // Executes every task in parallel

        async.parallel(fileTasks.concat(nodeTasks).concat(directoryTasks), recurseSubdirectories);

        // When work is done in this directory, recurse in subdirectories

        function recurseSubdirectories(err) {
            if (err) {
                return done(err);
            }

            var tasks = directories.map(function (directory) {
                return createRootDirectoryTask(path.join(root, directory), concat(resource, directory));
            });

            async.parallel(tasks, done);
        }
    }
};

function SlingImport(grunt, task) {
    this.grunt = grunt;
    this.task = task;
}

exports.SlingImport = SlingImport;

SlingImport.prototype.run = function() {
    var grunt = this.grunt;

    function printOptions(grunt, options) {
        grunt.verbose.writeln("Host: " + options.host);
        grunt.verbose.writeln("Port: " + options.port);
        grunt.verbose.writeln("User: " + options.user);
        grunt.verbose.writeln("Checkin: " + options.checkin);
        grunt.verbose.writeln("Auto-checkout: " + options.checkin);
        grunt.verbose.writeln("Replace: " + options.checkin);
        grunt.verbose.writeln("Replace properties: " + options.checkin);
    }

    function validateImportTypes(grunt, files) {
        files.forEach(function (group) {
            group.src.forEach(function (file) {
                if (!getImportType(file)) {
                    grunt.fatal(util.format("Unable to determine the import type of %s", file));
                }
            });
        });
    }

    function getImportType(file) {
        var supported = ["json", "jar", "zip", "jcr.xml", "xml"];

        var i;

        for (i = 0; i < supported.length; i++) {
            var extension = "." + supported[i];

            // Is the file using an import type as extension?

            if (file.indexOf(extension, file.length - extension.length) !== -1) {
                return supported[i];
            }
        }
    }

    function getNodeName(file, type) {
        var base = path.basename(file);
        return base.slice(0, base.length - type.length - 1);
    }

    var options = this.task.options({
        host: "localhost",
        port: 8080,
        user: "admin",
        pass: "admin",
        checkin: false,
        autoCheckout: false,
        replace: false,
        replaceProperties: false
    });

    printOptions(grunt, options);

    validateImportTypes(grunt, this.task.files);

    var tasks = [];

    var postServlet = new servlet.Post(options);

    function withWarnings(done) {
        return function (err, response, body) {
            if (err) {
                return done(err);
            }

            var b = JSON.parse(body);

            if (response.statusCode < 200 || response.statusCode >= 300) {
                grunt.warn(util.format("Error writing %s: %s.", b.path, b["status.message"]));
            }

            done(err, response, body);
        };
    }

    function createImportTask(node, file, options) {
        return function (done) {
            var type = getImportType(file);
            var name = getNodeName(file, type);

            grunt.log.writeln(util.format("Importing %s with type '%s'", file, type));

            postServlet.importContent(node, name, file, type, options, withWarnings(done));
        };
    }

    this.task.files.forEach(function (group) {
        group.src.forEach(function (file) {
            tasks.push(createImportTask(group.orig.dest, file, options));
        });
    });

    async.parallel(tasks, this.task.async());
};<commit_msg>Refactor the post task<commit_after>var fs = require("fs");
var path = require("path");
var util = require("util");
var async = require("async");

var servlet = require("./servlet");

/**
 * Concatenate two resource paths. Ensure that path separators are correctly
 * handled.
 * @param {String} parent The path of the parent resource.
 * @param {String} node The name of the node to concatenate.
 * @return {String} A new path resulting from the concatenation of parent and
 *     node.
 */
function concatResource(parent, node) {
    if (parent.slice(-1) === "/") {
        return parent + node;
    }
    else {
        return parent + "/" + node;
    }
}

/**
 * Takes care of posting file recuresively in a directory.
 * @param {Object} task The task object, loose requirements on its APIs are needed.
 * @param {String} root Path of the directory.
 * @param {String} resource Path of the resource whom the directory wil be mapped to.
 */
function DirectoryPoster(task, root, resource) {
    this.task = task;
    this.directory = root;
    this.resource = resource;
}

exports.DirectoryPoster = DirectoryPoster;

/**
 * Creates an instance of the POST Servlet wrapper, correctly configured. The
 * instance is cached, multiple invocations of this method will return the
 * same instance.
 * @return {Object} An instance of the POST Servlet wrapper.
 */
DirectoryPoster.prototype.getServlet = function () {
    var self = this;

    if (self.servlet) {
        return self.servlet;
    }

    self.servlet = new servlet.Post(self.task.getOptions());

    return self.servlet;
};

/**
 * Reads the children of the current directory. Returns an array of file
 * names. The returned array is cached, multiple invocations of this method
 * will return the same array.
 * @return {Array} Array of names for children of the current directory.
 */
DirectoryPoster.prototype.getChildren = function() {
    var self = this;

    if (self.children) {
        return self.children;
    }

    self.children = fs.readdirSync(self.directory);

    return self.children;
};

/**
 * Reads the children files of the current directory. Returns an array of file
 * names. The returned array is cached, multiple invocations of this method
 * will return the same array.
 * @return {Array} Array of names for children files of the current directory.
 */
DirectoryPoster.prototype.getFiles = function() {
    var self = this;

    if (self.files) {
        return self.files;
    }

    var isFile = self.task.grunt.file.isFile;

    function filesOnly(child) {
        return isFile(self.directory, child) && path.extname(child) !== ".json";
    }

    self.files = self.getChildren().filter(filesOnly);

    return self.files;
};

/**
 * Reads the children directories of the current directory. Returns an array
 * of directory names. The returned array is cached, multiple invocations of
 * this method will return the same array.
 * @return {Array} Array of names for children directories of the current
 *     directory.
 */
DirectoryPoster.prototype.getDirectories = function() {
    var self = this;

    if (self.directories) {
        return self.directories;
    }

    var isDir = self.task.grunt.file.isDir;

    function directoriesOnly(child) {
        return isDir(self.directory, child);
    }

    self.directories = self.getChildren().filter(directoriesOnly);

    return self.directories;
};

/**
 * Reads the children descriptor files of the current directory. Returns an
 * array of directory names. The returned array is cached, multiple
 * invocations of this method will return the same array.
 * @return {Array} Array of names for children descriptor files of the current
 *     directory.
 */
DirectoryPoster.prototype.getDescriptors = function() {
    var self = this;

    if (self.descriptors) {
        return self.descriptors;
    }

    var isFile = self.task.grunt.file.isFile;

    self.descriptors = self.getChildren().filter(function (child) {
        return isFile(self.directory, child) && path.extname(child) === ".json";
    });

    return self.descriptors;
};

/**
 * Return a map of descriptor files. The map will have as keys the base name
 * of the descriptor file (without the ".json" extension), and as value the
 * content of the corresponding descriptor file, already parsed.
 * @return {Object} Map of descriptor names to descriptor contents.
 */
DirectoryPoster.prototype.getDescriptorMap = function() {
    var self = this;

    if (self.descriptorMap) {
        return self.descriptorMap;
    }

    var readJSON = self.task.grunt.file.readJSON;

    function toDescriptorMap(map, descriptor) {
        var base = path.basename(descriptor, ".json");

        var json = readJSON(path.join(self.directory, descriptor));

        map[base] = json;

        return map;
    }

    self.descriptorMap = self.getDescriptors().reduce(toDescriptorMap, {});

    return self.descriptorMap;
};

/**
 * Return the descriptor properties for a file of a given name. The properties
 * are read from the corresponding descriptors, if it exists. Initial values,
 * for properties can be provided to the method. Initial values are used if
 * the descriptor files doesn't contain those properties.
 * @param  {String} name Name of the file.
 * @param  {Object} initial Initial properties.
 * @return {Object} Descriptor properties for the file with the given name,
 *     optionally merged with initial properties.
 */
DirectoryPoster.prototype.propertiesFor = function(name, initial) {
    var self = this;

    var base = path.basename(name, path.extname(name));

    var descriptorMap = self.getDescriptorMap();

    var descriptor = descriptorMap[base] || {};

    var result = initial || {};

    Object.keys(descriptor).forEach(function (name) {
        result[name] = descriptor[name];
    });

    return result;
};

/**
 * Decorate the callback which will be passed to the POST Servlet. The
 * decorated callback will parse the JSON response of the POST Servlet and
 * will print a warning if an error occurs.
 * @param  {Function} callback Original callback.
 * @return {Function} A function wrapping the original callback and adding
 *     functionalities.
 */
DirectoryPoster.prototype.decorateCallback = function(callback) {
    return function (err, response, body) {
        if (err) {
            return callback(err);
        }

        var b = JSON.parse(body);

        if (response.statusCode < 200 || response.statusCode >= 300) {
            self.task.grunt.warn(util.format("Error writing %s: %s: %s.", b.path, b.error.class, b.error.message));
        }

        callback(err, response, body);
    };
};

/**
 * Creates task functions to post the files contained in the current
 * directory.
 * @return {Array} Array of task functions.
 */
DirectoryPoster.prototype.getFileTasks = function() {
    var self = this;

    function toFileTasks(file) {
        return function (done) {
            self.task.grunt.log.writeln("File: " + path.join(self.directory, file));

            var parentResource = self.resource;
            var filePath = path.join(self.directory, file);
            var properties = self.propertiesFor(file);
            var callback = self.decorateCallback(done);

            self.getServlet().createFile(parentResource, filePath, properties, callback);
        };
    }

    return self.getFiles().map(toFileTasks);
};

/**
 * Creates task functions to post the directories contained in the current
 * directory.
 * @return {Array} Array of task functions.
 */
DirectoryPoster.prototype.getDirectoryTasks = function() {
    var self = this;

    function toDirectoryTask(directory) {
        return function (done) {
            self.task.grunt.log.writeln("Dir : " + path.join(self.directory, directory));

            var resource = concatResource(self.resource, directory);
            var callback = self.decorateCallback(done);

            var properties = self.propertiesFor(directory, {
                "jcr:primaryType": "sling:Folder"
            });

            self.getServlet().create(resource, properties, callback);
        };
    }

    return self.getDirectories().map(toDirectoryTask);
};

/**
 * Returns the names of the descriptors which are not used to describe other
 * files or directories in the current directory.
 * @return {Array} Array of descriptor names.
 */
DirectoryPoster.prototype.getUnusedDescriptorNames = function() {
    var self = this;

    function unusedDescriptor(name) {
        var usedForDirectories = self.getDirectories().some(function (directory) {
            return directory === name;
        });

        var usedForFiles = self.getFiles().some(function (file) {
            return path.basename(file, path.extname(file)) === name;
        });

        return !usedForFiles && !usedForDirectories;
    }

    return Object.keys(self.getDescriptorMap()).filter(unusedDescriptor);
};

/**
 * Creates task functions to post node descriptors contained in the current
 * directory.
 * @return {Array} Array of task functions.
 */
DirectoryPoster.prototype.getNodeTasks = function() {
    var self = this;

    function toNodeTask(name) {
        return function (done) {
            var jsonName = path.join(self.directory, name + ".json");

            self.task.grunt.log.writeln("Node: " + jsonName);

            var resource = concatResource(self.resource, name);
            var properties = self.propertiesFor(jsonName);
            var callback = self.decorateCallback(done);

            self.getServlet().create(resource, properties, callback);
        };
    }

    return self.getUnusedDescriptorNames().map(toNodeTask);
};

/**
 * Creates task functions which will be used to recurse in the directories
 * contained in the current directory.
 * @return {Array} Array of task functions.
 */
DirectoryPoster.prototype.getRecursionTasks = function() {
    var self = this;

    function toRecursionTask(directory) {
        return function (done) {
            var newRoot = path.join(self.directory, directory);
            var newResource = concatResource(self.resource, directory);

            var poster = new DirectoryPoster(self.task, newRoot, newResource);

            poster.post(done);
        };
    }

    return self.getDirectories().map(toRecursionTask);
};

/**
 * Submit the content of the current directory to the POST Servlet. After the
 * required content for the current directory has been created, recurse into
 * the directories in the current directory and repeat the process.
 * @param  {Function} done Callback which will be invoked when the submission
 *     of data is finished.
 */
DirectoryPoster.prototype.post = function(done) {
    var self = this;

    var fileTasks = self.getFileTasks();
    var directoryTasks = self.getDirectoryTasks();
    var nodeTasks = self.getNodeTasks();

    var tasks = fileTasks.concat(nodeTasks).concat(directoryTasks);

    async.parallel(tasks, function (err) {
        if (err) {
            return done(err);
        }

        var tasks = self.getRecursionTasks();

        async.parallel(tasks, done);
    });
};

/**
 * Task to subnit content of directories to the Sling POST Servlet.
 * @param {Object} grunt The Grunt main object.
 * @param {Object} task The task object ("this" inside the task function).
 */
function SlingPost(grunt, task) {
    this.grunt = grunt;
    this.task = task;
}

exports.SlingPost = SlingPost;

/**
 * Execute the task.
 */
SlingPost.prototype.run = function() {
    var self = this;

    self.printOptions();
    self.validate();
    self.postDirectories(self.task.async());
};

/**
 * Return the option object, with default values used if none is provided from
 * the user.
 * @return {Object} Option object.
 */
SlingPost.prototype.getOptions = function() {
    if (this.options) {
        return this.options;
    }

    this.options = this.task.options({
        host: "localhost",
        port: 8080,
        user: "admin",
        pass: "admin"
    });

    return this.options;
};

/**
 * Print the options to the Grunt logger.
 */
SlingPost.prototype.printOptions = function () {
    var options = this.getOptions();
    var writeln = this.grunt.verbose.writeln;

    writeln("Host: " + options.host);
    writeln("Port: " + options.port);
    writeln("User: " + options.user);
};

/**
 * Validate the input files provided by the user.
 */
SlingPost.prototype.validate = function() {
    var self = this;

    self.task.files.forEach(function (group) {
        group.src.forEach(function (root) {
            if (self.grunt.file.exists(root) === false) {
                self.grunt.fatal(util.format("The directory %s does not exist.", root));
            }

            if (self.grunt.file.isDir(root) === false) {
                self.grunt.fatal(util.format("The path %s is not a directory.", root));
            }
        });
    });
};

/**
 * Recursively submit the content of the directories passed to the task to the
 * Sling POST Servlet.
 * @param  {Function} done Callback which will be invoked when every input
 *     directory is submitted to the Sling POST Servlet.
 */
SlingPost.prototype.postDirectories = function(done) {
    var self = this;

    var tasks = [];

    self.task.files.forEach(function (group) {
        group.src.forEach(function (directory) {
            tasks.push(function (done) {
                new DirectoryPoster(self, directory, group.orig.dest).post(done);
            });
        });
    });

    async.parallel(tasks, done);
};

function SlingImport(grunt, task) {
    this.grunt = grunt;
    this.task = task;
}

exports.SlingImport = SlingImport;

SlingImport.prototype.run = function() {
    var grunt = this.grunt;

    function printOptions(grunt, options) {
        grunt.verbose.writeln("Host: " + options.host);
        grunt.verbose.writeln("Port: " + options.port);
        grunt.verbose.writeln("User: " + options.user);
        grunt.verbose.writeln("Checkin: " + options.checkin);
        grunt.verbose.writeln("Auto-checkout: " + options.checkin);
        grunt.verbose.writeln("Replace: " + options.checkin);
        grunt.verbose.writeln("Replace properties: " + options.checkin);
    }

    function validateImportTypes(grunt, files) {
        files.forEach(function (group) {
            group.src.forEach(function (file) {
                if (!getImportType(file)) {
                    grunt.fatal(util.format("Unable to determine the import type of %s", file));
                }
            });
        });
    }

    function getImportType(file) {
        var supported = ["json", "jar", "zip", "jcr.xml", "xml"];

        var i;

        for (i = 0; i < supported.length; i++) {
            var extension = "." + supported[i];

            // Is the file using an import type as extension?

            if (file.indexOf(extension, file.length - extension.length) !== -1) {
                return supported[i];
            }
        }
    }

    function getNodeName(file, type) {
        var base = path.basename(file);
        return base.slice(0, base.length - type.length - 1);
    }

    var options = this.task.options({
        host: "localhost",
        port: 8080,
        user: "admin",
        pass: "admin",
        checkin: false,
        autoCheckout: false,
        replace: false,
        replaceProperties: false
    });

    printOptions(grunt, options);

    validateImportTypes(grunt, this.task.files);

    var tasks = [];

    var postServlet = new servlet.Post(options);

    function withWarnings(done) {
        return function (err, response, body) {
            if (err) {
                return done(err);
            }

            var b = JSON.parse(body);

            if (response.statusCode < 200 || response.statusCode >= 300) {
                grunt.warn(util.format("Error writing %s: %s.", b.path, b["status.message"]));
            }

            done(err, response, body);
        };
    }

    function createImportTask(node, file, options) {
        return function (done) {
            var type = getImportType(file);
            var name = getNodeName(file, type);

            grunt.log.writeln(util.format("Importing %s with type '%s'", file, type));

            postServlet.importContent(node, name, file, type, options, withWarnings(done));
        };
    }

    this.task.files.forEach(function (group) {
        group.src.forEach(function (file) {
            tasks.push(createImportTask(group.orig.dest, file, options));
        });
    });

    async.parallel(tasks, this.task.async());
};<|endoftext|>


<commit_before>/* Most of this code is from require.js so higher complexity is allowed. */
/* jshint maxcomplexity:20 */

module.exports = function (cfg) {
	"use strict";

	var config = require("./normalizeConfig").loader(cfg),
		getOwn = require("./lang").getOwn,
		jsSuffixRegExp = /\.js$/;

	/**
	 * Trims the . and .. from an array of path segments.
	 * It will keep a leading path segment if a .. will become
	 * the first path segment, to help with module name lookups,
	 * which act like paths, but can be remapped. But the end result,
	 * all paths that use this function should look normalized.
	 * NOTE: this method MODIFIES the input array.
	 * @param {Array} ary the array of path segments.
	 */
	function trimDots(ary) {
		var i, part;
		for (i = 0; i < ary.length; i++) {
			part = ary[i];
			if (part === '.') {
				ary.splice(i, 1);
				i -= 1;
			} else if (part === '..') {
				// If at the start, or previous value is still ..,
				// keep them so that when converted to a path it may
				// still work when converted to a path, even though
				// as an ID it is less than ideal. In larger point
				// releases, may be better to just kick out an error.
				if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
					continue;
				} else if (i > 0) {
					ary.splice(i - 1, 2);
					i -= 2;
				}
			}
		}
	}

	return {

		/**
		 * Given a relative module name, like ./something, normalize it to
		 * a real name that can be mapped to a path.
		 * @param {String} name the relative name
		 * @param {String} baseName a real name that the name arg is relative
		 * to.
		 * @param {Boolean} applyMap apply the map config to the value. Should
		 * only be done if this normalization is for a dependency ID.
		 * @returns {String} normalized name
		 */
		/* Remove jshint warning for the bad indentation @l.96 because of the named for-loop. */
		/* jshint -W015 */
		normalize: function (name, baseName, applyMap) {
			var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
				foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
				baseParts = (baseName && baseName.split('/')),
				map = config.map,
				starMap = map && map['*'];

			//Adjust any relative paths.
			if (name) {
				name = name.split('/');
				lastIndex = name.length - 1;

				// If wanting node ID compatibility, strip .js from end
				// of IDs. Have to do this here, and not in nameToUrl
				// because node allows either .js or non .js to map
				// to same file.
				if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
					name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
				}

				// Starts with a '.' so need the baseName
				if (name[0].charAt(0) === '.' && baseParts) {
					//Convert baseName to array, and lop off the last part,
					//so that . matches that 'directory' and not name of the baseName's
					//module. For instance, baseName of 'one/two/three', maps to
					//'one/two/three.js', but we want the directory, 'one/two' for
					//this normalization.
					normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
					name = normalizedBaseParts.concat(name);
				}

				trimDots(name);
				name = name.join('/');
			}

			//Apply map config if available.
			if (applyMap && map && (baseParts || starMap)) {
				nameParts = name.split('/');

				outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
					nameSegment = nameParts.slice(0, i).join('/');

					if (baseParts) {
						//Find the longest baseName segment match in the config.
						//So, do joins on the biggest to smallest lengths of baseParts.
						for (j = baseParts.length; j > 0; j -= 1) {
							mapValue = getOwn(map, baseParts.slice(0, j).join('/'));

							//baseName segment has config, find if it has one for
							//this name.
							if (mapValue) {
								mapValue = getOwn(mapValue, nameSegment);
								if (mapValue) {
									//Match, update name to the new value.
									foundMap = mapValue;
									foundI = i;
									break outerLoop;
								}
							}
						}
					}

					//Check for a star map match, but just hold on to it,
					//if there is a shorter segment match later in a matching
					//config, then favor over this star map.
					if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
						foundStarMap = getOwn(starMap, nameSegment);
						starI = i;
					}
				}

				if (!foundMap && foundStarMap) {
					foundMap = foundStarMap;
					foundI = starI;
				}

				if (foundMap) {
					nameParts.splice(0, foundI, foundMap);
					name = nameParts.join('/');
				}
			}

			// If the name points to a package's name, use
			// the package main instead.
			pkgMain = getOwn(config.pkgs, name);

			return pkgMain ? pkgMain : name;
		},
		/* jshint +W015 */
		/* Restore jshint warning */

		/**
		 * Converts a module name to a file path.
		 * Note that it **does not** call normalize on the moduleName,
		 * it is assumed to have already been normalized. This is an
		 * internal API, not a public one. Use toUrl for the public API.
		 */
		nameToFilepath: function (moduleName, noExt) {
			var paths, syms, i, parentModule, url,
				parentPath,
				pkgMain = getOwn(config.pkgs, moduleName);

			if (pkgMain) {
				moduleName = pkgMain;
			}

			paths = config.paths;

			syms = moduleName.split('/');
			//For each module name segment, see if there is a path
			//registered for it. Start with most specific name
			//and work up from it.
			for (i = syms.length; i > 0; i -= 1) {
				parentModule = syms.slice(0, i).join('/');

				parentPath = getOwn(paths, parentModule);
				if (parentPath) {
					//If an array, it means there are a few choices,
					//Choose the one that is desired
					if (Array.isArray(parentPath)) {
						parentPath = parentPath[0];
					}
					syms.splice(0, i, parentPath);
					break;
				}
			}

			//Join the path parts together, then figure out if baseUrl is needed.
			url = syms.join('/') + (noExt ? '' : '.js');
			url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;

			return url;
		}
	};
};
<commit_msg>fix a bug regarding normalization of plugin resources starting with ../../<commit_after>/* Most of this code is from require.js so higher complexity is allowed. */
/* jshint maxcomplexity:20 */

module.exports = function (cfg) {
	"use strict";

	var config = require("./normalizeConfig").loader(cfg),
		getOwn = require("./lang").getOwn,
		jsSuffixRegExp = /\.js$/;

	/**
	 * Trims the . and .. from an array of path segments.
	 * It will keep a leading path segment if a .. will become
	 * the first path segment, to help with module name lookups,
	 * which act like paths, but can be remapped. But the end result,
	 * all paths that use this function should look normalized.
	 * NOTE: this method MODIFIES the input array.
	 * @param {Array} ary the array of path segments.
	 */
	function trimDots(ary) {
		var i, part;
		for (i = 0; i < ary.length; i++) {
			part = ary[i];
			if (part === '.') {
				ary.splice(i, 1);
				i -= 1;
			} else if (part === '..') {
				// If at the start, or previous value is still ..,
				// keep them so that when converted to a path it may
				// still work when converted to a path, even though
				// as an ID it is less than ideal. In larger point
				// releases, may be better to just kick out an error.
				if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
					continue;
				} else if (i > 0) {
					ary.splice(i - 1, 2);
					i -= 2;
				}
			}
		}
	}

	return {

		/**
		 * Given a relative module name, like ./something, normalize it to
		 * a real name that can be mapped to a path.
		 * @param {String} name the relative name
		 * @param {String} baseName a real name that the name arg is relative
		 * to.
		 * @param {Boolean} applyMap apply the map config to the value. Should
		 * only be done if this normalization is for a dependency ID.
		 * @returns {String} normalized name
		 */
		/* Remove jshint warning for the bad indentation @l.96 because of the named for-loop. */
		/* jshint -W015 */
		normalize: function (name, baseName, applyMap) {
			var parts = name.split("!");
			name = parts[0];

			var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
				foundMap, foundI, foundStarMap, starI, normalizedBaseParts,
				baseParts = (baseName && baseName.split('/')),
				map = config.map,
				starMap = map && map['*'];

			//Adjust any relative paths.
			if (name) {
				name = name.split('/');
				lastIndex = name.length - 1;

				// If wanting node ID compatibility, strip .js from end
				// of IDs. Have to do this here, and not in nameToUrl
				// because node allows either .js or non .js to map
				// to same file.
				if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
					name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
				}

				// Starts with a '.' so need the baseName
				if (name[0].charAt(0) === '.' && baseParts) {
					//Convert baseName to array, and lop off the last part,
					//so that . matches that 'directory' and not name of the baseName's
					//module. For instance, baseName of 'one/two/three', maps to
					//'one/two/three.js', but we want the directory, 'one/two' for
					//this normalization.
					normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
					name = normalizedBaseParts.concat(name);
				}

				trimDots(name);
				name = name.join('/');
			}

			//Apply map config if available.
			if (applyMap && map && (baseParts || starMap)) {
				nameParts = name.split('/');

				outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
					nameSegment = nameParts.slice(0, i).join('/');

					if (baseParts) {
						//Find the longest baseName segment match in the config.
						//So, do joins on the biggest to smallest lengths of baseParts.
						for (j = baseParts.length; j > 0; j -= 1) {
							mapValue = getOwn(map, baseParts.slice(0, j).join('/'));

							//baseName segment has config, find if it has one for
							//this name.
							if (mapValue) {
								mapValue = getOwn(mapValue, nameSegment);
								if (mapValue) {
									//Match, update name to the new value.
									foundMap = mapValue;
									foundI = i;
									break outerLoop;
								}
							}
						}
					}

					//Check for a star map match, but just hold on to it,
					//if there is a shorter segment match later in a matching
					//config, then favor over this star map.
					if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
						foundStarMap = getOwn(starMap, nameSegment);
						starI = i;
					}
				}

				if (!foundMap && foundStarMap) {
					foundMap = foundStarMap;
					foundI = starI;
				}

				if (foundMap) {
					nameParts.splice(0, foundI, foundMap);
					name = nameParts.join('/');
				}
			}

			// If the name points to a package's name, use
			// the package main instead.
			pkgMain = getOwn(config.pkgs, name);

			// Restore plugin
			parts[0] = name;
			name = parts.join("!");

			return pkgMain ? pkgMain : name;
		},
		/* jshint +W015 */
		/* Restore jshint warning */

		/**
		 * Converts a module name to a file path.
		 * Note that it **does not** call normalize on the moduleName,
		 * it is assumed to have already been normalized. This is an
		 * internal API, not a public one. Use toUrl for the public API.
		 */
		nameToFilepath: function (moduleName, noExt) {
			var paths, syms, i, parentModule, url,
				parentPath,
				pkgMain = getOwn(config.pkgs, moduleName);

			if (pkgMain) {
				moduleName = pkgMain;
			}

			paths = config.paths;

			syms = moduleName.split('/');
			//For each module name segment, see if there is a path
			//registered for it. Start with most specific name
			//and work up from it.
			for (i = syms.length; i > 0; i -= 1) {
				parentModule = syms.slice(0, i).join('/');

				parentPath = getOwn(paths, parentModule);
				if (parentPath) {
					//If an array, it means there are a few choices,
					//Choose the one that is desired
					if (Array.isArray(parentPath)) {
						parentPath = parentPath[0];
					}
					syms.splice(0, i, parentPath);
					break;
				}
			}

			//Join the path parts together, then figure out if baseUrl is needed.
			url = syms.join('/') + (noExt ? '' : '.js');
			url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;

			return url;
		}
	};
};
<|endoftext|>