<commit_before>/*
Name: Mag-importer v0.1.1
Description: similar to html-imports implementation
Author: Michael Glazer
License: MIT
Homepage: https://github.com/magnumjs/mag.js
@TODO: implement css loader
@requires mag.js & mag addons
(c) 2016
*/
mag.importer = function(url, eleSelector) {

  function checkIfIncluded(file) {
    var links = document.getElementsByTagName("link");
    for (var i = 0; i < links.length; i++) {
      if (links[i].href.substr(-file.length) == file) {
        return true;
      }
    }

    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
      if (scripts[i].src.substr(-file.length) == file) {
        return true;
      }
    }

    return false;
  }

  function loadInternalScripts(node, type) {
    var scripts = node.getElementsByTagName(type);
    // multiple promises
    var promises = [];
    var names = {}
    for (var i = 0; i < scripts.length; i++) {
      // and doesn't already exist
      var url = type == 'script' ? scripts[i].attributes.src.value : scripts[i].attributes.href.value;
      if (url != "") {

        if (checkIfIncluded(url)) {
          promises.push(mag.importer.namedPromises[url].promise);
          continue;
        }

        var deferred = mag.deferred();
        if (!names[url]) {
          mag.importer.namedPromises[url] = names[url] = deferred;
        } else continue;

        var tag = document.createElement(type);
        tag[(type == 'script' ? 'src' : 'href')] = url;
        if (type == 'link') tag.rel = 'stylesheet';

        tag.onload = function() {
          deferred.resolve(1);
        }

        document.getElementsByTagName("head")[0].appendChild(tag);
        promises.push(deferred.promise);

      }
    }
    return promises;
  }

  var deferred = mag.deferred();

  mag.request({
      url: url,
      cache: false
    })
    .then(function(data) {
      //save data
      //get html as node
      var template = document.createElement('template');
      template.innerHTML = data;
      var newNode = template.content.firstChild;

      var node = document.querySelector(eleSelector);

      var promises1 = loadInternalScripts(newNode, 'script');
      var promises2 = loadInternalScripts(newNode, 'link');
      var promises = mag.merge(promises1, promises2);

      mag.when(promises, function() {
        // multiple invocations
        newNode.children[0].id = eleSelector
        node.appendChild(newNode.children[0]);
        deferred.resolve(1);
      })

    });
  return deferred.promise;
};

mag.importer.namedPromises = {};
<commit_msg>Update mag-importer.js<commit_after>/*
Name: Mag-importer v0.1.1
Description: similar to html-imports implementation
Author: Michael Glazer
License: MIT
Homepage: https://github.com/magnumjs/mag.js
@TODO: implement css loader
@requires mag.js & mag addons
(c) 2016
*/
mag.importer = function(url, eleSelector) {

  function checkIfIncluded(file) {
    var links = document.getElementsByTagName("link");
    for (var i = 0; i < links.length; i++) {
      if (links[i].href.substr(-file.length) == file) {
        return true;
      }
    }

    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
      if (scripts[i].src.substr(-file.length) == file) {
        return true;
      }
    }

    return false;
  }

  function loadInternalScripts(node, type) {
    var scripts = node.getElementsByTagName(type);
    // multiple promises
    var promises = [];
    var names = {}
    for (var i = 0; i < scripts.length; i++) {
      // and doesn't already exist
      var url = type == 'script' ? scripts[i].attributes.src.value : scripts[i].attributes.href.value;
      if (url != "") {

        if (checkIfIncluded(url)) {
          promises.push(mag.importer.namedPromises[url].promise);
          continue;
        }

        var deferred = mag.deferred();
        if (!names[url]) {
          mag.importer.namedPromises[url] = names[url] = deferred;
        } else continue;

        var tag = document.createElement(type);
        tag[(type == 'script' ? 'src' : 'href')] = url;
        if (type == 'link') tag.rel = 'stylesheet';

        tag.onload = function() {
          deferred.resolve(1);
        }

        document.getElementsByTagName("head")[0].appendChild(tag);
        promises.push(deferred.promise);

      }
    }
    return promises;
  }

  var deferred = mag.deferred();

  mag.request({
      url: url,
      cache: true
    })
    .then(function(data) {
      //save data
      //get html as node
      var template = document.createElement('template');
      template.innerHTML = data;
      var newNode = template.content.firstChild;

      var node = document.querySelector(eleSelector);

      var promises1 = loadInternalScripts(newNode, 'script');
      var promises2 = loadInternalScripts(newNode, 'link');
      var promises = mag.merge(promises1, promises2);

      mag.when(promises, function() {
        // multiple invocations
        newNode.children[0].id = eleSelector
        node.appendChild(newNode.children[0]);
        deferred.resolve(1);
      })

    });
  return deferred.promise;
};

mag.importer.namedPromises = {};
<|endoftext|>


<commit_before>var AppDispatcher = require('../dispatcher/app-dispatcher');
var EventEmitter = require('events').EventEmitter;
var LoanConstants = require('../constants/loan-constants');
var ScenarioConstants = require('../constants/scenario-constants');
var assign = require('object-assign');
var jumbo = require('jumbo-mortgage');
var mortgageCalculations = require('../mortgage-calculations');
var common = require('../common');
var api = require('../api');
var $ = jQuery = require('jquery');
var ScenarioStore = require('./scenario-store');

var calculatedProperties = ['loan-summary', 'loan-amount'];
var calculatedPropertiesBasedOnIR = [
    'discount',
    'processing',
    'lender-fees',
    'third-party-fees',
    'third-party-services',
    'insurance',
    'taxes-gov-fees',
    'prepaid-expenses',
    'initial-escrow',
    'monthly-taxes-insurance',
    'monthly-hoa-dues',
    'monthly-principal-interest',
    'monthly-mortgage-insurance',
    'monthly-payment',
    'closing-costs',
    'principal-paid',
    'interest-fees-paid',
    'overall-costs'
];
var CHANGE_EVENT = 'change';

var defaultLoanData = {
    'credit-score': 700,
    'downpayment': 20000,
    'price': 200000,
    'rate-structure': 'fixed',
    'points': 0,
    'loan-term': 30,
    'loan-type': 'conf',
    'arm-type': '5-1',
    'state': 'AL'
};

var validators;

var _loans = [];

function init () {
    resetLoans(true);
}

function resetLoans (init) {
    var len = _loans.length || 2;
    var scenario = ScenarioStore.getScenario();

    // if initial setup or a new scenario has been chosen,
    // set data on loans
    if (init || scenario) {
        // get scenario-specific loan data
        var scenarioLoanData = scenario ? scenario.loanProps : {};

        // If we're moving into a scenario with existing loans,
        // make sure they have matching values by copying A's vals to B.
        // The scenario-specific differences will be set below.
        if (scenario && _loans[0] && _loans[1]) {
            assign(_loans[1], _loans[0]);
            _loans[1].id = 1;
        }

        // create each loan from default + current + scenario loan data,
        // then generate loan's calculated & state-based properties,
        // and finally fetch interest rates
        for (i = 0; i < len; i++) {
            var currentLoanData = _loans[i];
            _loans[i] = assign({id: i}, defaultLoanData, currentLoanData, scenarioLoanData[i]);
            updateLoan(i);
            updateLoanRates(i);
        }
    }
}

// update all the loans
function updateAllLoans(prop, val) {
    for (var id in _loans) {
        updateLoan(id, prop, val);
    }
}

// update a single loan
function updateLoan(id, prop, val) {
    var loan = _loans[id];
    var rateChange = (prop === 'interest-rate');

    // If a prop was passed in, update it on the loan
    if (prop) {
        loan[prop] = val || null;
    }

    // Changing any loan property but interest-rate
    // will prompt user to request new rates.
    loan['edited'] = !rateChange;

    // Update dependencies & calculated properties
    // and validate loan based on changed prop.
    updateDependencies(loan, prop);
    validateLoan(loan);
    generateCalculatedProperties(loan, rateChange);

    // If a rate request is in progress, update it.
    if (loan['edited'] && loan['rate-request']) {
        updateLoanRates(id);
    }
}

function updateDependencies (loan, prop) {
    if (prop === 'price' || prop === 'downpayment') {
        loan['downpayment-percent'] = mortgageCalculations['downpayment-percent'](loan);
    } else if (!prop || prop === 'downpayment-percent') {
        loan['downpayment'] = mortgageCalculations['downpayment'](loan);
    }
    return loan;
}

function fetchRates(loan) {
    if (loan['rate-request']) {
        api.stopRequest(loan['rate-request']);
    }
    return api.fetchRateData(loan);
}

function fetchIns(loan) {
    if (loan['mtg-ins-request']) {
        api.stopRequest(loan['mtg-ins-request']);
    }
    return api.fetchMortgageInsuranceData(loan);
}

function processRatesResults(results) {
    var rates = [];
    var totalRates = [];
    for (key in results.data) {
        if (results.data.hasOwnProperty(key)) {
            rates.push(key);
            var len = results.data[key];
            for (var i=0; i<len; i++){
                totalRates.push(key)
            }
        }
    }
    rates = rates.sort();
    var medianRate = common.median(totalRates) || 0;
    var processedRates = $.map(rates, function( rate, i ) {
        return {val: Number(rate), label: rate + '%'};
    });
    return {vals: processedRates, median: Number(medianRate)};
}

function updateLoanRates(id) {
    var loans = $.isNumeric(id) ? [_loans[id]] : _loans;
    $.each(loans, function (ind, loan) {
        var dfd = fetchRates(loan);
        var insDfd = fetchIns(loan);
        dfd
            .done(function(results) {
                var rates = processRatesResults(results);
                loan['edited'] = false;
                loan['rates'] = rates.vals;
                loan['interest-rate'] = rates.median;
                generateCalculatedProperties(loan, true);
            });
        insDfd
            .done(function(results) {
                loan['mtg-ins-data'] = results.data;
             });
        $.when(dfd, insDfd)
            .done(function () {
                generateCalculatedProperties(loan, true);
            })
            .always(function() {
                    loan['rate-request'] = null;
                    loan['mtg-ins-request'] = null;
                // TODO: maybe this fetch should be an api action?
                LoanStore.emitChange();
            });
        loan['rate-request'] = dfd;
        loan['mtg-ins-request'] = insDfd;
    });
}

function generateCalculatedProperties (loan, rateChange) {
    var props = rateChange
                ? calculatedPropertiesBasedOnIR
                : calculatedProperties;
    for (var i = 0; i < props.length; i++) {
        var prop = props[i];
        loan[prop] = mortgageCalculations[prop](loan);
    }
    return loan;
}

