<commit_before>(function(undefined) {
  // The Opal object that is exposed globally
  var Opal = this.Opal = {};

  // Very root class
  function BasicObject(){}

  // Core Object class
  function Object(){}

  // Class' class
  function Class(){}

  // the class of nil
  function NilClass(){}

  // TopScope is used for inheriting constants from the top scope
  var TopScope = function(){};

  // Opal just acts as the top scope
  TopScope.prototype = Opal;

  // To inherit scopes
  Opal.constructor  = TopScope;

  // This is a useful reference to global object inside ruby files
  Opal.global = this;

  // Minify common function calls
  var __hasOwn = Opal.hasOwnProperty;
  var __slice  = Opal.slice = Array.prototype.slice;

  // Generates unique id for every ruby object
  var unique_id = 0;

  // Return next unique id
  Opal.uid = function() {
    return unique_id++;
  };

  // Table holds all class variables
  Opal.cvars = {};

  // Globals table
  Opal.gvars = {};

  /*
   * Create a new constants scope for the given class with the given
   * base. Constants are looked up through their parents, so the base
   * scope will be the outer scope of the new klass.
   */
  function create_scope(base, klass, id) {
    var const_alloc   = function() {};
    var const_scope   = const_alloc.prototype = new base.constructor();
    klass._scope      = const_scope;
    const_scope.base  = klass;
    const_scope.constructor = const_alloc;

    if (id) {
      base[id] = base.constructor[id] = klass;
    }
  }

  /*
    Define a bridged class. Bridged classes will always be in the top level
    scope, and will always be a subclass of Object.
  */
  Opal.bridge = function(name, constructor) {
    var klass = bridge_class(constructor);

    klass._name = name;

    create_scope(Opal, klass, name);

    return klass;
  };

  Opal.klass = function(base, superklass, id, constructor) {
    var klass;
    if (typeof(base) !== 'function') {
      base = base.constructor;
    }

    if (superklass === null) {
      superklass = Object;
    }

    if (__hasOwn.call(base._scope, id)) {
      klass = base._scope[id];
    }
    else {
      klass = boot_class(superklass, constructor);

      klass._name = (base === Object ? id : base._name + '::' + id);

      create_scope(base._scope, klass);

      base[id] = base._scope[id] = klass;

      if (superklass.$inherited) {
        superklass.$inherited(klass);
      }
    }

    return klass;
  };

  // Define new module (or return existing module)
  Opal.module = function(base, id, constructor) {
    var klass;
    if (typeof(base) !== 'function') {
      base = base.constructor;
    }

    if (__hasOwn.call(base._scope, id)) {
      klass = base._scope[id];
    }
    else {
      klass = boot_class(Class, constructor);
      klass._name = (base === Object ? id : base._name + '::' + id);

      klass._included_in = [];

      create_scope(base._scope, klass, id);
    }

    return klass;
  };

  // Utility function to raise a "no block given" error
  var no_block_given = function() {
    throw new Error('no block given');
  };

  // Boot a base class (makes instances).
  var boot_defclass = function(id, constructor, superklass) {
    if (superklass) {
      var ctor           = function() {};
          ctor.prototype = superklass.prototype;

      constructor.prototype = new ctor();
    }

    var prototype = constructor.prototype;

    prototype.constructor = constructor;
    prototype.constructor      = constructor;

    constructor._name         = id;
    constructor._super        = superklass;
    constructor._methods      = [];
    constructor._smethods     = [];

    constructor['$==='] = module_eqq;
    constructor.$to_s = module_to_s;
    constructor.toString = module_to_s;

    Opal[id] = constructor;

    return constructor;
  };

  // Create generic class with given superclass.
  var boot_class = Opal.boot = function(superklass, constructor) {
    var ctor = function() {};
        ctor.prototype = superklass.prototype;

    constructor.prototype = new ctor();
    var prototype = constructor.prototype;

    prototype.constructor = constructor;

    constructor._super        = superklass;
    constructor._methods      = [];
    constructor.constructor   = Class;

    constructor['$===']  = module_eqq;
    constructor.$to_s    = module_to_s;
    constructor.toString = module_to_s;

    constructor['$[]'] = undefined;
    constructor.$call  = undefined;

    var smethods;

    smethods = superklass._smethods.slice();

    constructor._smethods = smethods;
    for (var i = 0, length = smethods.length; i < length; i++) {
      var m = smethods[i];
      constructor[m] = superklass[m];
    }

    var inherited = superklass._inherited;

    if (!inherited) {
      inherited = superklass._inherited = [];
    }

    inherited.push(constructor);

    return constructor;
  };

  var bridge_class = function(constructor) {
    var i, length, m;

    constructor.prototype.constructor = constructor;

    constructor._super        = Object;
    constructor.constructor   = Class;
    constructor._methods      = [];
    constructor._smethods     = [];

    constructor['$===']  = module_eqq;
    constructor.$to_s    = module_to_s;
    constructor.toString = module_to_s;

    var smethods = constructor._smethods = Class._methods.slice();
    for (i = 0, length = smethods.length; i < length; i++) {
      m = smethods[i];
      constructor[m] = Object[m];
    }

    bridged_classes.push(constructor);

    var table = Object.prototype, methods = Object._methods;

    for (i = 0, length = methods.length; i < length; i++) {
      m = methods[i];
      constructor.prototype[m] = table[m];
    }

    constructor._smethods.push('$allocate');

    return constructor;
  };

  Opal.puts = function(a) { console.log(a); };

  // Method missing dispatcher
  Opal.mm = function(mid) {
    var dispatcher = function() {
      var args = __slice.call(arguments);

      if (this.$method_missing) {
        this.$method_missing._p = dispatcher._p;
        return this.$method_missing.apply(this, [mid].concat(args));
      }
      else {
        native_send._p = dispatcher._p;
        return native_send(this, mid, args);
      }
    };

    return dispatcher;
  };

  // send a method to a native object
  var native_send = function(obj, mid, args) {
    var prop, block = native_send._p;
    native_send._p = null;

    if ( (prop = native_methods[mid]) ) {
      return prop(obj, args, block);
    }

    prop = obj[mid];

    if (typeof(prop) === "function") {
      prop = prop.apply(obj, args.$to_native());
    }
    else if (mid.charAt(mid.length - 1) === "=") {
      prop = mid.slice(0, mid.length - 1);
      return obj[prop] = args[0];
    }

    if (prop != null) {
      return prop;
    }

    return null;
  };

  var native_methods = {
    "==": function(obj, args) {
      return obj === args[0];
    },

    "[]": function(obj, args) {
      return obj[args[0]];
    },

    "[]=": function(obj, args) {
      return obj[args[0]] = args[1];
    },

    "inspect": function(obj) {
      return obj.toString();
    },

    "to_s": function(obj) {
      return obj.toString();
    },

    "respond_to?": function(obj, args) {
      return obj[args[0]] != null;
    },

    "each": function(obj, args, block) {
      if (obj.length === +obj.length) {
        for (var i = 0, len = obj.length; i < len; i++) {
          block(obj[i]);
        }
      }
      else {
        for (var key in obj) {
          block(key, obj[key]);
        }
      }

      return obj;
    },

    "to_a": function(obj, args) {
      var result = [];

      for (var i = 0, length = obj.length; i < length; i++) {
        result.push(obj[i]);
      }

      return result;
    },

    "to_h": function(obj) {
      var keys = [], values = {}, value;

      for (var key in obj) {
        keys.push(key);
        values[key] = obj[key];
      }

      return Opal.hash2(keys, values);
    }
  };

  // Const missing dispatcher
  Opal.cm = function(name) {
    return this.base.$const_missing(name);
  };

  // Arity count error dispatcher
  Opal.ac = function(actual, expected, object, meth) {
    var inspect = ((typeof(object) !== 'function') ? object.constructor._name + '#' : object._name + '.') + meth;
    var msg = '[' + inspect + '] wrong number of arguments(' + actual + ' for ' + expected + ')';
    throw Opal.ArgumentError.$new(msg);
  };

  /*
    Call a ruby method on a ruby object with some arguments:

      var my_array = [1, 2, 3, 4]
      Opal.send(my_array, 'length')     # => 4
      Opal.send(my_array, 'reverse!')   # => [4, 3, 2, 1]

    A missing method will be forwarded to the object via
    method_missing.

    The result of either call with be returned.

    @param [Object] recv the ruby object
    @param [String] mid ruby method to call
  */
  Opal.send = function(recv, mid) {
    var args = __slice.call(arguments, 2),
        func = recv['$' + mid];

    if (func) {
      return func.apply(recv, args);
    }

    return recv.$method_missing.apply(recv, [mid].concat(args));
  };

  Opal.block_send = function(recv, mid, block) {
    var args = __slice.call(arguments, 3),
        func = recv['$' + mid];

    if (func) {
      func._p = block;
      return func.apply(recv, args);
    }

    return recv.$method_missing.apply(recv, [mid].concat(args));
  };

  // Implementation of Class#===
  function module_eqq(object) {
    if (object == null) {
      return false;
    }

    var search = object.constructor;

    while (search) {
      if (search === this) {
        return true;
      }

      search = search._super;
    }

    return false;
  }

  // Implementation of Class#to_s
  function module_to_s() {
    return this._name;
  }

  /**
   * Donate methods for a class/module
   */
  Opal.donate = function(klass, defined, indirect) {
    var methods = klass._methods, included_in = klass._included_in;

    // if (!indirect) {
      klass._methods = methods.concat(defined);
    // }

    if (included_in) {
      for (var i = 0, length = included_in.length; i < length; i++) {
        var includee = included_in[i];
        var dest = includee.prototype;

        for (var j = 0, jj = defined.length; j < jj; j++) {
          var method = defined[j];
          dest[method] = klass.prototype[method];
        }

        if (includee._included_in) {
          Opal.donate(includee, defined, true);
        }
      }
    }
  };

  /*
    Define a singleton method on the given klass

        Opal.defs(Array, '$foo', function() {})

    @param [Function] klass
    @param [String] mid the method_id
    @param [Function] body function body
  */
  Opal.defs = function(klass, mid, body) {
    klass._smethods.push(mid);
    klass[mid] = body;

    var inherited = klass._inherited;
    if (inherited && inherited.length) {
      for (var i = 0, length = inherited.length, subclass; i < length; i++) {
        subclass = inherited[i];
        if (!subclass[mid]) {
          Opal.defs(subclass, mid, body);
        }
      }
    }
  };

  // Defines methods onto Object (which are then donated to bridged classes)
  Object._defn = function (mid, body) {
    this.prototype[mid] = body;
    Opal.donate(this, [mid]);
  };

  // Initialization
  // --------------

  boot_defclass('BasicObject', BasicObject);
  boot_defclass('Object', Object, BasicObject);
  boot_defclass('Class', Class, Object);

  Class.prototype = Function.prototype;

  BasicObject.constructor = Object.constructor = Class.constructor = Class;

  var bridged_classes = Object._included_in = [];

  Opal.base = Object;
  BasicObject._scope = Object._scope = Opal;
  Opal.Module = Opal.Class;
  Opal.Kernel = Object;

  create_scope(Opal, Class);

  Object.prototype.toString = function() {
    return this.$to_s();
  };

  Opal.top = new Object;

  Opal.klass(Object, Object, 'NilClass', NilClass);

  var nil = Opal.nil = new NilClass;
  nil.call = nil.apply = function() { throw Opal.LocalJumpError.$new('no block given'); };

  Opal.breaker  = new Error('unexpected break');

  Opal.bridge('Array', Array);
  Opal.bridge('Boolean', Boolean);
  Opal.bridge('Numeric', Number);
  Opal.bridge('String', String);
  Opal.bridge('Proc', Function);
  Opal.bridge('Exception', Error);
  Opal.bridge('Regexp', RegExp);
  Opal.bridge('Time', Date);
}).call(this);
<commit_msg>Remove one more null == null check<commit_after>(function(undefined) {
  // The Opal object that is exposed globally
  var Opal = this.Opal = {};

  // Very root class
  function BasicObject(){}

  // Core Object class
  function Object(){}

  // Class' class
  function Class(){}

  // the class of nil
  function NilClass(){}

  // TopScope is used for inheriting constants from the top scope
  var TopScope = function(){};

  // Opal just acts as the top scope
  TopScope.prototype = Opal;

  // To inherit scopes
  Opal.constructor  = TopScope;

  // This is a useful reference to global object inside ruby files
  Opal.global = this;

  // Minify common function calls
  var __hasOwn = Opal.hasOwnProperty;
  var __slice  = Opal.slice = Array.prototype.slice;

  // Generates unique id for every ruby object
  var unique_id = 0;

  // Return next unique id
  Opal.uid = function() {
    return unique_id++;
  };

  // Table holds all class variables
  Opal.cvars = {};

  // Globals table
  Opal.gvars = {};

  /*
   * Create a new constants scope for the given class with the given
   * base. Constants are looked up through their parents, so the base
   * scope will be the outer scope of the new klass.
   */
  function create_scope(base, klass, id) {
    var const_alloc   = function() {};
    var const_scope   = const_alloc.prototype = new base.constructor();
    klass._scope      = const_scope;
    const_scope.base  = klass;
    const_scope.constructor = const_alloc;

    if (id) {
      base[id] = base.constructor[id] = klass;
    }
  }

  /*
    Define a bridged class. Bridged classes will always be in the top level
    scope, and will always be a subclass of Object.
  */
  Opal.bridge = function(name, constructor) {
    var klass = bridge_class(constructor);

    klass._name = name;

    create_scope(Opal, klass, name);

    return klass;
  };

  Opal.klass = function(base, superklass, id, constructor) {
    var klass;
    if (typeof(base) !== 'function') {
      base = base.constructor;
    }

    if (superklass === null) {
      superklass = Object;
    }

    if (__hasOwn.call(base._scope, id)) {
      klass = base._scope[id];
    }
    else {
      klass = boot_class(superklass, constructor);

      klass._name = (base === Object ? id : base._name + '::' + id);

      create_scope(base._scope, klass);

      base[id] = base._scope[id] = klass;

      if (superklass.$inherited) {
        superklass.$inherited(klass);
      }
    }

    return klass;
  };

  // Define new module (or return existing module)
  Opal.module = function(base, id, constructor) {
    var klass;
    if (typeof(base) !== 'function') {
      base = base.constructor;
    }

    if (__hasOwn.call(base._scope, id)) {
      klass = base._scope[id];
    }
    else {
      klass = boot_class(Class, constructor);
      klass._name = (base === Object ? id : base._name + '::' + id);

      klass._included_in = [];

      create_scope(base._scope, klass, id);
    }

    return klass;
  };

  // Utility function to raise a "no block given" error
  var no_block_given = function() {
    throw new Error('no block given');
  };

  // Boot a base class (makes instances).
  var boot_defclass = function(id, constructor, superklass) {
    if (superklass) {
      var ctor           = function() {};
          ctor.prototype = superklass.prototype;

      constructor.prototype = new ctor();
    }

    var prototype = constructor.prototype;

    prototype.constructor = constructor;
    prototype.constructor      = constructor;

    constructor._name         = id;
    constructor._super        = superklass;
    constructor._methods      = [];
    constructor._smethods     = [];

    constructor['$==='] = module_eqq;
    constructor.$to_s = module_to_s;
    constructor.toString = module_to_s;

    Opal[id] = constructor;

    return constructor;
  };

  // Create generic class with given superclass.
  var boot_class = Opal.boot = function(superklass, constructor) {
    var ctor = function() {};
        ctor.prototype = superklass.prototype;

    constructor.prototype = new ctor();
    var prototype = constructor.prototype;

    prototype.constructor = constructor;

    constructor._super        = superklass;
    constructor._methods      = [];
    constructor.constructor   = Class;

    constructor['$===']  = module_eqq;
    constructor.$to_s    = module_to_s;
    constructor.toString = module_to_s;

    constructor['$[]'] = undefined;
    constructor.$call  = undefined;

    var smethods;

    smethods = superklass._smethods.slice();

    constructor._smethods = smethods;
    for (var i = 0, length = smethods.length; i < length; i++) {
      var m = smethods[i];
      constructor[m] = superklass[m];
    }

    var inherited = superklass._inherited;

    if (!inherited) {
      inherited = superklass._inherited = [];
    }

    inherited.push(constructor);

    return constructor;
  };

  var bridge_class = function(constructor) {
    var i, length, m;

    constructor.prototype.constructor = constructor;

    constructor._super        = Object;
    constructor.constructor   = Class;
    constructor._methods      = [];
    constructor._smethods     = [];

    constructor['$===']  = module_eqq;
    constructor.$to_s    = module_to_s;
    constructor.toString = module_to_s;

    var smethods = constructor._smethods = Class._methods.slice();
    for (i = 0, length = smethods.length; i < length; i++) {
      m = smethods[i];
      constructor[m] = Object[m];
    }

    bridged_classes.push(constructor);

    var table = Object.prototype, methods = Object._methods;

    for (i = 0, length = methods.length; i < length; i++) {
      m = methods[i];
      constructor.prototype[m] = table[m];
    }

    constructor._smethods.push('$allocate');

    return constructor;
  };

  Opal.puts = function(a) { console.log(a); };

  // Method missing dispatcher
  Opal.mm = function(mid) {
    var dispatcher = function() {
      var args = __slice.call(arguments);

      if (this.$method_missing) {
        this.$method_missing._p = dispatcher._p;
        return this.$method_missing.apply(this, [mid].concat(args));
      }
      else {
        native_send._p = dispatcher._p;
        return native_send(this, mid, args);
      }
    };

    return dispatcher;
  };

  // send a method to a native object
  var native_send = function(obj, mid, args) {
    var prop, block = native_send._p;
    native_send._p = null;

    if ( (prop = native_methods[mid]) ) {
      return prop(obj, args, block);
    }

    prop = obj[mid];

    if (typeof(prop) === "function") {
      prop = prop.apply(obj, args.$to_native());
    }
    else if (mid.charAt(mid.length - 1) === "=") {
      prop = mid.slice(0, mid.length - 1);
      return obj[prop] = args[0];
    }

    return prop;
  };

  var native_methods = {
    "==": function(obj, args) {
      return obj === args[0];
    },

    "[]": function(obj, args) {
      return obj[args[0]];
    },

    "[]=": function(obj, args) {
      return obj[args[0]] = args[1];
    },

    "inspect": function(obj) {
      return obj.toString();
    },

    "to_s": function(obj) {
      return obj.toString();
    },

    "respond_to?": function(obj, args) {
      return obj[args[0]] != null;
    },

    "each": function(obj, args, block) {
      if (obj.length === +obj.length) {
        for (var i = 0, len = obj.length; i < len; i++) {
          block(obj[i]);
        }
      }
      else {
        for (var key in obj) {
          block(key, obj[key]);
        }
      }

      return obj;
    },

    "to_a": function(obj, args) {
      var result = [];

      for (var i = 0, length = obj.length; i < length; i++) {
        result.push(obj[i]);
      }

      return result;
    },

    "to_h": function(obj) {
      var keys = [], values = {}, value;

      for (var key in obj) {
        keys.push(key);
        values[key] = obj[key];
      }

      return Opal.hash2(keys, values);
    }
  };

  // Const missing dispatcher
  Opal.cm = function(name) {
    return this.base.$const_missing(name);
  };

  // Arity count error dispatcher
  Opal.ac = function(actual, expected, object, meth) {
    var inspect = ((typeof(object) !== 'function') ? object.constructor._name + '#' : object._name + '.') + meth;
    var msg = '[' + inspect + '] wrong number of arguments(' + actual + ' for ' + expected + ')';
    throw Opal.ArgumentError.$new(msg);
  };

  /*
    Call a ruby method on a ruby object with some arguments:

      var my_array = [1, 2, 3, 4]
      Opal.send(my_array, 'length')     # => 4
      Opal.send(my_array, 'reverse!')   # => [4, 3, 2, 1]

    A missing method will be forwarded to the object via
    method_missing.

    The result of either call with be returned.

    @param [Object] recv the ruby object
    @param [String] mid ruby method to call
  */
  Opal.send = function(recv, mid) {
    var args = __slice.call(arguments, 2),
        func = recv['$' + mid];

    if (func) {
      return func.apply(recv, args);
    }

    return recv.$method_missing.apply(recv, [mid].concat(args));
  };

  Opal.block_send = function(recv, mid, block) {
    var args = __slice.call(arguments, 3),
        func = recv['$' + mid];

    if (func) {
      func._p = block;
      return func.apply(recv, args);
    }

    return recv.$method_missing.apply(recv, [mid].concat(args));
  };

  // Implementation of Class#===
  function module_eqq(object) {
    if (object == null) {
      return false;
    }

    var search = object.constructor;

    while (search) {
      if (search === this) {
        return true;
      }

      search = search._super;
    }

    return false;
  }

  // Implementation of Class#to_s
  function module_to_s() {
    return this._name;
  }

  /**
   * Donate methods for a class/module
   */
  Opal.donate = function(klass, defined, indirect) {
    var methods = klass._methods, included_in = klass._included_in;

    // if (!indirect) {
      klass._methods = methods.concat(defined);
    // }

    if (included_in) {
      for (var i = 0, length = included_in.length; i < length; i++) {
        var includee = included_in[i];
        var dest = includee.prototype;

        for (var j = 0, jj = defined.length; j < jj; j++) {
          var method = defined[j];
          dest[method] = klass.prototype[method];
        }

        if (includee._included_in) {
          Opal.donate(includee, defined, true);
        }
      }
    }
  };

  /*
    Define a singleton method on the given klass

        Opal.defs(Array, '$foo', function() {})

    @param [Function] klass
    @param [String] mid the method_id
    @param [Function] body function body
  */
  Opal.defs = function(klass, mid, body) {
    klass._smethods.push(mid);
    klass[mid] = body;

    var inherited = klass._inherited;
    if (inherited && inherited.length) {
      for (var i = 0, length = inherited.length, subclass; i < length; i++) {
        subclass = inherited[i];
        if (!subclass[mid]) {
          Opal.defs(subclass, mid, body);
        }
      }
    }
  };

  // Defines methods onto Object (which are then donated to bridged classes)
  Object._defn = function (mid, body) {
    this.prototype[mid] = body;
    Opal.donate(this, [mid]);
  };

  // Initialization
  // --------------

  boot_defclass('BasicObject', BasicObject);
  boot_defclass('Object', Object, BasicObject);
  boot_defclass('Class', Class, Object);

  Class.prototype = Function.prototype;

  BasicObject.constructor = Object.constructor = Class.constructor = Class;

  var bridged_classes = Object._included_in = [];

  Opal.base = Object;
  BasicObject._scope = Object._scope = Opal;
  Opal.Module = Opal.Class;
  Opal.Kernel = Object;

  create_scope(Opal, Class);

  Object.prototype.toString = function() {
    return this.$to_s();
  };

  Opal.top = new Object;

  Opal.klass(Object, Object, 'NilClass', NilClass);

  var nil = Opal.nil = new NilClass;
  nil.call = nil.apply = function() { throw Opal.LocalJumpError.$new('no block given'); };

  Opal.breaker  = new Error('unexpected break');

  Opal.bridge('Array', Array);
  Opal.bridge('Boolean', Boolean);
  Opal.bridge('Numeric', Number);
  Opal.bridge('String', String);
  Opal.bridge('Proc', Function);
  Opal.bridge('Exception', Error);
  Opal.bridge('Regexp', RegExp);
  Opal.bridge('Time', Date);
}).call(this);
<|endoftext|>


<commit_before>/*
Copyright 2016 First People's Cultural Council

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {Component, PropTypes} from 'react';
import Immutable, {List, Map} from 'immutable';
import classNames from 'classnames';
import provide from 'react-redux-provide';
import ConfGlobal from 'conf/local.json';
import selectn from 'selectn';

import PromiseWrapper from 'views/components/Document/PromiseWrapper';

import TextHeader from 'views/components/Document/Typography/text-header';

import ProviderHelpers from 'common/ProviderHelpers';
import PageDialectLearnBase from 'views/pages/explore/dialect/learn/base';
import AlphabetListView from 'views/pages/explore/dialect/learn/alphabet/list-view';

import Paper from 'material-ui/lib/paper';
import FlatButton from 'material-ui/lib/flat-button';
import RaisedButton from 'material-ui/lib/raised-button';
import FontIcon from 'material-ui/lib/font-icon';
import GridTile from 'material-ui/lib/grid-list/grid-tile';

import Header from 'views/pages/explore/dialect/header';
import ToolbarNavigation from 'views/pages/explore/dialect/learn/base/toolbar-navigation';
import LearningSidebar from 'views/pages/explore/dialect/learn/base/learning-sidebar';
import IntlService from 'views/services/intl';

const intl = IntlService.instance;

class AlphabetGridTile extends Component {

    constructor(props) {
        super(props)
    }

    render() {
        return <GridTile key={selectn('uid', this.props.tile)} style={{
            border: '3px solid #e0e0e0',
            borderRadius: '5px',
            textAlign: 'center',
            paddingTop: '15px'
        }}>
            {selectn('properties.fvcharacter:upper_case_character', this.props.tile)} {selectn('properties.dc:title', this.props.tile)}

            {selectn('contextParameters.character.related_words[0].dc:title', this.props.tile)}

            {selectn('contextParameters.character.related_words[0].fv:definitions[0].translation', this.props.tile) || selectn('contextParameters.character.related_words[0].fv:literal_translation[0].translation', this.props.tile)}
        </GridTile>;
    }
}

/**
 * Learn alphabet
 */