validators = {
    'loan-type': function (loan) {
        if (loan['rate-structure'] === 'arm' && $.inArray(loan['loan-type'], common.armDisallowedOptions['loan-type']) >= 0) {
            loan['loan-type'] = 'conf';
            return common.errorMessages['loan-type'];
        }
    },
    'loan-term': function (loan) {
        if (loan['rate-structure'] === 'arm' && $.inArray(+loan['loan-term'], common.armDisallowedOptions['loan-term']) >= 0) {
            loan['loan-term'] = 30;
            return common.errorMessages['loan-term'];
        }
    },
    'downpayment': function (loan) {
        if (isDownpaymentTooHigh(loan)) {
            return common.errorMessages['downpayment-too-high'];
        } else if (isDownpaymentTooLow(loan)) {
            return common.errorMessages['downpayment-too-low' + '-' + loan['loan-type']];
        }
    }
}

function validateLoan (loan) {
    loan['errors'] = {};
    $.each(validators, function (prop, validator) {
        var err = validator(loan);
        if (err) {
            loan['errors'][prop] = err;
        }
    });

    //loan['is-jumbo'] = isJumbo(loan);
    return loan;
}

function isDownpaymentTooHigh (loan) {
    return +loan['downpayment'] > +loan['price'];
}

function isDownpaymentTooLow (loan) {
    switch (loan['loan-type']) {
        case 'conf':
            return loan['downpayment'] < common.minDownpaymentPcts.conf * loan['price'];
            break;
        case 'fha':
            return loan['downpayment'] < common.minDownpaymentPcts.fha * loan['price'];
            break;
        default:
            return false;
    }
}

function isJumbo (loan) {
    // TODO: finish this
    var jumbos = ['jumbo', 'agency', 'fha-hb', 'va-hb'],
        bounces = { 'fha-hb' : 'fha', 'va-hb': 'va' };

    var jumboTest = jumbo({
        loanType: loan['loan-type'],
        loanAmount: loan['loan-amount']
    });

    return false;
}

function isPropLinked(prop) {
    var scenario = ScenarioStore.getScenario();
    return (scenario && $.inArray(prop, scenario.independentInputs) === -1);
}

var LoanStore = assign({}, EventEmitter.prototype, {
    /**
    * Get the entire collection of loans.
    * @return {object}
    */
    getAll: function() {
        return _loans;
    },

    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },

    /**
    * @param {function} callback
    */
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
    },

    /**
    * @param {function} callback
    */
    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    },
    validators: validators
});

// Register callback to handle all updates
LoanStore.dispatchToken = AppDispatcher.register(function(action) {
    switch(action.actionType) {

        case LoanConstants.UPDATE_LOAN:
            // update both loans or single loan, dep. on whether the prop changed
            // is independent or linked in the current scenario
            if (isPropLinked(action.prop)) {
                updateAllLoans(action.prop, action.val);
            } else {
                updateLoan(action.id, action.prop, action.val);
            }
            LoanStore.emitChange();
            break;

        case LoanConstants.UPDATE_ALL:
            updateAllLoans(action.prop, action.val);
            LoanStore.emitChange();
            break;

        case LoanConstants.UPDATE_RATES:
            // update both loans if both are edited
            var id = action.id;
            var otherLoan = _loans[id == 1 ? 0 : 1];
            if (otherLoan.edited) {
                id = null;
            }
            updateLoanRates(id);
            LoanStore.emitChange();
            break;

        case ScenarioConstants.UPDATE_SCENARIO:
            AppDispatcher.waitFor([ScenarioStore.dispatchToken]);
            resetLoans();
            LoanStore.emitChange();
            break;

        default:
        // no op
    }
});

// INITIAL SETUP
init(true);

module.exports = LoanStore;<commit_msg>fix(lc): add downpaymentPercentMode variable to determine whether downpayment or downpayment-percent value is calculated when house price is updated.<commit_after>var AppDispatcher = require('../dispatcher/app-dispatcher');
var EventEmitter = require('events').EventEmitter;
var LoanConstants = require('../constants/loan-constants');
var ScenarioConstants = require('../constants/scenario-constants');
var assign = require('object-assign');
var jumbo = require('jumbo-mortgage');
var mortgageCalculations = require('../mortgage-calculations');
var common = require('../common');
var api = require('../api');
var $ = jQuery = require('jquery');
var ScenarioStore = require('./scenario-store');

var calculatedProperties = ['loan-summary', 'loan-amount'];
var calculatedPropertiesBasedOnIR = [
    'discount',
    'processing',
    'lender-fees',
    'third-party-fees',
    'third-party-services',
    'insurance',
    'taxes-gov-fees',
    'prepaid-expenses',
    'initial-escrow',
    'monthly-taxes-insurance',
    'monthly-hoa-dues',
    'monthly-principal-interest',
    'monthly-mortgage-insurance',
    'monthly-payment',
    'closing-costs',
    'principal-paid',
    'interest-fees-paid',
    'overall-costs'
];
var CHANGE_EVENT = 'change';

var defaultLoanData = {
    'credit-score': 700,
    'downpayment': 20000,
    'price': 200000,
    'rate-structure': 'fixed',
    'points': 0,
    'loan-term': 30,
    'loan-type': 'conf',
    'arm-type': '5-1',
    'state': 'AL'
};

var validators;

var _loans = [];

var downpaymentPercentMode = true;

function init () {
    resetLoans(true);
}

function resetLoans (init) {
    var len = _loans.length || 2;
    var scenario = ScenarioStore.getScenario();

    // if initial setup or a new scenario has been chosen,
    // set data on loans
    if (init || scenario) {
        // get scenario-specific loan data
        var scenarioLoanData = scenario ? scenario.loanProps : {};

        // If we're moving into a scenario with existing loans,
        // make sure they have matching values by copying A's vals to B.
        // The scenario-specific differences will be set below.
        if (scenario && _loans[0] && _loans[1]) {
            assign(_loans[1], _loans[0]);
            _loans[1].id = 1;
        }

        // Make sure downpaymentPercentMode is triggered on start of the
        // downpayment scenario since it compares two common dp percentages.
        if (scenario && scenario.val === 'downpayment') {
            downpaymentPercentMode = true;
        }

        // create each loan from default + current + scenario loan data,
        // then generate loan's calculated & state-based properties,
        // and finally fetch interest rates
        for (i = 0; i < len; i++) {
            _loans[i] = assign({id: i}, defaultLoanData, _loans[i], scenarioLoanData[i]);
            updateLoan(i);
            updateLoanRates(i);
        }
    }
}

// update all the loans
function updateAllLoans(prop, val) {
    for (var id in _loans) {
        updateLoan(id, prop, val);
    }
}

// update a single loan
function updateLoan(id, prop, val) {
    var loan = _loans[id];
    var rateChange = (prop === 'interest-rate');

    // If a prop was passed in, update it on the loan
    if (prop) {
        loan[prop] = val || null;
    }

    // Changing any loan property but interest-rate
    // will prompt user to request new rates.
    loan['edited'] = !rateChange;

    // Update dependencies & calculated properties
    // and validate loan based on changed prop.
    updateDependencies(loan, prop);
    validateLoan(loan);
    generateCalculatedProperties(loan, rateChange);

    // If a rate request is in progress, update it.
    if (loan['edited'] && loan['rate-request']) {
        updateLoanRates(id);
    }
}

function updateDependencies (loan, prop) {
    var dependentProp;
    if (prop === 'downpayment') {
        dependentProp = 'downpayment-percent';
        downpaymentPercentMode = false;
    } else if (prop === 'downpayment-percent') {
        dependentProp = 'downpayment';
        downpaymentPercentMode = true;
    } else if (prop === 'price' || !prop) {
        dependentProp = downpaymentPercentMode && typeof loan['downpayment-percent'] !== 'undefined'
                        ? 'downpayment'
                        : 'downpayment-percent';
    }
    if (dependentProp) {
        loan[dependentProp] = mortgageCalculations[dependentProp](loan)
    }
    return loan;
}

function fetchRates(loan) {
    if (loan['rate-request']) {
        api.stopRequest(loan['rate-request']);
    }
    return api.fetchRateData(loan);
}

function fetchIns(loan) {
    if (loan['mtg-ins-request']) {
        api.stopRequest(loan['mtg-ins-request']);
    }
    return api.fetchMortgageInsuranceData(loan);
}

function processRatesResults(results) {
    var rates = [];
    var totalRates = [];
    for (key in results.data) {
        if (results.data.hasOwnProperty(key)) {
            rates.push(key);
            var len = results.data[key];
            for (var i=0; i<len; i++){
                totalRates.push(key)
            }
        }
    }
    rates = rates.sort();
    var medianRate = common.median(totalRates) || 0;
    var processedRates = $.map(rates, function( rate, i ) {
        return {val: Number(rate), label: rate + '%'};
    });
    return {vals: processedRates, median: Number(medianRate)};
}

function updateLoanRates(id) {
    var loans = $.isNumeric(id) ? [_loans[id]] : _loans;
    $.each(loans, function (ind, loan) {
        var dfd = fetchRates(loan);
        var insDfd = fetchIns(loan);
        dfd
            .done(function(results) {
                var rates = processRatesResults(results);
                loan['edited'] = false;
                loan['rates'] = rates.vals;
                loan['interest-rate'] = rates.median;
                generateCalculatedProperties(loan, true);
            });
        insDfd
            .done(function(results) {
                loan['mtg-ins-data'] = results.data;
             });
        $.when(dfd, insDfd)
            .done(function () {
                generateCalculatedProperties(loan, true);
            })
            .always(function() {
                    loan['rate-request'] = null;
                    loan['mtg-ins-request'] = null;
                // TODO: maybe this fetch should be an api action?
                LoanStore.emitChange();
            });
        loan['rate-request'] = dfd;
        loan['mtg-ins-request'] = insDfd;
    });
}

function generateCalculatedProperties (loan, rateChange) {
    var props = rateChange
                ? calculatedPropertiesBasedOnIR
                : calculatedProperties;
    for (var i = 0; i < props.length; i++) {
        var prop = props[i];
        loan[prop] = mortgageCalculations[prop](loan);
    }
    return loan;
}

validators = {
    'loan-type': function (loan) {
        if (loan['rate-structure'] === 'arm' && $.inArray(loan['loan-type'], common.armDisallowedOptions['loan-type']) >= 0) {
            loan['loan-type'] = 'conf';
            return common.errorMessages['loan-type'];
        }
    },
    'loan-term': function (loan) {
        if (loan['rate-structure'] === 'arm' && $.inArray(+loan['loan-term'], common.armDisallowedOptions['loan-term']) >= 0) {
            loan['loan-term'] = 30;
            return common.errorMessages['loan-term'];
        }
    },
    'downpayment': function (loan) {
        if (isDownpaymentTooHigh(loan)) {
            return common.errorMessages['downpayment-too-high'];
        } else if (isDownpaymentTooLow(loan)) {
            return common.errorMessages['downpayment-too-low' + '-' + loan['loan-type']];
        }
    }
}