@provide
export default class PageDialectLearnAlphabet extends PageDialectLearnBase {

    static defaultProps = {
        print: false
    }

    static propTypes = {
        properties: PropTypes.object.isRequired,
        navigateTo: PropTypes.func.isRequired,
        windowPath: PropTypes.string.isRequired,
        pushWindowPath: PropTypes.func.isRequired,
        splitWindowPath: PropTypes.array.isRequired,
        fetchDocument: PropTypes.func.isRequired,
        computeDocument: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        fetchDialect2: PropTypes.func.isRequired,
        computeDialect2: PropTypes.object.isRequired,
        fetchCharacters: PropTypes.func.isRequired,
        computeCharacters: PropTypes.object.isRequired,
        updateDialect2: PropTypes.func.isRequired,
        fetchPortal: PropTypes.func.isRequired,
        computePortal: PropTypes.object.isRequired,
        updatePortal: PropTypes.func.isRequired,
        routeParams: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        print: PropTypes.bool
    };

    constructor(props, context) {
        super(props, context);

        this.state = {
            current_char: null
        };

        // Bind methods to 'this'
        ['_onNavigateRequest'].forEach((method => this[method] = this[method].bind(this)));
    }

    fetchData(newProps) {
        newProps.fetchDialect2(newProps.routeParams.dialect_path);
        newProps.fetchPortal(newProps.routeParams.dialect_path + '/Portal');

        newProps.fetchDocument(newProps.routeParams.dialect_path + '/Dictionary');

        newProps.fetchCharacters(newProps.routeParams.dialect_path + '/Alphabet', '&sortOrder=asc&sortBy=fvcharacter:alphabet_order');
    }

    _onCharAudioTouchTap(char) {
        document.getElementById("charAudio" + char.uid).play();
        this.setState({
            current_char: char
        });
    }

    _onNavigateRequest(path) {
        const destination = this.props.navigateTo(path);
        const newPathArray = this.props.splitWindowPath.slice();

        newPathArray.push(destination.path);

        this.props.pushWindowPath('/' + newPathArray.join('/'));
    }