function validateLoan (loan) {
    loan['errors'] = {};
    $.each(validators, function (prop, validator) {
        var err = validator(loan);
        if (err) {
            loan['errors'][prop] = err;
        }
    });

    //loan['is-jumbo'] = isJumbo(loan);
    return loan;
}

function isDownpaymentTooHigh (loan) {
    return +loan['downpayment'] > +loan['price'];
}

function isDownpaymentTooLow (loan) {
    switch (loan['loan-type']) {
        case 'conf':
            return loan['downpayment'] < common.minDownpaymentPcts.conf * loan['price'];
            break;
        case 'fha':
            return loan['downpayment'] < common.minDownpaymentPcts.fha * loan['price'];
            break;
        default:
            return false;
    }
}

function isJumbo (loan) {
    // TODO: finish this
    var jumbos = ['jumbo', 'agency', 'fha-hb', 'va-hb'],
        bounces = { 'fha-hb' : 'fha', 'va-hb': 'va' };

    var jumboTest = jumbo({
        loanType: loan['loan-type'],
        loanAmount: loan['loan-amount']
    });

    return false;
}

function isPropLinked(prop) {
    var scenario = ScenarioStore.getScenario();
    return (scenario && $.inArray(prop, scenario.independentInputs) === -1);
}

var LoanStore = assign({}, EventEmitter.prototype, {
    /**
    * Get the entire collection of loans.
    * @return {object}
    */
    getAll: function() {
        return _loans;
    },

    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },

    /**
    * @param {function} callback
    */
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
    },

    /**
    * @param {function} callback
    */
    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    },
    validators: validators
});

// Register callback to handle all updates
LoanStore.dispatchToken = AppDispatcher.register(function(action) {
    switch(action.actionType) {

        case LoanConstants.UPDATE_LOAN:
            // update both loans or single loan, dep. on whether the prop changed
            // is independent or linked in the current scenario
            if (isPropLinked(action.prop)) {
                updateAllLoans(action.prop, action.val);
            } else {
                updateLoan(action.id, action.prop, action.val);
            }
            LoanStore.emitChange();
            break;

        case LoanConstants.UPDATE_ALL:
            updateAllLoans(action.prop, action.val);
            LoanStore.emitChange();
            break;

        case LoanConstants.UPDATE_RATES:
            // update both loans if both are edited
            var id = action.id;
            var otherLoan = _loans[id == 1 ? 0 : 1];
            if (otherLoan.edited) {
                id = null;
            }
            updateLoanRates(id);
            LoanStore.emitChange();
            break;

        case ScenarioConstants.UPDATE_SCENARIO:
            AppDispatcher.waitFor([ScenarioStore.dispatchToken]);
            resetLoans();
            LoanStore.emitChange();
            break;

        default:
        // no op
    }
});

// INITIAL SETUP
init(true);

module.exports = LoanStore;<|endoftext|>


<commit_before>(function (angular) {
  'use strict';

  /**
   * @ngdoc overview
   * @name angulartics.hubspot
   * Enables analytics support for Hubspot (http://www.hubspot.com)
   */
  angular.module('angulartics.hubspot', ['angulartics'])
    .config(['$analyticsProvider', function ($analyticsProvider) {
      // Don't send the first page, hubspot does it
      $analyticsProvider.settings.pageTracking.autoTrackFirstPage = false;

      /**
       * Track a page view
       */
      $analyticsProvider.registerPageTrack(function (path) {
        if (window._hsq) {
          _hsq.push();
          _hsq.push(['trackPageView', path]);
        }
      });

      // https://developers.hubspot.com/docs/methods/enterprise_events/javascript_api
      $analyticsProvider.registerEventTrack(function (action, properties) {
        if(properties.value) {
          var parsed = parseInt(properties.value, 10);
          properties.value = isNaN(parsed) ? 0 : parsed;
        }

        if (window._hsq) {
          _hsq.push();
          _hsq.push(["trackEvent", action, {value: properties.value}]);

        }
      });
    }]);
})(angular);
<commit_msg>Add register user properties to Hubspot plugin<commit_after>(function (angular) {
  'use strict';

  /**
   * @ngdoc overview
   * @name angulartics.hubspot
   * Enables analytics support for Hubspot (http://www.hubspot.com)
   */
  angular.module('angulartics.hubspot', ['angulartics'])
    .config(['$analyticsProvider', function ($analyticsProvider) {
      // Don't send the first page, hubspot does it
      $analyticsProvider.settings.pageTracking.autoTrackFirstPage = false;

      /**
       * Track a page view
       */
      $analyticsProvider.registerPageTrack(function (path) {
        if (window._hsq) {
          _hsq.push();
          _hsq.push(['trackPageView', path]);
        }
      });

      // https://developers.hubspot.com/docs/methods/enterprise_events/javascript_api
      $analyticsProvider.registerEventTrack(function (action, properties) {
        if(properties.value) {
          var parsed = parseInt(properties.value, 10);
          properties.value = isNaN(parsed) ? 0 : parsed;
        }

        if (window._hsq) {
          _hsq.push();
          _hsq.push(["trackEvent", action, {value: properties.value}]);

        }
      });

      /**
      * Add user identify to Hubspot tracking calls
      * @name identify
      *
      * @param {object} properties
      *
      * @link http://developers.hubspot.com/docs/methods/enterprise_events/javascript_api
      */

      $analyticsProvider.registerSetUserProperties(function (properties) {
        if (window._hsq) {
          _hsq.push(["identify", properties]);
        }
      });

    }]);
})(angular);
<|endoftext|>


<commit_before>import React from "react";
import { Row } from "antd";
import { getI18N } from "../../../utilities/i18n-utilties";

const { BASE_URL } = window.TL;

export default function CartEmpty() {
  return (
    <Row
      type="flex"
      justify="center"
      align="middle"
      style={{ height: "100%", flexDirection: "column", padding: 100 }}
    >
      <img
        height="300px"
        src={`${BASE_URL}resources/img/empty-cart.svg`}
        alt={getI18N("CartEmpty.imageAlt")}
      />
      <p
        style={{
          fontSize: "1.4em",
          fontWeight: 600,
          color: "#1890ff",
          marginBottom: ".5em"
        }}
      >
        {getI18N("CartEmpty.heading")}
      </p>
      <a
        href={`${BASE_URL}projects`}
        style={{ borderBottom: "2px solid #1890ff" }}
      >
        {getI18N("CartEmpty.subheading")}
      </a>
    </Row>
  );
}
<commit_msg>Cleaned up empty styles to match new styles<commit_after>import React from "react";
import { Row } from "antd";
import { getI18N } from "../../../utilities/i18n-utilties";
import { COLOURS, FONTS } from "../../../styles";

const { BASE_URL } = window.TL;

export default function CartEmpty() {
  return (
    <Row
      type="flex"
      justify="center"
      align="middle"
      style={{ height: "100%", flexDirection: "column", padding: 100 }}
    >
      <img
        height="300px"
        src={`${BASE_URL}resources/img/empty-cart.svg`}
        alt={getI18N("CartEmpty.imageAlt")}
      />
      <p
        style={{
          fontSize: FONTS.SIZE_LG,
          fontWeight: FONTS.WEIGHT_HEAVY,
          color: COLOURS.TEXT_HIGHLIGHTED,
          marginBottom: ".5em"
        }}
      >
        {getI18N("CartEmpty.heading")}
      </p>
      <a
        href={`${BASE_URL}projects`}
        style={{ borderBottom: `2px solid ${COLOURS.TEXT_HIGHLIGHTED}` }}
      >
        {getI18N("CartEmpty.subheading")}
      </a>
    </Row>
  );
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'

import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import update from 'immutability-helper'

class WritePage extends Component {

  state = {
    title: '',
    body: ''
  }

  handleInputChange = (field) => (e) => {
    this.setState({
      [field]: e.target.value
    })
  }

  handleFormSubmit = (e) => {
    e.preventDefault()
    const { title, body } = this.state
    this.props.submitPost({
      title,
      body
    })
    .then(({ data }) => {
      console.log('got data', data)
    }).catch((error) => {
      console.log('there was an error sending the query', error)
    })
  }

  render () {
    return (

        <form onSubmit={this.handleFormSubmit}>
          <input type="text" onChange={this.handleInputChange('title')} />
          <textarea onChange={this.handleInputChange('body')} />
          <button>Submit</button>
        </form>

    )
  }
}

WritePage.propTypes = {
  submitPost: PropTypes.func.isRequired,
  router: PropTypes.shape({
    push: PropTypes.func.isRequired
  }).isRequired
}

const addPost = gql`
  mutation addPost($title: String!, $body: String!) {
    addPost(
      title: $title,
      body: $body
    ) {
      post {
        _id
        title
        body
      }
      errors {
        key
        message
      }
    }
  }
`

export default graphql(addPost, {
  props: ({ mutate }) => ({
    // Apollo Client cannot update existing queries if you want to add something to a list of objects without refetching the entire list
    // - refetch
    // - polling
    // - subscriptions
    submitPost: ({ title, body }) => mutate({
      variables: {
        title,
        body
      }

      // Update your UI based on the result of a mutation.
      // Most of the time, your UI will update automatically based on mutation results,
      //   as long as the object IDs in the result match up with the IDs you already have in your store
      // updateQueries = Redux's reducer
      // Use cases: removing or adding items to a list with a mutation

      // updateQueries map the name of a query to a function that receives the previous result
      //   that this query received and the result returned by the mutation

      // Note that the function must not alter the prev object (use mmutable data)

      // Once the mutation fires and the result arrives from the server (or, a result is provided through optimistic UI),
      // our updateQueries function for the Comment query will be called and the Comment query will be updated accordingly
      // updateQueries: {
      //   getPosts: (prev, { mutationResult }) => {
      //     const newPost = mutationResult.data.addPost.post
      //     return update(prev, {
      //       posts: {
      //         $unshift: [newPost]
      //       }
      //     })
      //   }
      // },

      // While waiting for mutation results from server, use this fake result instead (will be passed to updateQueries)

      // Mutation returns the single new post that was added, not the whole list
      // If we have a thousand posts, we don’t want to refetch all of them if we add a single new post.
      // optimisticResponse: {
      //   __typename: 'Mutation',
      //   addPost: {
      //     __typename: 'addPostResponseType',
      //     post: {
      //       __typename: 'PostType',
      //       _id: new Date().getTime(),
      //       title,
      //       body
      //     },
      //     errors: []
      //   }
      // }
    })
  })
})(WritePage)
<commit_msg>bring back optimistic response cause it still works with reducer<commit_after>import React, {Component, PropTypes} from 'react'

import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import update from 'immutability-helper'

class WritePage extends Component {

  state = {
    title: '',
    body: ''
  }

  handleInputChange = (field) => (e) => {
    this.setState({
      [field]: e.target.value
    })
  }

  handleFormSubmit = (e) => {
    e.preventDefault()
    const { title, body } = this.state
    this.props.submitPost({
      title,
      body
    })
    .then(({ data }) => {
      console.log('got data', data)
    }).catch((error) => {
      console.log('there was an error sending the query', error)
    })
  }

  render () {
    return (

        <form onSubmit={this.handleFormSubmit}>
          <input type="text" onChange={this.handleInputChange('title')} />
          <textarea onChange={this.handleInputChange('body')} />
          <button>Submit</button>
        </form>

    )
  }
}

WritePage.propTypes = {
  submitPost: PropTypes.func.isRequired,
  router: PropTypes.shape({
    push: PropTypes.func.isRequired
  }).isRequired
}

const addPost = gql`
  mutation addPost($title: String!, $body: String!) {
    addPost(
      title: $title,
      body: $body
    ) {
      post {
        _id
        title
        body
      }
      errors {
        key
        message
      }
    }
  }
`

export default graphql(addPost, {
  props: ({ mutate }) => ({
    // Apollo Client cannot update existing queries if you want to add something to a list of objects without refetching the entire list
    // - refetch
    // - polling
    // - subscriptions
    submitPost: ({ title, body }) => mutate({
      variables: {
        title,
        body
      },

      // Update your UI based on the result of a mutation.
      // Most of the time, your UI will update automatically based on mutation results,
      //   as long as the object IDs in the result match up with the IDs you already have in your store
      // updateQueries = Redux's reducer
      // Use cases: removing or adding items to a list with a mutation

      // updateQueries map the name of a query to a function that receives the previous result
      //   that this query received and the result returned by the mutation

      // Note that the function must not alter the prev object (use mmutable data)

      // Once the mutation fires and the result arrives from the server (or, a result is provided through optimistic UI),
      // our updateQueries function for the Comment query will be called and the Comment query will be updated accordingly
      // updateQueries: {
      //   getPosts: (prev, { mutationResult }) => {
      //     const newPost = mutationResult.data.addPost.post
      //     return update(prev, {
      //       posts: {
      //         $unshift: [newPost]
      //       }
      //     })
      //   }
      // },

      // While waiting for mutation results from server, use this fake result instead (will be passed to updateQueries)

      // Mutation returns the single new post that was added, not the whole list
      // If we have a thousand posts, we don’t want to refetch all of them if we add a single new post.
      optimisticResponse: {
        __typename: 'Mutation',
        addPost: {
          __typename: 'addPostResponseType',
          post: {
            __typename: 'PostType',
            _id: new Date().getTime(),
            title,
            body
          },
          errors: []
        }
      }
    })
  })
})(WritePage)
<|endoftext|>


<commit_before>/* ========================================================================
 * Django Ajax v2.2.4
 * https://github.com/yceruto/django-ajax/
 * Copyright 2014 Yonel Ceruto Glez
 * ======================================================================== */

 ;(function ($) { "use strict";

    // AJAX CLASS DEFINITION
    // ======================

    var dismiss = '[data-ajax]',
        Ajax = function (el) {
            $(el).on('click', dismiss, this.send)
        };

    Ajax.prototype.send = function (e) {
        var $this = $(this),
            method = $this.data('method'),
            url = $this.attr('href') || $this.data('href') || $this.data('url') || null,
            data = $this.data('data') || null,
            onSuccess = $this.data('success') || null,
            onError = $this.data('error') || null;

        if (e) e.preventDefault();

        if (!url) {
            alert('href, data-href or data-url attribute not found!');
            return
        }

        if (onSuccess) {
            try {
                onSuccess = window[onSuccess];
                if (!$.isFunction(onSuccess))
                    onSuccess = null
            } catch (e) {
                alert(e);
            }
        }

        if (onError) {
            try {
                onError = window[onError];
                if (!$.isFunction(onError))
                    onError = null
            } catch (e) {
                alert(e);
            }
        }

        method = method ? method.toLowerCase() : 'get';

        url = url && url.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7

        data = data && data.replace(/'/g, '"'); // Fix single quote

        try {
            data = $.parseJSON(data)
        } catch (e) {
            alert(method.toUpperCase() + ' ' + url + '   PARSE JSON ERROR' + '\n' + data + '\n' + e);
            return
        }

        if (ajax && $.isFunction(ajax))
            ajax(url, {
                method: method,
                data: data,
                onSuccess: function(response) {
                    processData(response, $this);
                    $.isFunction(onSuccess) && onSuccess(response.content);
                },
                onError: onError
            });
        else
            alert('jquery.ajax.js is required')
    };

    function processData(response, $el) {
        var replace_selector = $el.data('replace'),
            replace_closest_selector = $el.data('replace-closest'),
            replace_inner_selector = $el.data('replace-inner'),
            replace_closest_inner_selector = $el.data('replace-closest-inner'),
            append_selector = $el.data('append'),
            prepend_selector = $el.data('prepend'),
            refresh_selector = $el.data('refresh'),
            refresh_closest_selector = $el.data('refresh-closest'),
            clear_selector = $el.data('clear'),
            clear_closest_selector = $el.data('clear-closest'),
            remove_selector = $el.data('remove'),
            remove_closest_selector = $el.data('remove-closest');

        if (replace_selector) {
            $(replace_selector).replaceWith(response.content)
        }

        if (replace_closest_selector) {
            $el.closest(replace_closest_selector).replaceWith(response.content)
        }

        if (replace_inner_selector) {
            $(replace_inner_selector).html(response.content)
        }

        if (replace_closest_inner_selector) {
            $el.closest(replace_closest_inner_selector).html(response.content)
        }

        if (append_selector) {
            $(append_selector).append(response.content)
        }

        if (prepend_selector) {
            $(prepend_selector).prepend(response.content)
        }

        if (refresh_selector) {
            $.each($(refresh_selector), function(index, value) {
                ajaxGet($(value).data('refresh-url'), function(content) {
                    $(value).replaceWith(content)
                })
            })
        }

        if (refresh_closest_selector) {
            $.each($(refresh_closest_selector), function(index, value) {
                ajaxGet($(value).data('refresh-url'), function(content) {
                    $el.closest($(value)).replaceWith(content)
                })
            })
        }

        if (clear_selector) {
            $(clear_selector).html('')
        }

        if (remove_selector) {
            $(remove_selector).remove()
        }

        if (clear_closest_selector) {
            $el.closest(clear_closest_selector).html('')
        }

        if (remove_closest_selector) {
            $el.closest(remove_closest_selector).remove()
        }
    }

    // ALERT PLUGIN DEFINITION
    // =======================

    var old = $.fn.ajax;

    $.fn.ajax = function (option) {
        return this.each(function () {
            var $this = $(this),
                data  = $this.data('django.ajax');

            if (!data) $this.data('django.ajax', (data = new Ajax(this)));
            if (typeof option == 'string') data[option].call($this)
        })
    };

    $.fn.ajax.Constructor = Ajax;

    // ALERT NO CONFLICT
    // =================

    $.fn.ajax.noConflict = function () {
        $.fn.ajax = old;
        return this
    };

    // ALERT DATA-API
    // ==============

    $(document).on('click.ajax.data-api', dismiss, Ajax.prototype.send)

})(jQuery);
<commit_msg>Better presentation of the exception messages<commit_after>/* ========================================================================
 * Django Ajax v2.2.4
 * https://github.com/yceruto/django-ajax/
 * Copyright 2014 Yonel Ceruto Glez
 * ======================================================================== */

 ;(function ($) { "use strict";

    // AJAX CLASS DEFINITION
    // ======================

    var dismiss = '[data-ajax]',
        Ajax = function (el) {
            $(el).on('click', dismiss, this.send)
        };

    Ajax.prototype.send = function (e) {
        var $this = $(this),
            method = $this.data('method'),
            url = $this.attr('href') || $this.data('href') || $this.data('url') || null,
            data = $this.data('data') || null,
            onSuccess = $this.data('success') || null,
            onError = $this.data('error') || null;

        if (e) e.preventDefault();

        if (!url) {
            alert('href, data-href or data-url attribute not found!');
            return
        }

        if (onSuccess) {
            try {
                onSuccess = window[onSuccess];
                if (!$.isFunction(onSuccess))
                    onSuccess = null
            } catch (e) {
                alert(e.name + '\n' + e.message);
            }
        }

        if (onError) {
            try {
                onError = window[onError];
                if (!$.isFunction(onError))
                    onError = null
            } catch (e) {
                alert(e.name + '\n' + e.message);
            }
        }

        method = method ? method.toLowerCase() : 'get';

        url = url && url.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7

        data = data && data.replace(/'/g, '"'); // Fix single quote

        try {
            data = $.parseJSON(data)
        } catch (e) {
            alert(e.name + '\n' + e.message);
            return
        }

        if (ajax && $.isFunction(ajax))
            ajax(url, {
                method: method,
                data: data,
                onSuccess: function(response) {
                    processData(response, $this);
                    $.isFunction(onSuccess) && onSuccess(response.content);
                },
                onError: onError
            });
        else
            alert('jquery.ajax.js is required')
    };

    function processData(response, $el) {
        var replace_selector = $el.data('replace'),
            replace_closest_selector = $el.data('replace-closest'),
            replace_inner_selector = $el.data('replace-inner'),
            replace_closest_inner_selector = $el.data('replace-closest-inner'),
            append_selector = $el.data('append'),
            prepend_selector = $el.data('prepend'),
            refresh_selector = $el.data('refresh'),
            refresh_closest_selector = $el.data('refresh-closest'),
            clear_selector = $el.data('clear'),
            clear_closest_selector = $el.data('clear-closest'),
            remove_selector = $el.data('remove'),
            remove_closest_selector = $el.data('remove-closest');

        if (replace_selector) {
            $(replace_selector).replaceWith(response.content)
        }

        if (replace_closest_selector) {
            $el.closest(replace_closest_selector).replaceWith(response.content)
        }

        if (replace_inner_selector) {
            $(replace_inner_selector).html(response.content)
        }

        if (replace_closest_inner_selector) {
            $el.closest(replace_closest_inner_selector).html(response.content)
        }

        if (append_selector) {
            $(append_selector).append(response.content)
        }

        if (prepend_selector) {
            $(prepend_selector).prepend(response.content)
        }

        if (refresh_selector) {
            $.each($(refresh_selector), function(index, value) {
                ajaxGet($(value).data('refresh-url'), function(content) {
                    $(value).replaceWith(content)
                })
            })
        }

        if (refresh_closest_selector) {
            $.each($(refresh_closest_selector), function(index, value) {
                ajaxGet($(value).data('refresh-url'), function(content) {
                    $el.closest($(value)).replaceWith(content)
                })
            })
        }

        if (clear_selector) {
            $(clear_selector).html('')
        }

        if (remove_selector) {
            $(remove_selector).remove()
        }

        if (clear_closest_selector) {
            $el.closest(clear_closest_selector).html('')
        }

        if (remove_closest_selector) {
            $el.closest(remove_closest_selector).remove()
        }
    }

    // ALERT PLUGIN DEFINITION
    // =======================

    var old = $.fn.ajax;

    $.fn.ajax = function (option) {
        return this.each(function () {
            var $this = $(this),
                data  = $this.data('django.ajax');

            if (!data) $this.data('django.ajax', (data = new Ajax(this)));
            if (typeof option == 'string') data[option].call($this)
        })
    };

    $.fn.ajax.Constructor = Ajax;

    // ALERT NO CONFLICT
    // =================

    $.fn.ajax.noConflict = function () {
        $.fn.ajax = old;
        return this
    };

    // ALERT DATA-API
    // ==============

    $(document).on('click.ajax.data-api', dismiss, Ajax.prototype.send)

})(jQuery);
<|endoftext|>


<commit_before>var FieldWorkspace = {
  onDragEnterWorkspace: function(event) {
    event.preventDefault();
    FieldWorkspace.updateDragCount(event);
  },

  onDragOverWorkspace: function(event) {
    var type = window.beingDraggedType;
    var text = window.beingDraggedText;
    var index = FieldWorkspace.getIndexForEvent(event);

    var tempElement = '
' + text + '
';

    if(window.currentDropIndex && (index != window.currentDropIndex)) {
      console.log("currentDropIndex is " + window.currentDropIndex + " but actual index is " + index);
      $(".temp").remove();
    }
    console.log("Setting drop index to" + index);
    window.currentDropIndex = index;

    if($(".temp").length < 1) {
      if($(".form-element").length > 0) {
        if(index == 0) {
          $(".form-element:eq(0)").before(tempElement);
        } else {
          $(".form-element:eq(" + (index-1) + ")").after(tempElement);
        }
      } else {
        $("#workspace").append(tempElement);
      }
    }
    $("#workspace").addClass('hover');
    event.preventDefault();
  },

  onDragLeaveWorkspace: function(event) {
    if(window.dragCount < 1) {
      $("#workspace").removeClass('hover')
      $(".temp").remove();
    }
    FieldWorkspace.updateDragCount(event);
  },

  onDragEndWorkspace: function(event) {
    FieldWorkspace.resetDragCount(event);
    $(this).removeClass('hover');
  },

  onDropWorkspace: function(event) {
    var type = event.dataTransfer.getData('text/plain');
    var template = _.template( $('#' + type + '_template').html(), {} );
    var index = FieldWorkspace.getIndexForEvent(event);

    $('.temp').remove();

    if($(".form-element").length > 0) {
      if(index == 0) {
        console.log("Appending before");
        $(".form-element:eq(0)").before(template);
      } else {
        $(".form-element:eq(" + (index-1) + ")").after(template);
      }
    } else {
      $("#workspace").append(template);
    }

    FieldWorkspace.bindLinks();
    FieldWorkspace.reindexFormElements();
    FieldWorkspace.resetDragCount(event);
    event.preventDefault();
  },

  getIndexForEvent: function(event) {
    var middles = _.map($(".form-element"), function(o) {
      var top = $(o).offset().top;
      return top;
    });

    var index = 0;
    _.each(middles, function(t) {
      if(event.clientY > t) {
        index++;
      }
    });
    return index;
  },

  bindLinks: function() {
    $("#workspace a.close-this-element").unbind();
    $("#workspace a.close-this-element").click(function(e) {
      $(this).parents(".form-element").remove();
      FieldWorkspace.reindexFormElements();
      e.preventDefault();
    });

    $("#workspace .pick_one .add-option, #workspace .pick_several .add-option").unbind();
    $("#workspace .pick_one .add-option, #workspace .pick_several .add-option").click(function(e) {
      var newElement = "
<input name='fields[]possible_values[]' class='form-control' />
";
      $(this).parent().siblings('.option-list').append(newElement);
      FieldWorkspace.bindLinks();
      e.preventDefault();
    });

    $("#workspace .pick_one .remove-option, #workspace .pick_several .remove-option").unbind();
    $("#workspace .pick_one .remove-option, #workspace .pick_several .remove-option").click(function(e) {
      $(this).parents('p.input-group').remove();
      e.preventDefault();
    });
  },

  reindexFormElements: function() {
    $("#workspace .form-element").each(function(i, el) {
      $(el).find("input#index").val(i);
    });
  },

  updateDragCount: function(event) {
    var $hierarchy = $(event.target).parents().add(event.target),
    $workspace = $hierarchy.filter('#workspace'),
    countOffset = event.type === 'dragenter' ? 1 : -1;

    var dragCount = ($('#workspace').data('dragCount') || 0) + countOffset;
    $('#workspace')
      .data('dragCount', dragCount)
      .toggleClass('dragging', dragCount > 0);

    //console.log("Dragcount: " + dragCount);
  },

  resetDragCount: function(event) {
    $('#workspace')
      .data('dragCount', 0)
      .removeClass('dragging');

    //console.log("Dragcount: " + 0);
  }

}
<commit_msg>Fixing some drag/drop funkiness.<commit_after>var FieldWorkspace = {
  onDragEnterWorkspace: function(event) {
    event.preventDefault();
    FieldWorkspace.updateDragCount(event);
  },

  onDragOverWorkspace: function(event) {
    var type = window.beingDraggedType;
    var text = window.beingDraggedText;
    var index = FieldWorkspace.getIndexForEvent(event);

    var tempElement = '
' + text + '
';

    if(window.currentDropIndex && (index != window.currentDropIndex)) {
      console.log("currentDropIndex is " + window.currentDropIndex + " but actual index is " + index);
      $(".temp").remove();
    }
    console.log("Setting drop index to" + index);
    window.currentDropIndex = index;

    if($(".temp").length < 1) {
      if($(".form-element").length > 0) {
        if(index == 0) {
          $(".form-element:eq(0)").before(tempElement);
        } else {
          $(".form-element:eq(" + (index-1) + ")").after(tempElement);
        }
      } else {
        $("#workspace").append(tempElement);
      }
    }
    $("#workspace").addClass('hover');
    event.preventDefault();
  },

  onDragLeaveWorkspace: function(event) {
    if($("#workspace").data('dragCount') < 2) {
      $("#workspace").removeClass('hover')
      $(".temp").remove();
    }
    FieldWorkspace.updateDragCount(event);
  },

  onDragEndWorkspace: function(event) {
    FieldWorkspace.resetDragCount(event);
    $(this).removeClass('hover');
  },

  onDropWorkspace: function(event) {
    var type = event.dataTransfer.getData('text/plain');
    var template = _.template( $('#' + type + '_template').html(), {} );
    var index = FieldWorkspace.getIndexForEvent(event);

    $('.temp').remove();

    if($(".form-element").length > 0) {
      if(index == 0) {
        console.log("Appending before");
        $(".form-element:eq(0)").before(template);
      } else {
        $(".form-element:eq(" + (index-1) + ")").after(template);
      }
    } else {
      $("#workspace").append(template);
    }

    FieldWorkspace.bindLinks();
    FieldWorkspace.reindexFormElements();
    FieldWorkspace.resetDragCount(event);
    event.preventDefault();
  },

  getIndexForEvent: function(event) {
    var middles = _.map($(".form-element"), function(o) {
      var top = $(o).offset().top;
      return top;
    });

    var index = 0;
    _.each(middles, function(t) {
      if(event.pageY > t) {
        index++;
      }
    });
    return index;
  },

  bindLinks: function() {
    $("#workspace a.close-this-element").unbind();
    $("#workspace a.close-this-element").click(function(e) {
      $(this).parents(".form-element").remove();
      FieldWorkspace.reindexFormElements();
      e.preventDefault();
    });

    $("#workspace .pick_one .add-option, #workspace .pick_several .add-option").unbind();
    $("#workspace .pick_one .add-option, #workspace .pick_several .add-option").click(function(e) {
      var newElement = "
<input name='fields[]possible_values[]' class='form-control' />
";
      $(this).parent().siblings('.option-list').append(newElement);
      FieldWorkspace.bindLinks();
      e.preventDefault();
    });

    $("#workspace .pick_one .remove-option, #workspace .pick_several .remove-option").unbind();
    $("#workspace .pick_one .remove-option, #workspace .pick_several .remove-option").click(function(e) {
      $(this).parents('p.input-group').remove();
      e.preventDefault();
    });
  },

  reindexFormElements: function() {
    $("#workspace .form-element").each(function(i, el) {
      $(el).find("input#index").val(i);
    });
  },

  updateDragCount: function(event) {
    var $hierarchy = $(event.target).parents().add(event.target),
    $workspace = $hierarchy.filter('#workspace'),
    countOffset = event.type === 'dragenter' ? 1 : -1;

    var dragCount = ($('#workspace').data('dragCount') || 0) + countOffset;
    $('#workspace')
      .data('dragCount', dragCount)
      .toggleClass('dragging', dragCount > 0);

    //console.log("Dragcount: " + dragCount);
  },

  resetDragCount: function(event) {
    $('#workspace')
      .data('dragCount', 0)
      .removeClass('dragging');

    //console.log("Dragcount: " + 0);
  }

}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Francisco Salavert (SGL-CIPF)
 * Copyright (c) 2014 Alejandro Alemán (SGL-CIPF)
 * Copyright (c) 2014 Ignacio Medina (EBI-EMBL)
 *
 * This file is part of JSorolla.
 *
 * JSorolla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * JSorolla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JSorolla. If not, see <http://www.gnu.org/licenses/>.
 */
function EvaFormPanel(args) {
    _.extend(this, Backbone.Events);

    //set default args
    this.id = Utils.genId("FormPanel");
    this.target;
    this.autoRender = true;
    this.height;
    this.width;
    this.title;
    this.collapsible = false;
    this.titleCollapse = false;
    this.submitButtonId = 'submit-btn';
    this.submitButtonText = 'Search';
    this.clearButtonText = 'Clear';
    this.barItems = [];
    this.border = true;
    this.filters = [];
    this.mode = 'accordion';
    this.toolbarPosition = 'top';
    this.headerConfig;
    this.type;

    //set instantiation args, must be last
    _.extend(this, args);

    this.on(this.handlers);

    this.rendered = false;
    if (this.autoRender) {
        this.render(this.targetId);
    }
}

EvaFormPanel.prototype = {
    render: function () {
        var _this = this;
        console.log("Initializing " + this.id);

        //HTML skel
        this.div = document.createElement('div');
        this.div.setAttribute('id', this.id);

        var filters = [];

        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            filters.push(filter.getPanel());
        }

        this.panel = this._createPanel();
        this.filtersPanel.add(filters);
        if (this.mode == 'tabs') {
            this.filtersPanel.setActiveTab(0);
        }

    },
    draw: function () {
        this.targetDiv = (this.target instanceof HTMLElement ) ? this.target : document.querySelector('#' + this.target);
        if (!this.targetDiv) {
            console.log('target not found');
            return;
        }
        this.targetDiv.appendChild(this.div);

        this.panel.render(this.div);

    },
    clear: function () {
        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            filter.clear();
        }
    },
    getValues: function () {
        var values = {};
        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            _.extend(values, filter.getValues());
        }
        return values;
    },
    update: function () {
        if (this.panel) {
            this.panel.update();
        }
    },
    _createPanel: function () {
        var _this = this;

        var barItems = [
            {
                xtype: 'button',
                text: this.clearButtonText,
                tooltip: this.clearButtonText,
                handler: function () {
                    _this.clear();
                    Utils.msg('Clear', 'Successful');
                    if(_this.type == 'variantBrowser'){
                        Ext.Msg.alert('','Please select a Organism/Assembly and enter a valid Positional Filter');
                    }
                }
            },
            {
                xtype: 'button',
                id:this.submitButtonId,
                text: this.submitButtonText,
                tooltip: this.submitButtonText,
                formBind: true,
                flex:1,
                handler: function () {
                    _this.trigger('submit', {values: _this.getValues(), sender: _this});
//                            console.log(values);
//
//                            for (var i = 0; i < _this.filters.length; i++) {
//                                var filter = _this.filters[i];
//                                var form = filter.filter.getForm();
//                                _.extend(values, form);
//
//                            }

                }
            }
        ];
        barItems = barItems.concat(this.barItems);

        var pan;
        switch (this.mode) {
            case 'tabs':
                this.filtersPanel = Ext.create('Ext.tab.Panel', {
                    border: false,
                    plain: true
                });
                break;
            case 'accordion':
                this.filtersPanel = Ext.create('Ext.container.Container', {
                    layout: {
                        type: 'accordion',
                        titleCollapse: true,
                        multi: true
                    }
                });
                break;
            case 'vbox':
                this.filtersPanel = Ext.create('Ext.container.Container', {
                    margin: '5 0 0 0',
                    layout: {
                        type: 'vbox',
                        align: 'stretch'
                    },
                    defaults: {
                        margin: '5 0 0 0'
                    }
                });
                break;
        }

        var form = Ext.create('Ext.form.Panel', {
            border: this.border,
            height: this.height,
            width: this.width,
            title: this.title,
            header: this.headerConfig,
            collapsible: this.collapsible,
            titleCollapse: this.titleCollapse,
            margin: '0 20 0 0',
            autoScroll: true,
            items: [
                {
                    xtype: 'container',
                    width: '100%',
                    bodyPadding: 5,
                    defaults: {
                        margin: '10'
                    },
                    layout: {
                        type: 'hbox'
                    },
                    items: barItems
                },
                this.filtersPanel
            ]
//            dockedItems: [
//                {
//                    xtype: 'toolbar',
//                    dock: this.toolbarPosition,
//                    width: '100%',
//                    height: 45,
////                border:false,
//                    items: barItems
//                }
//            ]
        });
        return form;

    }
}
<commit_msg>varinat browser clear<commit_after>/*
 * Copyright (c) 2014 Francisco Salavert (SGL-CIPF)
 * Copyright (c) 2014 Alejandro Alemán (SGL-CIPF)
 * Copyright (c) 2014 Ignacio Medina (EBI-EMBL)
 *
 * This file is part of JSorolla.
 *
 * JSorolla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * JSorolla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JSorolla. If not, see <http://www.gnu.org/licenses/>.
 */