    render() {

        const computeEntities = Immutable.fromJS([{
            'id': this.props.routeParams.dialect_path,
            'entity': this.props.computeDialect2
        }, {
            'id': this.props.routeParams.dialect_path + '/Portal',
            'entity': this.props.computePortal
        }])

        const {updatePortal, updateDialect2, computeLogin} = this.props;

        const computeDocument = ProviderHelpers.getEntry(this.props.computeDocument, this.props.routeParams.dialect_path + '/Dictionary');
        const computeDialect2 = ProviderHelpers.getEntry(this.props.computeDialect2, this.props.routeParams.dialect_path);
        const computePortal = ProviderHelpers.getEntry(this.props.computePortal, this.props.routeParams.dialect_path + '/Portal');

        const computeCharacters = ProviderHelpers.getEntry(this.props.computeCharacters, this.props.routeParams.dialect_path + '/Alphabet');

        const isSection = this.props.routeParams.area === 'sections';

        const alphabetListView = <AlphabetListView pagination={false} routeParams={this.props.routeParams}
                                                   dialect={selectn('response', computeDialect2)}/>;

        if (this.props.print) {
            return <PromiseWrapper renderOnError={true} computeEntities={computeEntities}>

{intl.trans('views.pages.explore.dialect.learn.alphabet.x_alphabet',
                            selectn('response.title', computeDialect2) + ' Alphabet', 'words',
                            [selectn('response.title', computeDialect2)])}

                        {React.cloneElement(alphabetListView, {
                            gridListView: true,
                            gridViewProps: {style: {overflowY: 'auto', maxHeight: '100%'}},
                            gridListTile: AlphabetGridTile,
                            dialect: selectn('response', computeDialect2)
                        })}

            </PromiseWrapper>;
        }

        return <PromiseWrapper computeEntities={computeEntities}>

                <ToolbarNavigation showStats={this._showStats} routeParams={this.props.routeParams}/>

                    <TextHeader title={intl.trans('views.pages.explore.dialect.learn.alphabet.x_alphabet', selectn('response.title', computeDialect2) + ' Alphabet', null, [selectn('response.title', computeDialect2)])}
                        tag="h1"
                        properties={this.props.properties}
                        appendToTitle={print}/>

                    {(() => {
                        if (this.state.current_char !== null) {
                            return <RaisedButton
                                primary={true}
                                label={"View Words and Phrases that start with " + this.state.current_char.title}
                                onTouchTap={this._onNavigateRequest.bind(this, this.state.current_char.path.split('/')[this.state.current_char.path.split('/').length - 1])}
                                style={{minWidth: 'inherit', textTransform: 'initial', margin: '10px 0'}}/>;
                        }
                    })()}

                    {(() => {

                        const characters = selectn('response.entries', computeCharacters);

                        if (characters && characters.length > 0) {
                            return

                                {selectn('response.entries', computeCharacters).map((char, i) =>
                                        <Paper key={char.uid} style={{
                                            textAlign: 'center',
                                            margin: '5px',
                                            padding: '5px 10px',
                                            width: '100px',
                                            display: 'inline-block'
                                        }}>
                                            <FlatButton
                                             icon={<FontIcon
                                                className="material-icons">play_arrow</FontIcon>}
                                                onTouchTap={this._onCharAudioTouchTap.bind(this, char)}
                                                //onTouchTap={this._onNavigateRequest.bind(this, char.path.split('/')[char.path.split('/').length - 1])}
                                                label={char.title} style={{minWidth: 'inherit', textTransform: 'initial'}}/>
                                            {(char.contextParameters.character.related_audio[0]) ?

                                  <audio id={'charAudio' + char.uid}
                                         src={ConfGlobal.baseURL + char.contextParameters.character.related_audio[0].path}/>

                                                : ''}
                                        </Paper>
                                )}

;
                        }

                    })()}

                    <LearningSidebar
                        isSection={isSection}
                        properties={this.props.properties}
                        dialect={{compute: computeDialect2, update: updateDialect2}}/>

        </PromiseWrapper>;
    }
}<commit_msg>TRELLO-328 - missing audio files should not show icon<commit_after>/*
Copyright 2016 First People's Cultural Council

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {Component, PropTypes} from 'react';
import Immutable, {List, Map} from 'immutable';
import classNames from 'classnames';
import provide from 'react-redux-provide';
import ConfGlobal from 'conf/local.json';
import selectn from 'selectn';

import PromiseWrapper from 'views/components/Document/PromiseWrapper';

import TextHeader from 'views/components/Document/Typography/text-header';

import ProviderHelpers from 'common/ProviderHelpers';
import PageDialectLearnBase from 'views/pages/explore/dialect/learn/base';
import AlphabetListView from 'views/pages/explore/dialect/learn/alphabet/list-view';

import Paper from 'material-ui/lib/paper';
import FlatButton from 'material-ui/lib/flat-button';
import RaisedButton from 'material-ui/lib/raised-button';
import FontIcon from 'material-ui/lib/font-icon';
import GridTile from 'material-ui/lib/grid-list/grid-tile';

import Header from 'views/pages/explore/dialect/header';
import ToolbarNavigation from 'views/pages/explore/dialect/learn/base/toolbar-navigation';
import LearningSidebar from 'views/pages/explore/dialect/learn/base/learning-sidebar';
import IntlService from 'views/services/intl';

const intl = IntlService.instance;

class AlphabetGridTile extends Component {

    constructor(props) {
        super(props)
    }

    render() {
        return <GridTile key={selectn('uid', this.props.tile)} style={{
            border: '3px solid #e0e0e0',
            borderRadius: '5px',
            textAlign: 'center',
            paddingTop: '15px'
        }}>
            {selectn('properties.fvcharacter:upper_case_character', this.props.tile)} {selectn('properties.dc:title', this.props.tile)}

            {selectn('contextParameters.character.related_words[0].dc:title', this.props.tile)}

            {selectn('contextParameters.character.related_words[0].fv:definitions[0].translation', this.props.tile) || selectn('contextParameters.character.related_words[0].fv:literal_translation[0].translation', this.props.tile)}
        </GridTile>;
    }
}

/**
 * Learn alphabet
 */
@provide
export default class PageDialectLearnAlphabet extends PageDialectLearnBase {

    static defaultProps = {
        print: false
    }

    static propTypes = {
        properties: PropTypes.object.isRequired,
        navigateTo: PropTypes.func.isRequired,
        windowPath: PropTypes.string.isRequired,
        pushWindowPath: PropTypes.func.isRequired,
        splitWindowPath: PropTypes.array.isRequired,
        fetchDocument: PropTypes.func.isRequired,
        computeDocument: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        fetchDialect2: PropTypes.func.isRequired,
        computeDialect2: PropTypes.object.isRequired,
        fetchCharacters: PropTypes.func.isRequired,
        computeCharacters: PropTypes.object.isRequired,
        updateDialect2: PropTypes.func.isRequired,
        fetchPortal: PropTypes.func.isRequired,
        computePortal: PropTypes.object.isRequired,
        updatePortal: PropTypes.func.isRequired,
        routeParams: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        print: PropTypes.bool
    };

    constructor(props, context) {
        super(props, context);

        this.state = {
            current_char: null
        };

        // Bind methods to 'this'
        ['_onNavigateRequest', '_onCharAudioTouchTap'].forEach((method => this[method] = this[method].bind(this)));
    }

    fetchData(newProps) {
        newProps.fetchDialect2(newProps.routeParams.dialect_path);
        newProps.fetchPortal(newProps.routeParams.dialect_path + '/Portal');

        newProps.fetchDocument(newProps.routeParams.dialect_path + '/Dictionary');

        newProps.fetchCharacters(newProps.routeParams.dialect_path + '/Alphabet', '&sortOrder=asc&sortBy=fvcharacter:alphabet_order');
    }

    _onCharAudioTouchTap(char) {
        let charElement = document.getElementById("charAudio" + char.uid);

        if (charElement) {
            document.getElementById("charAudio" + char.uid).play();
        }

        this.setState({
            current_char: char
        });
    }

    _onNavigateRequest(path) {
        const destination = this.props.navigateTo(path);
        const newPathArray = this.props.splitWindowPath.slice();

        newPathArray.push(destination.path);

        this.props.pushWindowPath('/' + newPathArray.join('/'));
    }

    render() {

        const computeEntities = Immutable.fromJS([{
            'id': this.props.routeParams.dialect_path,
            'entity': this.props.computeDialect2
        }, {
            'id': this.props.routeParams.dialect_path + '/Portal',
            'entity': this.props.computePortal
        }])

        const {updatePortal, updateDialect2, computeLogin} = this.props;

        const computeDocument = ProviderHelpers.getEntry(this.props.computeDocument, this.props.routeParams.dialect_path + '/Dictionary');
        const computeDialect2 = ProviderHelpers.getEntry(this.props.computeDialect2, this.props.routeParams.dialect_path);
        const computePortal = ProviderHelpers.getEntry(this.props.computePortal, this.props.routeParams.dialect_path + '/Portal');

        const computeCharacters = ProviderHelpers.getEntry(this.props.computeCharacters, this.props.routeParams.dialect_path + '/Alphabet');

        const isSection = this.props.routeParams.area === 'sections';

        const alphabetListView = <AlphabetListView pagination={false} routeParams={this.props.routeParams}
                                                   dialect={selectn('response', computeDialect2)}/>;

        if (this.props.print) {
            return <PromiseWrapper renderOnError={true} computeEntities={computeEntities}>

{intl.trans('views.pages.explore.dialect.learn.alphabet.x_alphabet',
                            selectn('response.title', computeDialect2) + ' Alphabet', 'words',
                            [selectn('response.title', computeDialect2)])}

                        {React.cloneElement(alphabetListView, {
                            gridListView: true,
                            gridViewProps: {style: {overflowY: 'auto', maxHeight: '100%'}},
                            gridListTile: AlphabetGridTile,
                            dialect: selectn('response', computeDialect2)
                        })}

            </PromiseWrapper>;
        }

        return <PromiseWrapper computeEntities={computeEntities}>

                <ToolbarNavigation showStats={this._showStats} routeParams={this.props.routeParams}/>

                    <TextHeader title={intl.trans('views.pages.explore.dialect.learn.alphabet.x_alphabet', selectn('response.title', computeDialect2) + ' Alphabet', null, [selectn('response.title', computeDialect2)])}
                        tag="h1"
                        properties={this.props.properties}
                        appendToTitle={print}/>

                    {(() => {
                        if (this.state.current_char !== null) {
                            return <RaisedButton
                                primary={true}
                                label={"View Words and Phrases that start with " + this.state.current_char.title}
                                onTouchTap={this._onNavigateRequest.bind(this, this.state.current_char.path.split('/')[this.state.current_char.path.split('/').length - 1])}
                                style={{minWidth: 'inherit', textTransform: 'initial', margin: '10px 0'}}/>;
                        }
                    })()}

                    {(() => {

                        const characters = selectn('response.entries', computeCharacters);

                        if (characters && characters.length > 0) {

                            let _this = this;

                            return

                                {selectn('response.entries', computeCharacters).map(function (char, i) {
                                        let audioFile = selectn('contextParameters.character.related_audio[0].path', char);

                                        return <Paper key={char.uid} style={{
                                            textAlign: 'center',
                                            margin: '5px',
                                            padding: '5px 10px',
                                            width: '100px',
                                            display: 'inline-block'
                                        }}>
                                            <FlatButton
                                             icon={(audioFile) ? <FontIcon
                                                className="material-icons">play_arrow</FontIcon> : ''}
                                                onTouchTap={_this._onCharAudioTouchTap.bind(this, char)}
                                                //onTouchTap={this._onNavigateRequest.bind(this, char.path.split('/')[char.path.split('/').length - 1])}
                                                label={char.title} style={{minWidth: 'inherit', textTransform: 'initial'}}/>
                                            {(audioFile) ? <audio id={'charAudio' + char.uid} src={ConfGlobal.baseURL + audioFile}/> : ''}
                                        </Paper>;
                                })}

;
                        }

                    })()}

                    <LearningSidebar
                        isSection={isSection}
                        properties={this.props.properties}
                        dialect={{compute: computeDialect2, update: updateDialect2}}/>

        </PromiseWrapper>;
    }
}<|endoftext|>


<commit_before>/* eslint-disable react/no-array-index-key */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { actions, Form } from 'react-redux-form'
import formProps from 'shared/form/formPropsSelector'
import Textarea from 'shared/form/Textarea'
import Textfield from 'shared/form/Textfield'
import CheckboxDetailsField from 'shared/form/CheckboxDetailsField'
import AUheading from '@gov.au/headings/lib/js/react.js'
import ErrorAlert from 'marketplace/components/Alerts/ErrorAlert'
import styles from './BuyerEvaluationCriteriaStage.scss'

const noEmptyWeightingsEssential = v =>
  !v.includeWeightingsEssential || v.essentialRequirements.every(val => val.weighting)

export const weightingsAddUpTo100Essential = v =>
  !v.includeWeightingsEssential ||
  !noEmptyWeightingsEssential(v) ||
  v.essentialRequirements.reduce(
    (accumulator, currentValue) => accumulator + parseInt(currentValue.weighting, 10),
    0
  ) === 100

const noZeroWeightingsEssential = v =>
  !v.includeWeightingsEssential || v.essentialRequirements.every(val => parseInt(val.weighting, 10) > 0)

const noEmptyCriteriaEssential = v => v.essentialRequirements.every(val => val.criteria)

const noEmptyWeightingsNiceToHave = v =>
  !v.includeWeightingsNiceToHave || v.niceToHaveRequirements.every(val => (val.criteria ? val.weighting : true))

const noEmptyCriteriaNiceToHave = v => v.niceToHaveRequirements.every(val => (val.weighting ? val.criteria : true))

const weightingsAddUpTo100NiceToHave = v =>
  !v.includeWeightingsNiceToHave ||
  (noEmptyWeightingsNiceToHave(v) &&
    noEmptyCriteriaNiceToHave(v) &&
    v.niceToHaveRequirements.reduce(
      (accumulator, currentValue) => accumulator + parseInt(currentValue.weighting, 10),
      0
    ) === 100)

const noZeroWeightingsNiceToHave = v =>
  noEmptyCriteriaNiceToHave(v) ||
  !v.includeWeightingsNiceToHave ||
  v.niceToHaveRequirements.every(val => parseInt(val.weighting, 10) > 0)

export const done = v =>
  noEmptyWeightingsEssential(v) &&
  weightingsAddUpTo100Essential(v) &&
  noZeroWeightingsEssential(v) &&
  noEmptyCriteriaEssential(v) &&
  noEmptyWeightingsNiceToHave(v) &&
  weightingsAddUpTo100NiceToHave(v) &&
  noZeroWeightingsNiceToHave(v) &&
  noEmptyCriteriaNiceToHave(v)

class BuyerEvaluationCriteriaStage extends Component {
  constructor(props) {
    super(props)
    this.handleIncludeWeightingsEssentialChange = this.handleIncludeWeightingsEssentialChange.bind(this)
    this.handleIncludeWeightingsNiceToHaveChange = this.handleIncludeWeightingsNiceToHaveChange.bind(this)
    this.handleAddEssentialCriteriaClick = this.handleAddEssentialCriteriaClick.bind(this)
    this.handleAddNiceToHaveCriteriaClick = this.handleAddNiceToHaveCriteriaClick.bind(this)
    this.removeCriteria = this.removeCriteria.bind(this)
  }

  static getRemainingWeighting(criteria) {
    let remaining = 100
    criteria.map(evaluation => {
      if (evaluation.weighting && parseInt(evaluation.weighting, 10) > 0) {
        remaining -= parseInt(evaluation.weighting, 10)
      }
      return true
    })
    if (remaining > 100) {
      remaining = 100
    }
    return remaining
  }

  removeCriteria(index, criteria, property) {
    this.props.removeCriteriaByIndex(criteria, property, index)
  }

  handleAddEssentialCriteriaClick(e) {
    e.preventDefault()
    this.props.addEmptyEvalutationCriteria(this.props[this.props.model].essentialRequirements, 'essentialRequirements')
  }

  handleAddNiceToHaveCriteriaClick(e) {
    e.preventDefault()
    this.props.addEmptyEvalutationCriteria(
      this.props[this.props.model].niceToHaveRequirements,
      'niceToHaveRequirements'
    )
  }

  handleIncludeWeightingsEssentialChange(e) {
    const checked = e.target.checked
    if (!checked) {
      this.props.clearWeightingsFromEssentialCriteria(this.props[this.props.model].essentialRequirements)
    }
  }

  handleIncludeWeightingsNiceToHaveChange(e) {
    const checked = e.target.checked
    if (!checked) {
      this.props.clearWeightingsFromNiceToHaveCriteria(this.props[this.props.model].niceToHaveRequirements)
    }
  }

  render() {
    return (
      <Form
        model={this.props.model}
        validators={{
          '': {
            noEmptyWeightingsEssential,
            weightingsAddUpTo100Essential,
            noZeroWeightingsEssential,
            noEmptyCriteriaEssential,
            noEmptyWeightingsNiceToHave,
            weightingsAddUpTo100NiceToHave,
            noZeroWeightingsNiceToHave,
            noEmptyCriteriaNiceToHave
          }
        }}
        onSubmit={this.props.onSubmit}
        onSubmitFailed={this.props.onSubmitFailed}
        validateOn="submit"
      >
        <AUheading level="1" size="xl">
          Evaluation criteria
        </AUheading>
        <ErrorAlert
          model={this.props.model}
          messages={{
            noEmptyWeightingsEssential: 'You cannot have blank essential weightings.',
            weightingsAddUpTo100Essential: 'Essential weightings must add up to 100%.',
            noZeroWeightingsEssential: 'Essential weightings must be greater than 0.',
            noEmptyCriteriaEssential: 'You cannot have blank essential criteria.',
            noEmptyWeightingsNiceToHave: 'You cannot leave any weightings blank.',
            weightingsAddUpTo100NiceToHave: 'Desirable weightings must add up to 100%.',
            noZeroWeightingsNiceToHave: 'Desirable must be greater than 0.',
            noEmptyCriteriaNiceToHave: 'You cannot leave any weightings blank.'
          }}
        />
        <AUheading level="2" size="lg">
          Essential criteria
        </AUheading>

          <CheckboxDetailsField
            model={`${this.props.model}.includeWeightingsEssential`}
            id={`includeWeightingsEssential`}
            name={`includeWeightingsEssential`}
            onClick={this.handleIncludeWeightingsEssentialChange}
            label="Include weightings"
            detailsModel={this.props.model}
            validators={{}}
            messages={{}}
          />

        {this.props[this.props.model].essentialRequirements.map((essentialRequirements, i) => {
          if (
            essentialRequirements &&
            typeof essentialRequirements.criteria !== 'undefined' &&
            typeof essentialRequirements.weighting !== 'undefined'
          ) {
            return (
              <div className="row" key={`criteria_key_${i}`}>

                    <Textarea
                      model={`${this.props.model}.essentialRequirements[${i}].criteria`}
                      label="Criteria"
                      name={`essential_criteria_${i}`}
                      id={`essential_criteria_${i}`}
                      htmlFor={`essential_criteria_${i}`}
                      defaultValue={essentialRequirements.criteria}
                      controlProps={{ limit: 50 }}
                      messages={{
                        limitWords: 'Your criteria has exceeded the 50 word limit'
                      }}
                    />

                  {this.props[this.props.model].includeWeightingsEssential && (

                        <Textfield
                          model={`${this.props.model}.essentialRequirements[${i}].weighting`}
                          label="Weighting (%)"
                          name={`essential_weighting_${i}`}
                          id={`essential_weighting_${i}`}
                          htmlFor={`essential_weighting_${i}`}
                          defaultValue={essentialRequirements.weighting}
                          type="number"
                          min={1}
                          max={100}
                        />
                        {i === this.props[this.props.model].essentialRequirements.length - 1 && (

                            {BuyerEvaluationCriteriaStage.getRemainingWeighting(
                              this.props[this.props.model].essentialRequirements
                            )}
                            % remaining

                        )}

                  )}
                  {this.props[this.props.model].essentialRequirements.length > 1 && (
                    <div className={`col-xs-12 col-sm-2 ${styles.removeCriteria}`}>
                       {
                          e.preventDefault()
                          this.removeCriteria(
                            i,
                            this.props[this.props.model].essentialRequirements,
                            'essentialRequirements'
                          )
                        }}
                      >
                        Remove

                  )}

            )
          }
          return null
        })}

            Add another criteria

        <AUheading level="2" size="lg">
          Desirable criteria (optional)
        </AUheading>

          <CheckboxDetailsField
            model={`${this.props.model}.includeWeightingsNiceToHave`}
            id={`includeWeightingsNiceToHave`}
            name={`includeWeightingsNiceToHave`}
            onClick={this.handleIncludeWeightingsNiceToHaveChange}
            label="Include weightings"
            detailsModel={this.props.model}
            validators={{}}
            messages={{}}
          />

        {this.props[this.props.model].niceToHaveRequirements.map((niceToHaveRequirements, i) => {
          if (
            niceToHaveRequirements &&
            typeof niceToHaveRequirements.criteria !== 'undefined' &&
            typeof niceToHaveRequirements.weighting !== 'undefined'
          ) {
            return (
              <div className="row" key={`criteria_key_${i}`}>

                    <Textarea
                      model={`${this.props.model}.niceToHaveRequirements[${i}].criteria`}
                      label="Criteria"
                      name={`nice_to_have_criteria_${i}`}
                      id={`nice_to_have_criteria_${i}`}
                      htmlFor={`nice_to_have_criteria_${i}`}
                      defaultValue={niceToHaveRequirements.criteria}
                      controlProps={{ limit: 50 }}
                      messages={{
                        limitWords: 'Your criteria has exceeded the 50 word limit'
                      }}
                    />

                  {this.props[this.props.model].includeWeightingsNiceToHave && (

                        <Textfield
                          model={`${this.props.model}.niceToHaveRequirements[${i}].weighting`}
                          label="Weighting (%)"
                          name={`nice_to_have_weighting_${i}`}
                          id={`nice_to_have_weighting_${i}`}
                          htmlFor={`nice_to_have_weighting_${i}`}
                          defaultValue={niceToHaveRequirements.weighting}
                          type="number"
                          min={1}
                          max={100}
                        />
                        {i === this.props[this.props.model].niceToHaveRequirements.length - 1 && (

                            {BuyerEvaluationCriteriaStage.getRemainingWeighting(
                              this.props[this.props.model].niceToHaveRequirements
                            )}
                            % remaining

                        )}

                  )}
                  {this.props[this.props.model].niceToHaveRequirements.length > 1 && (
                    <div className={`col-xs-12 col-sm-2 ${styles.removeCriteria}`}>
                       {
                          e.preventDefault()
                          this.removeCriteria(
                            i,
                            this.props[this.props.model].niceToHaveRequirements,
                            'niceToHaveRequirements'
                          )
                        }}
                      >
                        Remove

                  )}

            )
          }
          return null
        })}