function EvaFormPanel(args) {
    _.extend(this, Backbone.Events);

    //set default args
    this.id = Utils.genId("FormPanel");
    this.target;
    this.autoRender = true;
    this.height;
    this.width;
    this.title;
    this.collapsible = false;
    this.titleCollapse = false;
    this.submitButtonId = 'submit-btn';
    this.submitButtonText = 'Search';
    this.clearButtonText = 'Clear';
    this.barItems = [];
    this.border = true;
    this.filters = [];
    this.mode = 'accordion';
    this.toolbarPosition = 'top';
    this.headerConfig;
    this.type;

    //set instantiation args, must be last
    _.extend(this, args);

    this.on(this.handlers);

    this.rendered = false;
    if (this.autoRender) {
        this.render(this.targetId);
    }
}

EvaFormPanel.prototype = {
    render: function () {
        var _this = this;
        console.log("Initializing " + this.id);

        //HTML skel
        this.div = document.createElement('div');
        this.div.setAttribute('id', this.id);

        var filters = [];

        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            filters.push(filter.getPanel());
        }

        this.panel = this._createPanel();
        this.filtersPanel.add(filters);
        if (this.mode == 'tabs') {
            this.filtersPanel.setActiveTab(0);
        }

    },
    draw: function () {
        this.targetDiv = (this.target instanceof HTMLElement ) ? this.target : document.querySelector('#' + this.target);
        if (!this.targetDiv) {
            console.log('target not found');
            return;
        }
        this.targetDiv.appendChild(this.div);

        this.panel.render(this.div);

    },
    clear: function () {
        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            filter.clear();
        }
    },
    getValues: function () {
        var values = {};
        for (var i = 0; i < this.filters.length; i++) {
            var filter = this.filters[i];
            _.extend(values, filter.getValues());
        }
        return values;
    },
    update: function () {
        if (this.panel) {
            this.panel.update();
        }
    },
    _createPanel: function () {
        var _this = this;

        var barItems = [
            {
                xtype: 'button',
                text: this.clearButtonText,
                tooltip: this.clearButtonText,
                handler: function () {
                    _this.clear();
                    Utils.msg('Clear', 'Successful');
                    if(_this.type == 'variantBrowser'){
                        Ext.Msg.alert('','Please select a Organism/Assembly and enter a valid Positional Filter');
                    }
                    _this.trigger('submit', {values: _this.getValues(), sender: _this});
                }
            },
            {
                xtype: 'button',
                id:this.submitButtonId,
                text: this.submitButtonText,
                tooltip: this.submitButtonText,
                formBind: true,
                flex:1,
                handler: function () {
                    _this.trigger('submit', {values: _this.getValues(), sender: _this});
//                            console.log(values);
//
//                            for (var i = 0; i < _this.filters.length; i++) {
//                                var filter = _this.filters[i];
//                                var form = filter.filter.getForm();
//                                _.extend(values, form);
//
//                            }

                }
            }
        ];
        barItems = barItems.concat(this.barItems);

        var pan;
        switch (this.mode) {
            case 'tabs':
                this.filtersPanel = Ext.create('Ext.tab.Panel', {
                    border: false,
                    plain: true
                });
                break;
            case 'accordion':
                this.filtersPanel = Ext.create('Ext.container.Container', {
                    layout: {
                        type: 'accordion',
                        titleCollapse: true,
                        multi: true
                    }
                });
                break;
            case 'vbox':
                this.filtersPanel = Ext.create('Ext.container.Container', {
                    margin: '5 0 0 0',
                    layout: {
                        type: 'vbox',
                        align: 'stretch'
                    },
                    defaults: {
                        margin: '5 0 0 0'
                    }
                });
                break;
        }

        var form = Ext.create('Ext.form.Panel', {
            border: this.border,
            height: this.height,
            width: this.width,
            title: this.title,
            header: this.headerConfig,
            collapsible: this.collapsible,
            titleCollapse: this.titleCollapse,
            margin: '0 20 0 0',
            autoScroll: true,
            items: [
                {
                    xtype: 'container',
                    width: '100%',
                    bodyPadding: 5,
                    defaults: {
                        margin: '10'
                    },
                    layout: {
                        type: 'hbox'
                    },
                    items: barItems
                },
                this.filtersPanel
            ]
//            dockedItems: [
//                {
//                    xtype: 'toolbar',
//                    dock: this.toolbarPosition,
//                    width: '100%',
//                    height: 45,
////                border:false,
//                    items: barItems
//                }
//            ]
        });
        return form;

    }
}
<|endoftext|>