            Add another criteria

        {this.props.formButtons}
      </Form>
    )
  }
}

BuyerEvaluationCriteriaStage.defaultProps = {
  clearWeightingsFromEssentialCriteria: () => {},
  clearWeightingsFromNiceToHaveCriteria: () => {},
  addEmptyEvalutationCriteria: () => {},
  removeCriteriaByIndex: () => {},
  onSubmit: () => {},
  onSubmitFailed: () => {}
}

BuyerEvaluationCriteriaStage.propTypes = {
  model: PropTypes.string.isRequired,
  clearWeightingsFromEssentialCriteria: PropTypes.func,
  clearWeightingsFromNiceToHaveCriteria: PropTypes.func,
  addEmptyEvalutationCriteria: PropTypes.func,
  removeCriteriaByIndex: PropTypes.func,
  onSubmit: PropTypes.func,
  onSubmitFailed: PropTypes.func,
  formButtons: PropTypes.node.isRequired
}

const mapStateToProps = (state, props) => ({
  ...formProps(state, props.model)
})

const mapDispatchToProps = (dispatch, props) => ({
  clearWeightingsFromEssentialCriteria: essentialRequirements => {
    const newData = essentialRequirements.map(evaluation => {
      const newEvaluation = { ...evaluation }
      newEvaluation.weighting = ''
      return newEvaluation
    })
    dispatch(actions.change(`${props.model}.essentialRequirements`, newData))
  },
  clearWeightingsFromNiceToHaveCriteria: niceToHaveRequirements => {
    const newData = niceToHaveRequirements.map(evaluation => {
      const newEvaluation = { ...evaluation }
      newEvaluation.weighting = ''
      return newEvaluation
    })
    dispatch(actions.change(`${props.model}.niceToHaveRequirements`, newData))
  },
  addEmptyEvalutationCriteria: (criteria, property) => {
    const newData = criteria.slice(0)
    newData.push({
      criteria: '',
      weighting: ''
    })
    dispatch(actions.change(`${props.model}.${property}`, newData))
  },
  removeCriteriaByIndex: (criteria, property, index) => {
    const newData = criteria.slice(0)
    newData.splice(index, 1)
    dispatch(actions.change(`${props.model}.${property}`, newData))
  }
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BuyerEvaluationCriteriaStage)
<commit_msg>changing content and fixing validation (#918)<commit_after>/* eslint-disable react/no-array-index-key */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { actions, Form } from 'react-redux-form'
import formProps from 'shared/form/formPropsSelector'
import Textarea from 'shared/form/Textarea'
import Textfield from 'shared/form/Textfield'
import CheckboxDetailsField from 'shared/form/CheckboxDetailsField'
import AUheading from '@gov.au/headings/lib/js/react.js'
import ErrorAlert from 'marketplace/components/Alerts/ErrorAlert'
import styles from './BuyerEvaluationCriteriaStage.scss'

const noEmptyWeightingsEssential = v =>
  !v.includeWeightingsEssential || v.essentialRequirements.every(val => val.weighting)

export const weightingsAddUpTo100Essential = v =>
  !v.includeWeightingsEssential ||
  !noEmptyWeightingsEssential(v) ||
  v.essentialRequirements.reduce(
    (accumulator, currentValue) => accumulator + parseInt(currentValue.weighting, 10),
    0
  ) === 100

const noZeroWeightingsEssential = v =>
  !v.includeWeightingsEssential ||
  v.essentialRequirements.every(val => val.weighting === '' || parseInt(val.weighting, 10) > 0)

const noEmptyCriteriaEssential = v => v.essentialRequirements.every(val => val.criteria)

const noEmptyWeightingsNiceToHave = v =>
  !v.includeWeightingsNiceToHave || v.niceToHaveRequirements.every(val => (val.criteria ? val.weighting : true))

const noEmptyCriteriaNiceToHave = v => v.niceToHaveRequirements.every(val => (val.weighting ? val.criteria : true))

const weightingsAddUpTo100NiceToHave = v =>
  !v.includeWeightingsNiceToHave ||
  !noEmptyWeightingsNiceToHave(v) ||
  v.niceToHaveRequirements.reduce(
    (accumulator, currentValue) => accumulator + parseInt(currentValue.weighting, 10),
    0
  ) === 100

const noZeroWeightingsNiceToHave = v =>
  noEmptyCriteriaNiceToHave(v) ||
  !v.includeWeightingsNiceToHave ||
  v.niceToHaveRequirements.every(val => parseInt(val.weighting, 10) > 0)

export const done = v =>
  noEmptyWeightingsEssential(v) &&
  weightingsAddUpTo100Essential(v) &&
  noZeroWeightingsEssential(v) &&
  noEmptyCriteriaEssential(v) &&
  noEmptyWeightingsNiceToHave(v) &&
  weightingsAddUpTo100NiceToHave(v) &&
  noZeroWeightingsNiceToHave(v) &&
  noEmptyCriteriaNiceToHave(v)

class BuyerEvaluationCriteriaStage extends Component {
  constructor(props) {
    super(props)
    this.handleIncludeWeightingsEssentialChange = this.handleIncludeWeightingsEssentialChange.bind(this)
    this.handleIncludeWeightingsNiceToHaveChange = this.handleIncludeWeightingsNiceToHaveChange.bind(this)
    this.handleAddEssentialCriteriaClick = this.handleAddEssentialCriteriaClick.bind(this)
    this.handleAddNiceToHaveCriteriaClick = this.handleAddNiceToHaveCriteriaClick.bind(this)
    this.removeCriteria = this.removeCriteria.bind(this)
  }

  static getRemainingWeighting(criteria) {
    let remaining = 100
    criteria.map(evaluation => {
      if (evaluation.weighting && parseInt(evaluation.weighting, 10) > 0) {
        remaining -= parseInt(evaluation.weighting, 10)
      }
      return true
    })
    if (remaining > 100) {
      remaining = 100
    }
    return remaining
  }

  removeCriteria(index, criteria, property) {
    this.props.removeCriteriaByIndex(criteria, property, index)
  }

  handleAddEssentialCriteriaClick(e) {
    e.preventDefault()
    this.props.addEmptyEvalutationCriteria(this.props[this.props.model].essentialRequirements, 'essentialRequirements')
  }

  handleAddNiceToHaveCriteriaClick(e) {
    e.preventDefault()
    this.props.addEmptyEvalutationCriteria(
      this.props[this.props.model].niceToHaveRequirements,
      'niceToHaveRequirements'
    )
  }

  handleIncludeWeightingsEssentialChange(e) {
    const checked = e.target.checked
    if (!checked) {
      this.props.clearWeightingsFromEssentialCriteria(this.props[this.props.model].essentialRequirements)
    }
  }

  handleIncludeWeightingsNiceToHaveChange(e) {
    const checked = e.target.checked
    if (!checked) {
      this.props.clearWeightingsFromNiceToHaveCriteria(this.props[this.props.model].niceToHaveRequirements)
    }
  }

  render() {
    return (
      <Form
        model={this.props.model}
        validators={{
          '': {
            noEmptyWeightingsEssential,
            weightingsAddUpTo100Essential,
            noZeroWeightingsEssential,
            noEmptyCriteriaEssential,
            noEmptyWeightingsNiceToHave,
            weightingsAddUpTo100NiceToHave,
            noZeroWeightingsNiceToHave,
            noEmptyCriteriaNiceToHave
          }
        }}
        onSubmit={this.props.onSubmit}
        onSubmitFailed={this.props.onSubmitFailed}
        validateOn="submit"
      >
        <AUheading level="1" size="xl">
          Evaluation criteria
        </AUheading>
        <ErrorAlert
          model={this.props.model}
          messages={{
            noEmptyWeightingsEssential: 'You cannot have blank essential weightings.',
            weightingsAddUpTo100Essential: 'Essential weightings must add up to 100%.',
            noZeroWeightingsEssential: 'Essential weightings must be greater than 0.',
            noEmptyCriteriaEssential: 'You cannot have blank essential criteria.',
            noEmptyWeightingsNiceToHave: 'You cannot leave any weightings blank.',
            weightingsAddUpTo100NiceToHave: 'Desirable weightings must add up to 100%.',
            noZeroWeightingsNiceToHave: 'Desirable must be greater than 0.',
            noEmptyCriteriaNiceToHave: 'You cannot have blank desirable criteria.'
          }}
        />
        <AUheading level="2" size="lg">
          Essential criteria
        </AUheading>

          <CheckboxDetailsField
            model={`${this.props.model}.includeWeightingsEssential`}
            id={`includeWeightingsEssential`}
            name={`includeWeightingsEssential`}
            onClick={this.handleIncludeWeightingsEssentialChange}
            label="Include weightings"
            detailsModel={this.props.model}
            validators={{}}
            messages={{}}
          />

        {this.props[this.props.model].essentialRequirements.map((essentialRequirements, i) => {
          if (
            essentialRequirements &&
            typeof essentialRequirements.criteria !== 'undefined' &&
            typeof essentialRequirements.weighting !== 'undefined'
          ) {
            return (
              <div className="row" key={`criteria_key_${i}`}>

                    <Textarea
                      model={`${this.props.model}.essentialRequirements[${i}].criteria`}
                      label="Criteria"
                      name={`essential_criteria_${i}`}
                      id={`essential_criteria_${i}`}
                      htmlFor={`essential_criteria_${i}`}
                      defaultValue={essentialRequirements.criteria}
                      controlProps={{ limit: 50 }}
                      messages={{
                        limitWords: 'Your criteria has exceeded the 50 word limit'
                      }}
                    />

                  {this.props[this.props.model].includeWeightingsEssential && (

                        <Textfield
                          model={`${this.props.model}.essentialRequirements[${i}].weighting`}
                          label="Weighting (%)"
                          name={`essential_weighting_${i}`}
                          id={`essential_weighting_${i}`}
                          htmlFor={`essential_weighting_${i}`}
                          defaultValue={essentialRequirements.weighting}
                          type="number"
                          min={1}
                          max={100}
                        />
                        {i === this.props[this.props.model].essentialRequirements.length - 1 && (

                            {BuyerEvaluationCriteriaStage.getRemainingWeighting(
                              this.props[this.props.model].essentialRequirements
                            )}
                            % remaining

                        )}

                  )}
                  {this.props[this.props.model].essentialRequirements.length > 1 && (
                    <div className={`col-xs-12 col-sm-2 ${styles.removeCriteria}`}>
                       {
                          e.preventDefault()
                          this.removeCriteria(
                            i,
                            this.props[this.props.model].essentialRequirements,
                            'essentialRequirements'
                          )
                        }}
                      >
                        Remove

                  )}

            )
          }
          return null
        })}

            Add another criteria

        <AUheading level="2" size="lg">
          Desirable criteria (optional)
        </AUheading>

          <CheckboxDetailsField
            model={`${this.props.model}.includeWeightingsNiceToHave`}
            id={`includeWeightingsNiceToHave`}
            name={`includeWeightingsNiceToHave`}
            onClick={this.handleIncludeWeightingsNiceToHaveChange}
            label="Include weightings"
            detailsModel={this.props.model}
            validators={{}}
            messages={{}}
          />

        {this.props[this.props.model].niceToHaveRequirements.map((niceToHaveRequirements, i) => {
          if (
            niceToHaveRequirements &&
            typeof niceToHaveRequirements.criteria !== 'undefined' &&
            typeof niceToHaveRequirements.weighting !== 'undefined'
          ) {
            return (
              <div className="row" key={`criteria_key_${i}`}>

                    <Textarea
                      model={`${this.props.model}.niceToHaveRequirements[${i}].criteria`}
                      label="Criteria"
                      name={`nice_to_have_criteria_${i}`}
                      id={`nice_to_have_criteria_${i}`}
                      htmlFor={`nice_to_have_criteria_${i}`}
                      defaultValue={niceToHaveRequirements.criteria}
                      controlProps={{ limit: 50 }}
                      messages={{
                        limitWords: 'Your criteria has exceeded the 50 word limit'
                      }}
                    />

                  {this.props[this.props.model].includeWeightingsNiceToHave && (

                        <Textfield
                          model={`${this.props.model}.niceToHaveRequirements[${i}].weighting`}
                          label="Weighting (%)"
                          name={`nice_to_have_weighting_${i}`}
                          id={`nice_to_have_weighting_${i}`}
                          htmlFor={`nice_to_have_weighting_${i}`}
                          defaultValue={niceToHaveRequirements.weighting}
                          type="number"
                          min={1}
                          max={100}
                        />
                        {i === this.props[this.props.model].niceToHaveRequirements.length - 1 && (

                            {BuyerEvaluationCriteriaStage.getRemainingWeighting(
                              this.props[this.props.model].niceToHaveRequirements
                            )}
                            % remaining

                        )}

                  )}
                  {this.props[this.props.model].niceToHaveRequirements.length > 1 && (
                    <div className={`col-xs-12 col-sm-2 ${styles.removeCriteria}`}>
                       {
                          e.preventDefault()
                          this.removeCriteria(
                            i,
                            this.props[this.props.model].niceToHaveRequirements,
                            'niceToHaveRequirements'
                          )
                        }}
                      >
                        Remove

                  )}

            )
          }
          return null
        })}

            Add another criteria

        {this.props.formButtons}
      </Form>
    )
  }
}

BuyerEvaluationCriteriaStage.defaultProps = {
  clearWeightingsFromEssentialCriteria: () => {},
  clearWeightingsFromNiceToHaveCriteria: () => {},
  addEmptyEvalutationCriteria: () => {},
  removeCriteriaByIndex: () => {},
  onSubmit: () => {},
  onSubmitFailed: () => {}
}

BuyerEvaluationCriteriaStage.propTypes = {
  model: PropTypes.string.isRequired,
  clearWeightingsFromEssentialCriteria: PropTypes.func,
  clearWeightingsFromNiceToHaveCriteria: PropTypes.func,
  addEmptyEvalutationCriteria: PropTypes.func,
  removeCriteriaByIndex: PropTypes.func,
  onSubmit: PropTypes.func,
  onSubmitFailed: PropTypes.func,
  formButtons: PropTypes.node.isRequired
}

const mapStateToProps = (state, props) => ({
  ...formProps(state, props.model)
})

const mapDispatchToProps = (dispatch, props) => ({
  clearWeightingsFromEssentialCriteria: essentialRequirements => {
    const newData = essentialRequirements.map(evaluation => {
      const newEvaluation = { ...evaluation }
      newEvaluation.weighting = ''
      return newEvaluation
    })
    dispatch(actions.change(`${props.model}.essentialRequirements`, newData))
  },
  clearWeightingsFromNiceToHaveCriteria: niceToHaveRequirements => {
    const newData = niceToHaveRequirements.map(evaluation => {
      const newEvaluation = { ...evaluation }
      newEvaluation.weighting = ''
      return newEvaluation
    })
    dispatch(actions.change(`${props.model}.niceToHaveRequirements`, newData))
  },
  addEmptyEvalutationCriteria: (criteria, property) => {
    const newData = criteria.slice(0)
    newData.push({
      criteria: '',
      weighting: ''
    })
    dispatch(actions.change(`${props.model}.${property}`, newData))
  },
  removeCriteriaByIndex: (criteria, property, index) => {
    const newData = criteria.slice(0)
    newData.splice(index, 1)
    dispatch(actions.change(`${props.model}.${property}`, newData))
  }
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BuyerEvaluationCriteriaStage)
<|endoftext|>


<commit_before>/*
 * Copyright 2012-2015 MarkLogic Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(['_marklogic/module', 'moment'], function (module, moment) {

  /**
   * @ngdoc provider
   * @name mlHttpInterceptorProvider
   *
   * @description
   * Configures CSRF handling in the {@link mlHttpInterceptor}.
   *
   * @property {boolean=} [enableCsrf=`false`] If true, CSRF handling is enabled
   * and the browser app will blcok until it can get a CSRF token.
   *
   * @property {string=} [endpoint="/v1/session"] Configured the endpoint
   * for requesting the CSRF token. Defaults to `'/v1/sesion'`.

   * @property {string=} [headerName="X-CSRF-TOKEN"] The header name
   * for the CSRF token.  Defaults to X-CSRF-TOKEN.
   */
  module.provider('mlHttpInterceptor', [

    function () {
      var self = this;

      this.enableCsrf = true;
      this.csrfUrl = '/v1/session';
      this.headerName = 'X-CSRF-TOKEN';

      /**
       * @ngdoc service
       * @name mlHttpInterceptor
       * @requires mlUtil
       *
       * @description
       * Handles CSRF tokens for all AJAX requests. May be configured or
       * disabled  in the
       * {@link mlHttpInterceptorProvider}.
       *
       * The intercept assumes that the CSRF token is named `X-CSRF-TOKEN`.
       * and that it may be retrived from a GET request to
       * `<protocol:server:port>//v1/session`.
       *
       * As such, before the interceptor will allow through
       * any methods that require CSRF (POST, PUT, PATCH, DELETE), it will
       * attempt to do a round trip to the /v1/session endpoint to pick
       * up the token from the server.
       *
       * The token is saved in the `$http` service default headers and will
       * be supplied for all requests subsequently.
       */
      this.$get = [
        // we inject injector in order to avoid circular dependency on $http
        '$injector', '$q', 'mlUtil', 'mlStore', '$rootScope',
        function ($injector, $q, mlUtil, mlStore, $rootScope) {

          var $http;
          var outstanding;

          var csrfMethods = {
            'POST': true,
            'PUT': true,
            'PATCH': true,
            'DELETE': true
          };

          // whether or not we need to get csrf before doing what the app
          // actually wants
          var mustGetCsrf = function (config) {

            return csrfMethods[config.method]
                && !$http.defaults.headers.common[self.headerName];
          };

          // return a promise to have set the csrf header default. To do this,
          // round-trip with the server on get /v1/session
          var setCsrf = function () {
            if (outstanding) {
              return outstanding;
            }
            else {
              var requestConfig = {
                method: 'GET',
                url: self.csrfUrl,
                doNotOverride: true
              };
              var deferred = $q.defer();
              $http(requestConfig).then(
                function (response) {
                  // if the server doesn't give us a CSRF token, we should
                  // we complain? TODO
                  var token = response.headers(self.headerName);
                  if (token){
                    $http.defaults.headers.common[self.headerName] = token;
                  }
                  else {
                    $http.defaults.headers.common[self.headerName] = 'dummy';
                  }
                  deferred.resolve(token);
                },
                function (reason) {
                  deferred.reject(
                    new Error('unable to get CSRF token', reason)
                  );
                }
              );

              outstanding = deferred.promise;
              return outstanding;
            }
          };

          var setCsrfThenResolveConfig = function (config) {
            var deferred = $q.defer();

            setCsrf().then(
              function (token) {
                config.headers[self.headerName] = token;
                deferred.resolve(config);
              },
              deferred.reject
            );

            return deferred.promise;
          };

          return {

            request: function (config) {
              // ensure we have $http
              $http = $http || $injector.get('$http');

              if (!self.enableCsrf) {
                return config;
              }
              else {
                if (mustGetCsrf(config)) {
                  return setCsrfThenResolveConfig(config);
                }
                else {
                  return config;
                }
              }
            },

            responseError: function (rejection) {
              var timeoutMsg =
                  '$http response.status === 0. This may indicate a timeout. ' +
                      'Is the middle tier working?';
              if (rejection.status === 0) {
                return $q.reject(timeoutMsg);
              }
              if (rejection.data && rejection.data.status === 500 &&
                  /RESTAPI-NODOCUMENT/.test(rejection.data.message)
              ) {
                rejection.status = 404;
              }

              if (rejection.status === 404) {
                // not found
                if (mlStore.session && mlStore.session.id) {
                  // someone is logged in
                  $rootScope.errorCondition = 'notFoundWithSession';
                }
                else {
                  $rootScope.errorCondition = 'notFoundNoSession';
                  // noone is logged in
                }
              }

              if (
                rejection.status === 401 ||
                rejection.status === 403
              ) {
                // permissions
                // what is state of session?
                if (mlStore.session && mlStore.session.id) {
                  // someone is logged in
                  $rootScope.errorCondition = 'preventedWithSession';
                }
                else {
                  $rootScope.errorCondition = 'preventedNoSession';
                  // noone is logged in
                }
              }
              return $q.reject(rejection);
            }
          };

        }
      ];
    }
  ]);

  var regexIso8601;
  /* jshint ignore:start */
  regexIso8601 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
  /* jshint ignore:end */

  // courtesy
  var convertDateStringsToDates = function (input) {
    var key;
    // Ignore things that aren't objects.
    if (typeof input === 'object') {
      /* jshint -W089 */
      for (key in input) {
        var value = input[key];
        var match;
        // Check for string properties which look like dates.
        if (typeof value === 'string' && (match = value.match(regexIso8601))) {
          input[key] = moment(value, moment.ISO_8601);
        }
        else {
          // Recurse into object
          convertDateStringsToDates(value);
        }
      }
    }
  };

  // add mlHttpInterceptor to interceptors list of $httpProvider
  module.config([
    '$httpProvider',
    function ($httpProvider) {
      $httpProvider.interceptors.push('mlHttpInterceptor');
      $httpProvider.defaults.transformResponse.push(function (responseData) {
        convertDateStringsToDates(responseData);
        return responseData;
      });
    }
  ]);

});
<commit_msg>js: catch up java static build<commit_after>/*
 * Copyright 2012-2015 MarkLogic Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(['_marklogic/module'], function (module) {

  /**
   * @ngdoc provider
   * @name mlHttpInterceptorProvider
   *
   * @description
   * Configures CSRF handling in the {@link mlHttpInterceptor}.
   *
   * @property {boolean=} [enableCsrf=`false`] If true, CSRF handling is enabled
   * and the browser app will blcok until it can get a CSRF token.
   *
   * @property {string=} [endpoint="/v1/session"] Configured the endpoint
   * for requesting the CSRF token. Defaults to `'/v1/sesion'`.

   * @property {string=} [headerName="X-CSRF-TOKEN"] The header name
   * for the CSRF token.  Defaults to X-CSRF-TOKEN.
   */
  module.provider('mlHttpInterceptor', [

    function () {
      var self = this;

      this.enableCsrf = true;
      this.csrfUrl = '/v1/session';
      this.headerName = 'X-CSRF-TOKEN';

      /**
       * @ngdoc service
       * @name mlHttpInterceptor
       * @requires mlUtil
       *
       * @description
       * Handles CSRF tokens for all AJAX requests. May be configured or
       * disabled  in the
       * {@link mlHttpInterceptorProvider}.
       *
       * The intercept assumes that the CSRF token is named `X-CSRF-TOKEN`.
       * and that it may be retrived from a GET request to
       * `<protocol:server:port>//v1/session`.
       *
       * As such, before the interceptor will allow through
       * any methods that require CSRF (POST, PUT, PATCH, DELETE), it will
       * attempt to do a round trip to the /v1/session endpoint to pick
       * up the token from the server.
       *
       * The token is saved in the `$http` service default headers and will
       * be supplied for all requests subsequently.
       */
      this.$get = [
        // we inject injector in order to avoid circular dependency on $http
        '$injector', '$q', 'mlUtil', 'mlStore', '$rootScope',
        function ($injector, $q, mlUtil, mlStore, $rootScope) {

          var $http;
          var outstanding;

          var csrfMethods = {
            'POST': true,
            'PUT': true,
            'PATCH': true,
            'DELETE': true
          };

          // whether or not we need to get csrf before doing what the app
          // actually wants
          var mustGetCsrf = function (config) {

            return csrfMethods[config.method]
                && !$http.defaults.headers.common[self.headerName];
          };

          // return a promise to have set the csrf header default. To do this,
          // round-trip with the server on get /v1/session
          var setCsrf = function () {
            if (outstanding) {
              return outstanding;
            }
            else {
              var requestConfig = {
                method: 'GET',
                url: self.csrfUrl,
                doNotOverride: true
              };
              var deferred = $q.defer();
              $http(requestConfig).then(
                function (response) {
                  // if the server doesn't give us a CSRF token, we should
                  // we complain? TODO
                  var token = response.headers(self.headerName);
                  if (token){
                    $http.defaults.headers.common[self.headerName] = token;
                  }
                  else {
                    $http.defaults.headers.common[self.headerName] = 'dummy';
                  }
                  deferred.resolve(token);
                },
                function (reason) {
                  deferred.reject(
                    new Error('unable to get CSRF token', reason)
                  );
                }
              );

              outstanding = deferred.promise;
              return outstanding;
            }
          };

          var setCsrfThenResolveConfig = function (config) {
            var deferred = $q.defer();

            setCsrf().then(
              function (token) {
                config.headers[self.headerName] = token;
                deferred.resolve(config);
              },
              deferred.reject
            );

            return deferred.promise;
          };

          return {

            request: function (config) {
              // ensure we have $http
              $http = $http || $injector.get('$http');

              if (!self.enableCsrf) {
                return config;
              }
              else {
                if (mustGetCsrf(config)) {
                  return setCsrfThenResolveConfig(config);
                }
                else {
                  return config;
                }
              }
            },

            responseError: function (rejection) {
              var timeoutMsg =
                  '$http response.status === 0. This may indicate a timeout. ' +
                      'Is the middle tier working?';
              if (rejection.status === 0) {
                return $q.reject(timeoutMsg);
              }
              if (rejection.data && rejection.data.status === 500 &&
                  /RESTAPI-NODOCUMENT/.test(rejection.data.message)
              ) {
                rejection.status = 404;
              }

              if (rejection.status === 404) {
                // not found
                if (mlStore.session && mlStore.session.id) {
                  // someone is logged in
                  $rootScope.errorCondition = 'notFoundWithSession';
                }
                else {
                  $rootScope.errorCondition = 'notFoundNoSession';
                  // noone is logged in
                }
              }

              if (
                rejection.status === 401 ||
                rejection.status === 403
              ) {
                // permissions
                // what is state of session?
                if (mlStore.session && mlStore.session.id) {
                  // someone is logged in
                  $rootScope.errorCondition = 'preventedWithSession';
                }
                else {
                  $rootScope.errorCondition = 'preventedNoSession';
                  // noone is logged in
                }
              }
              return $q.reject(rejection);
            }
          };

        }
      ];
    }
  ]);

  var regexIso8601;
  /* jshint ignore:start */
  regexIso8601 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
  /* jshint ignore:end */

  // courtesy
  var convertDateStringsToDates = function (input) {
    var key;
    // Ignore things that aren't objects.
    if (typeof input === 'object') {
      /* jshint -W089 */
      for (key in input) {
        var value = input[key];
        var match;
        // Check for string properties which look like dates.
        if (typeof value === 'string' && (match = value.match(regexIso8601))) {
          input[key] = window.moment(value, window.moment.ISO_8601);
        }
        else {
          // Recurse into object
          convertDateStringsToDates(value);
        }
      }
    }
  };

  // add mlHttpInterceptor to interceptors list of $httpProvider
  module.config([
    '$httpProvider',
    function ($httpProvider) {
      $httpProvider.interceptors.push('mlHttpInterceptor');
      $httpProvider.defaults.transformResponse.push(function (responseData) {
        convertDateStringsToDates(responseData);
        return responseData;
      });
    }
  ]);

});
<|endoftext|>