<commit_before>
// I'm the directive for the resistance matrix, and I do
// - create the table
// - highlight elements on hover
// - Adjust column and row visibility depending on the filters applied
//
// All data must be loaded, before I am called
infekt.directive( "resistanceMatrix", function( $compile, FilterFactory ) {

	function link( $scope, element, attributes ) {

		// I create the HTML code for the resistance-matrix table.
		// Afterwards I call $compile on it and add the compiled code to
		// the DOM (.resistanceMatrix)
		function createTable( data ) {

			console.log( "Create matrix with data %o", data );

			var table 	= [];

			//
			// create table head
			//
			table.push( "

	" );

			// Take first row of data (bacterium; see getResistanceTable) to get antibacteria's names; take resistance
			// property that contains all antibiotics
			for( var i = 0; i < data[ 0 ].resistances.length; i++ ) {
				table.push( "" + data[ 0 ].resistances[ i ].antibiotic.name + "	" );
			}
			table.push( "

" );

			//
			// table body
			//
			table.push( "
" );

			// Go through data
			// Sort is done in the controller
			for( var i = 0; i < data.length; i++ ) {

				// Row
				table.push( "
" );

				// Row title
				table.push( "" + data[ i ].bacterium.genus + " " + data[ i ].bacterium.species + "	" );

				// Cells with resistances
				for( var j = 0; j < data[ i ].resistances.length; j++ ) {

					var resistance 			= data[ i ].resistances[ j ].resistances;

					var className
						, cellValue;

					// Default resistances:
					// 1: low
					// 2: intermediate
					// 3: high
					// Only display H/L/I, but not values
					if( resistance.type === 'classResistanceDefault' || resistance.type === 'resistanceDefault' ) {
						cellValue = '';
						switch( resistance.value ) {
							case 1:
								className = 'low';
								cellValue = 'L';
								break;
							case 2:
								className = 'intermediate';
								cellValue = 'I';
								break;
							case 3:
								className = 'high';
								cellValue = 'H';
								break;
							default:
								className = 'not-available';
						}
					}

					// Type is missing: No data available.
					// Don't display anything at all
					else if( !resistance.type ) {

						className = 'not-available';
						cellValue = ' ';

					}

					// Detailed values: numbers betwee 0 and 1,
					// < 0.33: low
					// < 0.66: intermediate
					// < 1: high
					else {

						//console.error( resistance );

						// Add classes to cells, depending on resistance data
						if( resistance.value === null ) {
							className	= 'not-available';
							cellValue	= '&nbsp'; // Gives the cell some height
						}

						else if( resistance.value < 33 ) {
							className	= 'low';
							cellValue	= 'L';
						}

						else if (resistance.value < 66 ) {
							className	= 'intermediate';
							cellValue	= 'I';
							//cellValue = resistance.value;
						}

						else {
							className	= 'high';
							cellValue	= 'H';
						}

						//console.error( cellValue );

					}

					table.push( "" + cellValue + "	" );

				}

				table.push( "
" );
			}

			table.push( "
" );

			$compile( table.join( "" ) )( $scope )
				.appendTo( $( ".resistanceMatrix" ) );

			// Make thead sticky
			$( '.resistanceMatrix' ).floatThead();

		}

		//
		// Create the table
		//

		// Redraw table every time resistances change

		// antibiotics and bacteria do not need to be watched, as changes on them automatically
		// call ResistanceFactory.getResistances()
		$scope.$watch( 'resistances', function() {

			var tableContents = $scope.getResistanceTable();

			// Table can't be drawn, because there's no content: return
			if( tableContents.length == 0 ) {
				return;
			}

			createTable( tableContents );

		}, true );

		// MOUSE OVER
		// I listen to mouseovers of cells; if it happens, I determine if
		// the user hovered a colum or row title. If he did, I highlight the whole
		// row/col, else just the single cell and it's titles (row/col).
		element.on( "mouseenter", "th, td", function() {

				//
				// Highlight ROW
				//

				// If on title on a row (first-child of a tr): set highlightRow to row number
				var highlightRow = $( this ).is( ":first-child" );

				// Get row number
				var rowNr = element.find( "tr" ).index( $( this ).parent() );

				//
				// Highlight COLUMN
				//

				// If on title of a col: set highlightCol to true
				var highlightCol = $( this ).is( "th" ) && $( this ).attr( 'scope' ) == "col";

				// Get column number
				if( highlightCol ) {
					var colNr = $( ".resistanceMatrix tr:first th" ).index( $( this ) ) + 1;
				}
				else {
					var colNr = $( $( this ).closest( "tr" ).find( "td" ) ).index( $( this ) ) + 1;
				}

				//console.log( "highlightRow: %o rowNr %o, highlightCol: %o colNr %o", highlightRow, rowNr, highlightCol, colNr );

				// Mouse over top left cell: Don't do anything;
				if( highlightRow === 0 || highlightCol === 0 ) {
					return true;
				}

				// Lighten all elements
				var highlightedClass = 'highlighted';

				// Remove old highlight
				element.find( "th." + highlightedClass + ", td." + highlightedClass ).removeClass( highlightedClass );

				// Highlight row
				if( highlightRow ) {
					element.find( "tr:nth-child( " + rowNr + ") td, tr:nth-child( " + rowNr + ") th" ).addClass( highlightedClass );
				}

				// Highlight col
				else if( highlightCol ) {
					element.find( "tr" ).find( "td:nth-child(" + colNr + "), th[scope='col']:nth-child(" + colNr + ")" ).addClass( highlightedClass );
				}

				// Highlight single cell
				else {
					//$( this ).css( 'opacity', 1 );
					// col title
					element.find( "th[scope='col']:nth-child(" + ( colNr + 1 ) + ")").addClass( highlightedClass );
					// row title
					element.find( "tr:nth-child(" + rowNr + ") th[scope='row']" ).addClass( highlightedClass );
				}

				// Display resistance value
				if( $( this ).is( 'td') ) {
					displayHoverValue( $( this ) );
				}

			} )
			.on( "mouseleave", 'th, td', function() {

				// Display letter (H, L, I)
				if( $( this ).is( 'td' ) ) {
					displayRegularValue( $( this ) );
				}

			} );

		/**
		* On hovering, display value (number)
		*/
		function displayHoverValue( td ) {

			var type		= td.data( 'resistanceType' )
				, value		= td.data( 'resistanceValue' );

			//console.error( '%o - %o', type, value );

			// Type is (class)ResistanceDefault: Don't display numbers
			if( type === 'classResistanceDefault' || type === 'resistanceDefault' || type === 'undefined' ) {
				return;
			}

			td
				// Store original text (for mouseleave)
				.data( 'originalText', td.text() )
				.text( value );

		}

		/**
		* On mouseleave, display simpler value
		*/
		function displayRegularValue( td ) {
			if( td.data( 'originalText' ) ) {
				td.text( td.data( 'originalText' ) );
				td.removeData( 'originalText' );
			}
		}

		// FILTER CHANGES
		// I listen to changes of the filter and adjust the visiblity of columns/rows
		// depending on it

		// Watch for changes of getFilters in $scope (watch deeply!); when they change, update the table
		$scope.$watch( 'getFilters()', function( newFilter, oldFilter) {

			updateColVisibility();
			updateRowVisibility();

		}, true )

		function updateRowVisibility() {

			// Get filters for ba	cteria
			var filters = FilterFactory.getFilters( 'bacterium' );

			console.log( 'Bacteria filters: %o', filters );

			// Loop through all bacterias – they need to have the same order that was used to create the table;
			// If they don't match filter, hide them. Else show them.
			var allBacteria = $scope.getBacteriaSorted();
			for( var i = 0; i < allBacteria.length; i++ ) {

				var itemVisible = checkItemAgainstFilters( allBacteria[ i ], filters ) ? "show" : "hide";

				// Add 1 to i for the table header row
				var row 		= element.find( "tbody tr[data-bacterium-id='" + allBacteria[ i ].id + "']" );

				row[ itemVisible ]();

			}

		}

		function updateColVisibility() {

			// Get filters for bacteria
			var filters = FilterFactory.getFilters( 'antibiotic' );

			// Loop through all antibiotics – they need to have the same order that was used to create the table;
			// If they don't match filter, hide them. Else show them.
			var allAntibiotics = $scope.getAntibioticsSorted();

			var toHide = [];
			var toShow = [];

			for( var i = 0; i < allAntibiotics.length; i++ ) {

				// Check if current item should be visible or not
				var itemVisible 	= checkItemAgainstFilters( allAntibiotics[ i ], filters ) ? "show" : "hide";

				// Get column number for current antibiotic
				var colNr			= $( '.resistanceMatrix thead [data-antibiotic-id=\'' + allAntibiotics[ i ].id + '\'' ).index();

				console.error( colNr );

				// Get all cells of current antibiotic; index is 0-based, nth-child not
				var cells 			= $( ".resistanceMatrix" ).find( "td:nth-child(" + ( colNr + 1 ) + "), th:nth-child(" + ( ( colNr + 1 ) ) + ")" );

				cells[ itemVisible ]();

			}

		};

		// Scroll: FIX table head (add class .fixed to tableHead after scrolling too much)
		$( window ).scroll( function() {
			var tableHead 		= element.find( "thead:first" )
				, tableHeadTop 	= tableHead.offset().top - parseInt( $( "#content" ).css( 'margin-top' ), 10 )
				, scrollTop 	= $( document ).scrollTop();

			if( tableHeadTop < scrollTop ) {
				tableHead.addClass( "fixed" );
			}
			else {
				tableHead.removeClass( "fixed" );
			}

		} );

		/**
		* I check, if the item i matches filters, i.e. if it's properties and their values are the same
		* as the ones given in filters.
		*
		* @param item			antibiotic or bacterium
		* @param filter			Array with items from SearchTableFactory.searchTable
		*
		* @return <Boolean>		true if item matches filter, else false
		*/
		function checkItemAgainstFilters( item, filters ) {

			console.log( "resistanceMatrixDirective: check if item %o matches filters %o", item, filters );

			// For this type of item (bacteria or antibiotic) no filters were chosen, i.e. filter object has no properties:
			// There's nothing to hide, just return true
			var filterPropertyCount = 0;
			for( var i in filters ) {
				filterPropertyCount++;
			}
			if( filterPropertyCount == 0 ) {
				return true;
			}

			// Loop through filter types (substance, gram etc) – contains multiple results for that type of filter
			for( var type in filters ) {

				var filterType 					= filters[ type ]

					// Number of filters that have matched for this type; if type is gram and value is 1 or 0,
					// and bacterium is gram+, filtersMatched will be 1
					, filtersMatched 			= 0;

				// Loop through filters for each filter type
				for( var i = 0; i < filterType.length; i++ ) {

					//console.error( "is match? %o %o %o %o", item.genus, filterType[ i ].containers.indexOf( item ), item );

					if( filterType[ i ].containers.indexOf( item ) !== -1 ) {
						filtersMatched++;
					}

				}

				// No filter matched for this type
				if( filtersMatched == 0 ) {
					return false;
				}

			}

			// item was in all filters.containers: return true
			//console.log( "resistanceMatrixDirective: item %o did match all filters %o", item, filters );
			return true;

		}

	}

	return {
		restrict: "C"
		, link	: link
	}

} );

<commit_msg>Fixed highlight issue (when hovering first row head, header was highlighted)<commit_after>
// I'm the directive for the resistance matrix, and I do
// - create the table
// - highlight elements on hover
// - Adjust column and row visibility depending on the filters applied
//
// All data must be loaded, before I am called
infekt.directive( "resistanceMatrix", function( $compile, FilterFactory ) {

	function link( $scope, element, attributes ) {

		// I create the HTML code for the resistance-matrix table.
		// Afterwards I call $compile on it and add the compiled code to
		// the DOM (.resistanceMatrix)
		function createTable( data ) {

			console.log( "Create matrix with data %o", data );

			var table 	= [];

			//
			// create table head
			//
			table.push( "

	" );

			// Take first row of data (bacterium; see getResistanceTable) to get antibacteria's names; take resistance
			// property that contains all antibiotics
			for( var i = 0; i < data[ 0 ].resistances.length; i++ ) {
				table.push( "" + data[ 0 ].resistances[ i ].antibiotic.name + "	" );
			}
			table.push( "

" );

			//
			// table body
			//
			table.push( "
" );

			// Go through data
			// Sort is done in the controller
			for( var i = 0; i < data.length; i++ ) {

				// Row
				table.push( "
" );

				// Row title
				table.push( "" + data[ i ].bacterium.genus + " " + data[ i ].bacterium.species + "	" );

				// Cells with resistances
				for( var j = 0; j < data[ i ].resistances.length; j++ ) {

					var resistance 			= data[ i ].resistances[ j ].resistances;

					var className
						, cellValue;

					// Default resistances:
					// 1: low
					// 2: intermediate
					// 3: high
					// Only display H/L/I, but not values
					if( resistance.type === 'classResistanceDefault' || resistance.type === 'resistanceDefault' ) {
						cellValue = '';
						switch( resistance.value ) {
							case 1:
								className = 'low';
								cellValue = 'L';
								break;
							case 2:
								className = 'intermediate';
								cellValue = 'I';
								break;
							case 3:
								className = 'high';
								cellValue = 'H';
								break;
							default:
								className = 'not-available';
						}
					}

					// Type is missing: No data available.
					// Don't display anything at all
					else if( !resistance.type ) {

						className = 'not-available';
						cellValue = ' ';

					}

					// Detailed values: numbers betwee 0 and 1,
					// < 0.33: low
					// < 0.66: intermediate
					// < 1: high
					else {

						//console.error( resistance );

						// Add classes to cells, depending on resistance data
						if( resistance.value === null ) {
							className	= 'not-available';
							cellValue	= '&nbsp'; // Gives the cell some height
						}

						else if( resistance.value < 33 ) {
							className	= 'low';
							cellValue	= 'L';
						}

						else if (resistance.value < 66 ) {
							className	= 'intermediate';
							cellValue	= 'I';
							//cellValue = resistance.value;
						}

						else {
							className	= 'high';
							cellValue	= 'H';
						}

						//console.error( cellValue );

					}

					table.push( "" + cellValue + "	" );

				}

				table.push( "
" );
			}

			table.push( "
" );

			$compile( table.join( "" ) )( $scope )
				.appendTo( $( ".resistanceMatrix" ) );

			// Make thead sticky
			//$( '.resistanceMatrix' ).floatThead();

		}

		//
		// Create the table
		//

		// Redraw table every time resistances change

		// antibiotics and bacteria do not need to be watched, as changes on them automatically
		// call ResistanceFactory.getResistances()
		$scope.$watch( 'resistances', function() {

			var tableContents = $scope.getResistanceTable();

			// Table can't be drawn, because there's no content: return
			if( tableContents.length == 0 ) {
				return;
			}

			createTable( tableContents );

		}, true );

		// MOUSE OVER
		// I listen to mouseovers of cells; if it happens, I determine if
		// the user hovered a colum or row title. If he did, I highlight the whole
		// row/col, else just the single cell and it's titles (row/col).
		element.on( "mouseenter", "th, td", function() {

				//
				// Highlight ROW
				//

				// If on title on a row (first-child of a tr): set highlightRow to row number
				var highlightRow = $( this ).is( ":first-child" );

				// Get row number
				var rowNr = element.find( "tr" ).index( $( this ).parent() );

				//
				// Highlight COLUMN
				//

				// If on title of a col: set highlightCol to true
				var highlightCol = $( this ).is( "th" ) && $( this ).attr( 'scope' ) == "col";

				// Get column number
				if( highlightCol ) {
					var colNr = $( ".resistanceMatrix tr:first th" ).index( $( this ) ) + 1;
				}
				else {
					var colNr = $( $( this ).closest( "tr" ).find( "td" ) ).index( $( this ) ) + 1;
				}

				//console.log( "highlightRow: %o rowNr %o, highlightCol: %o colNr %o", highlightRow, rowNr, highlightCol, colNr );

				// Mouse over top left cell: Don't do anything;
				if( highlightRow === 0 || highlightCol === 0 ) {
					return true;
				}

				// Lighten all elements
				var highlightedClass = 'highlighted';

				// Remove old highlight
				element.find( "th." + highlightedClass + ", td." + highlightedClass ).removeClass( highlightedClass );

				// Highlight row
				if( highlightRow ) {
					element.find( "tbody tr:nth-child( " + rowNr + ") td, tbody tr:nth-child( " + rowNr + ") th" ).addClass( highlightedClass );
				}

				// Highlight col
				else if( highlightCol ) {
					element.find( "tr" ).find( "td:nth-child(" + colNr + "), th[scope='col']:nth-child(" + colNr + ")" ).addClass( highlightedClass );
				}

				// Highlight single cell
				else {
					//$( this ).css( 'opacity', 1 );
					// col title
					element.find( "th[scope='col']:nth-child(" + ( colNr + 1 ) + ")").addClass( highlightedClass );
					// row title
					element.find( "tr:nth-child(" + rowNr + ") th[scope='row']" ).addClass( highlightedClass );
				}

				// Display resistance value
				if( $( this ).is( 'td') ) {
					displayHoverValue( $( this ) );
				}

			} )
			.on( "mouseleave", 'th, td', function() {

				// Display letter (H, L, I)
				if( $( this ).is( 'td' ) ) {
					displayRegularValue( $( this ) );
				}

			} );

		/**
		* On hovering, display value (number)
		*/
		function displayHoverValue( td ) {

			var type		= td.data( 'resistanceType' )
				, value		= td.data( 'resistanceValue' );

			//console.error( '%o - %o', type, value );

			// Type is (class)ResistanceDefault: Don't display numbers
			if( type === 'classResistanceDefault' || type === 'resistanceDefault' || type === 'undefined' ) {
				return;
			}

			td
				// Store original text (for mouseleave)
				.data( 'originalText', td.text() )
				.text( value );

		}

		/**
		* On mouseleave, display simpler value
		*/
		function displayRegularValue( td ) {
			if( td.data( 'originalText' ) ) {
				td.text( td.data( 'originalText' ) );
				td.removeData( 'originalText' );
			}
		}

		// FILTER CHANGES
		// I listen to changes of the filter and adjust the visiblity of columns/rows
		// depending on it

		// Watch for changes of getFilters in $scope (watch deeply!); when they change, update the table
		$scope.$watch( 'getFilters()', function( newFilter, oldFilter) {

			updateColVisibility();
			updateRowVisibility();

		}, true )

		function updateRowVisibility() {

			// Get filters for ba	cteria
			var filters = FilterFactory.getFilters( 'bacterium' );

			console.log( 'Bacteria filters: %o', filters );

			// Loop through all bacterias – they need to have the same order that was used to create the table;
			// If they don't match filter, hide them. Else show them.
			var allBacteria = $scope.getBacteriaSorted();
			for( var i = 0; i < allBacteria.length; i++ ) {

				var itemVisible = checkItemAgainstFilters( allBacteria[ i ], filters ) ? "show" : "hide";

				// Add 1 to i for the table header row
				var row 		= element.find( "tbody tr[data-bacterium-id='" + allBacteria[ i ].id + "']" );

				row[ itemVisible ]();

			}

		}

		function updateColVisibility() {

			// Get filters for bacteria
			var filters = FilterFactory.getFilters( 'antibiotic' );

			// Loop through all antibiotics – they need to have the same order that was used to create the table;
			// If they don't match filter, hide them. Else show them.
			var allAntibiotics = $scope.getAntibioticsSorted();

			var toHide = [];
			var toShow = [];

			for( var i = 0; i < allAntibiotics.length; i++ ) {

				// Check if current item should be visible or not
				var itemVisible 	= checkItemAgainstFilters( allAntibiotics[ i ], filters ) ? "show" : "hide";

				// Get column number for current antibiotic
				var colNr			= $( '.resistanceMatrix thead [data-antibiotic-id=\'' + allAntibiotics[ i ].id + '\'' ).index();

				console.error( colNr );

				// Get all cells of current antibiotic; index is 0-based, nth-child not
				var cells 			= $( ".resistanceMatrix" ).find( "td:nth-child(" + ( colNr + 1 ) + "), th:nth-child(" + ( ( colNr + 1 ) ) + ")" );

				cells[ itemVisible ]();

			}

		};

		// Scroll: FIX table head (add class .fixed to tableHead after scrolling too much)
		$( window ).scroll( function() {
			var tableHead 		= element.find( "thead:first" )
				, tableHeadTop 	= tableHead.offset().top - parseInt( $( "#content" ).css( 'margin-top' ), 10 )
				, scrollTop 	= $( document ).scrollTop();

			if( tableHeadTop < scrollTop ) {
				tableHead.addClass( "fixed" );
			}
			else {
				tableHead.removeClass( "fixed" );
			}

		} );

		/**
		* I check, if the item i matches filters, i.e. if it's properties and their values are the same
		* as the ones given in filters.
		*
		* @param item			antibiotic or bacterium
		* @param filter			Array with items from SearchTableFactory.searchTable
		*
		* @return <Boolean>		true if item matches filter, else false
		*/
		function checkItemAgainstFilters( item, filters ) {

			console.log( "resistanceMatrixDirective: check if item %o matches filters %o", item, filters );

			// For this type of item (bacteria or antibiotic) no filters were chosen, i.e. filter object has no properties:
			// There's nothing to hide, just return true
			var filterPropertyCount = 0;
			for( var i in filters ) {
				filterPropertyCount++;
			}
			if( filterPropertyCount == 0 ) {
				return true;
			}

			// Loop through filter types (substance, gram etc) – contains multiple results for that type of filter
			for( var type in filters ) {

				var filterType 					= filters[ type ]

					// Number of filters that have matched for this type; if type is gram and value is 1 or 0,
					// and bacterium is gram+, filtersMatched will be 1
					, filtersMatched 			= 0;

				// Loop through filters for each filter type
				for( var i = 0; i < filterType.length; i++ ) {

					//console.error( "is match? %o %o %o %o", item.genus, filterType[ i ].containers.indexOf( item ), item );

					if( filterType[ i ].containers.indexOf( item ) !== -1 ) {
						filtersMatched++;
					}

				}

				// No filter matched for this type
				if( filtersMatched == 0 ) {
					return false;
				}

			}

			// item was in all filters.containers: return true
			//console.log( "resistanceMatrixDirective: item %o did match all filters %o", item, filters );
			return true;

		}

	}

	return {
		restrict: "C"
		, link	: link
	}

} );

<|endoftext|>