<commit_before>
module.exports = request
request.GET = GET
request.PUT = PUT
request.reg = reg
request.upload = upload

var npm = require("../../../npm")
  , http
  , https
  , url = require("url")
  , log = require("../log")
  , ini = require("../ini")
  , fs = require("graceful-fs")
  , rm = require("../rm-rf")
  , asyncMap = require("slide").asyncMap
  , proxyify = require("../proxyify")
  , warnedAuth = false
  , newloctimeout = 0

function request (method, where, what, etag, nofollow, cb_) {
  log.verbose(where||"/", method)

  if (where.match(/^\/?favicon.ico/)) {
    return cb_(new Error("favicon.ico isn't a package, it's a picture."))
  }
  if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false
  if (typeof cb_ !== "function") cb_ = etag, etag = null
  if (typeof cb_ !== "function") cb_ = what, what = null
  var registry = reg()
  if (registry instanceof Error) return cb_(registry)

  // Since there are multiple places where an error could occur,
  // don't let the cb be called more than once.
  var errState = null
  function cb (er) {
    if (errState) return
    if (er) errState = er
    cb_.apply(null, arguments)
  }

  var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/
    , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/
    , authRequired = (what || npm.config.get("always-auth"))
                      && !where.match(adduserNew)
                   || where.match(adduserChange)
                   || method === "DELETE"
  if (!where.match(/^https?:\/\//)) {
    log.verbose(where, "raw, before any munging")

    var q = where.split("?")
    where = q.shift()
    q = q.join("?")

    if (where.charAt(0) !== "/") where = "/" + where
    where = "." + where.split("/").map(function (p) {
      p = p.trim()
      if (p.match(/^org.couchdb.user/)) {
        return p.replace(/\//g, encodeURIComponent("/"))
      }
      return encodeURIComponent(p)
    }).join("/")
    if (q) where += "?" + q
    log.verbose([registry, where], "url resolving")
    where = url.resolve(registry, where)
    log.verbose(where, "url resolved")
  } else {
    log.verbose(where, "no need to resolve")
  }

  var remote = url.parse(where)
    , secure = remote.protocol === "https:"
    , port = remote.port || (secure ? 443 : 80)
    , hostname = remote.hostname
    , auth = authRequired && npm.config.get("_auth")

  log.verbose(remote, "url parsed")
  if (port !== (secure ? 443 : 80)) hostname += ":" + port

  if (authRequired && !auth) {
    return cb(new Error(
      "Cannot insert data into the registry without authorization\n"
      + "See: npm-adduser(1)"))
  }

  if (auth && !secure && !warnedAuth) {
    warnedAuth = true
    log.warn("Sending authorization over insecure channel.")
  }

  var headers = { "accept" : "application/json" }
  if (auth) headers.authorization = "Basic " + auth
  if (what) {
    if (what instanceof File) {
      log.verbose(what.name, "uploading")
      headers["content-type"] = "application/octet-stream"
    } else {
      delete what._etag
      log.silly(what,"writing json")
      what = new Buffer(JSON.stringify(what))
      headers["content-type"] = "application/json"
    }
    headers["content-length"] = what.length
  } else {
    headers["content-length"] = 0
  }

  if (etag) {
    log.verbose(etag, "etag")
    headers[method === "GET" ? "if-none-match" : "if-match"] = etag
  }

  if (!remote.protocol) log.warn(remote, "No protocol?")

  var opts = { method: method
             , headers: headers
             , path: (remote.pathname||"/")
                   + (remote.search||"")
                   + (remote.hash||"")
             , host: remote.hostname
             , secure: remote.protocol
                       && remote.protocol.toLowerCase() === "https:"
             , port: remote.port
             }
  if (!opts.port) opts.port = opts.secure ? 443 : 80
  if (opts.port !== (opts.secure ? 443 : 80)) {
    opts.headers.host = opts.headers.host || opts.host
    opts.headers.host += ":" + opts.port
  }
  log.verbose(headers.host, "headers.host")

  opts = proxyify(npm.config.get("proxy"), remote, opts)
  if (!opts) return cb(new Error("Bad proxy config: "+npm.config.get("proxy")))

  if (opts.secure) https = https || require("https")
  else http = http || require("http")

  var req = (opts.secure ? https : http).request(opts, function (response) {
    log.verbose(where, "response")

    var data = ""
    response.on("error", log.er(cb, "response error from "+where))
    response.on("data", function (chunk) {
      log.silly(chunk+"", "chunk")
      data += chunk
    })

    response.on("end", function () {
      if (!nofollow
          && (response.statusCode === 301 || response.statusCode === 302)) {
        // relative redirects SHOULD be disallowed, but alas...
        var newLoc = response.headers.location
        newLoc = newLoc && url.resolve(where, newLoc)
        log.verbose(newLoc, "redirect")
        if (!newLoc) return cb(new Error(
          response.statusCode + " status code with no location"))
        //FIXME: wtf? why does this timeout make it work?
        return setTimeout(function () {
          log.verbose(newLoc, "redirect fer reals")
          request(method, newLoc, what, etag,
                       log.er(cb, "Failed to fetch "+newLoc))
        }, 1000*(newloctimeout ++))
      }

      var parsed
      if (response.statusCode !== 304) {
        try {
          parsed = JSON.parse(data)
        } catch (ex) {
          ex.message += "\n" + data
          log.verbose(data, "bad json")
          log.error("error parsing json", "registry")
          return cb(ex, null, data, response)
        }
      }

      var er = null
      if (parsed && response.headers.etag) {
        parsed._etag = response.headers.etag
      }

      if (parsed && parsed.error) {
        var w = url.parse(where).pathname.substr(1)
        if (parsed.error === "not_found") {
          w = w.split("/")
          name = w[w.indexOf("_rewrite") + 1]
          er = new Error("404 Not Found: "+name)
          er.errno = npm.E404
          er.pkgid = name
        } else {
          er = new Error(
            parsed.error + " " + (parsed.reason || "") + ": " + w)
        }
      } else if (method !== "HEAD" && method !== "GET") {

        // invalidate cache
        // This is irrelevant for commands that do etag caching, but
        // ls and view also have a timed cache, so this keeps the user
        // from thinking that it didn't work when it did.
        // Note that failure is an acceptable option here, since the
        // only result will be a stale cache for some helper commands.
        var path = require("path")
          , p = url.parse(where).pathname.split("/")
          , _ = "/"
          , caches = p.map(function (part) {
              return _ = path.join(_, part)
            }).map(function (cache) {
              return path.join(npm.cache, cache, ".cache.json")
            })

        // if the method is DELETE, then also remove the thing itself.
        // Note that the search index is probably invalid.  Whatever.
        // That's what you get for deleting stuff.  Don't do that.
        if (method === "DELETE") {
          p = p.slice(0, p.indexOf("-rev"))
          caches.push(path.join(npm.cache, p.join("/")))
        }

        asyncMap(caches, rm, function () {})
      }
      return cb(er, parsed, data, response)
    })
  }).on("error", cb)

  if (what instanceof File) {
    var size = Math.min(what.length, 1024*1024*1024)
      , remaining = what.length
    log.verbose(what.length, "bytes")
    ;(function W () {
      var b = new Buffer(size)
      try {
        var bytesRead = fs.readSync(what.fd, b, 0, b.length, null)
      } catch (er) {
        return log.er(cb, "Failure to read tarball")(er)
      }
      remaining -= bytesRead
      if (bytesRead) {
        log(bytesRead, "read")
        log(remaining, "remain")
        return (
            req.write(bytesRead === b.length ? b : b.slice(0, bytesRead))
          ) ? W()
            : req.on("drain", function DRAIN () {
                log.silly(remaining, "drain")
                req.removeListener("drain", DRAIN)
                W()
              })
      }
      if (!remaining) {
        req.end()
        log.verbose(what.name, "written to uploading stream")
        log.verbose("Not done yet! If it hangs/quits now, it didn't work.", "upload")
        return
      }
      // wtf!? No bytes read, but also bytes remaining.
      return cb(new Error("Some kind of weirdness reading the file"))
    })()
    return

  } else if (typeof what === "string" || Buffer.isBuffer(what)) {
    // just a json blob
    req.write(what)
  }

  req.end()
}

function GET (where, etag, nofollow, cb) {
  request("GET", where, null, etag, nofollow, cb)
}

function PUT (where, what, etag, nofollow, cb) {
  request("PUT", where, what, etag, nofollow, cb)
}

function upload (where, filename, etag, nofollow, cb) {
  if (typeof nofollow === "function") cb = nofollow, nofollow = false
  if (typeof etag === "function") cb = etag, etag = null

  new File(filename, function (er, f) {
    if (er) return log.er(cb, "Couldn't open "+filename)(er)
    PUT(where, f, etag, nofollow, function (er) {
      log.info("done with upload", "publish")
      cb(er)
    })
  })
}

function File (name, cb) {
  var f = this
  f.name = name
  if (f.loaded) return cb(null, f)
  log.info(f.name, "stat")
  fs.stat(f.name, function (er, stat) {
    if (er) return log.er(cb, "doesn't exist "+f.name)(er)
    log.silly(stat, "stat "+name)
    f.length = stat.size
    fs.open(f.name, "r", 0666, function (er, fd) {
      if (er) return log.er(cb, "Error opening "+f.name)(er)
      f.fd = fd
      cb(null, f)
    })
  })
}

function reg () {
  var r = npm.config.get("registry")
  if (!r) {
    return new Error("Must define registry URL before accessing registry.")
  }
  if (r.substr(-1) !== "/") r += "/"
  npm.config.set("registry", r)
  return r
}
<commit_msg>undefined log message error<commit_after>
module.exports = request
request.GET = GET
request.PUT = PUT
request.reg = reg
request.upload = upload

var npm = require("../../../npm")
  , http
  , https
  , url = require("url")
  , log = require("../log")
  , ini = require("../ini")
  , fs = require("graceful-fs")
  , rm = require("../rm-rf")
  , asyncMap = require("slide").asyncMap
  , proxyify = require("../proxyify")
  , warnedAuth = false
  , newloctimeout = 0

function request (method, where, what, etag, nofollow, cb_) {
  log.verbose(where||"/", method)

  if (where.match(/^\/?favicon.ico/)) {
    return cb_(new Error("favicon.ico isn't a package, it's a picture."))
  }
  if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false
  if (typeof cb_ !== "function") cb_ = etag, etag = null
  if (typeof cb_ !== "function") cb_ = what, what = null
  var registry = reg()
  if (registry instanceof Error) return cb_(registry)

  // Since there are multiple places where an error could occur,
  // don't let the cb be called more than once.
  var errState = null
  function cb (er) {
    if (errState) return
    if (er) errState = er
    cb_.apply(null, arguments)
  }

  var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/
    , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/
    , authRequired = (what || npm.config.get("always-auth"))
                      && !where.match(adduserNew)
                   || where.match(adduserChange)
                   || method === "DELETE"
  if (!where.match(/^https?:\/\//)) {
    log.verbose(where, "raw, before any munging")

    var q = where.split("?")
    where = q.shift()
    q = q.join("?")

    if (where.charAt(0) !== "/") where = "/" + where
    where = "." + where.split("/").map(function (p) {
      p = p.trim()
      if (p.match(/^org.couchdb.user/)) {
        return p.replace(/\//g, encodeURIComponent("/"))
      }
      return encodeURIComponent(p)
    }).join("/")
    if (q) where += "?" + q
    log.verbose([registry, where], "url resolving")
    where = url.resolve(registry, where)
    log.verbose(where, "url resolved")
  } else {
    log.verbose(where, "no need to resolve")
  }

  var remote = url.parse(where)
    , secure = remote.protocol === "https:"
    , port = remote.port || (secure ? 443 : 80)
    , hostname = remote.hostname
    , auth = authRequired && npm.config.get("_auth")

  log.verbose(remote, "url parsed")
  if (port !== (secure ? 443 : 80)) hostname += ":" + port

  if (authRequired && !auth) {
    return cb(new Error(
      "Cannot insert data into the registry without authorization\n"
      + "See: npm-adduser(1)"))
  }

  if (auth && !secure && !warnedAuth) {
    warnedAuth = true
    log.warn("Sending authorization over insecure channel.")
  }

  var headers = { "accept" : "application/json" }
  if (auth) headers.authorization = "Basic " + auth
  if (what) {
    if (what instanceof File) {
      log.verbose(what.name, "uploading")
      headers["content-type"] = "application/octet-stream"
    } else {
      delete what._etag
      log.silly(what,"writing json")
      what = new Buffer(JSON.stringify(what))
      headers["content-type"] = "application/json"
    }
    headers["content-length"] = what.length
  } else {
    headers["content-length"] = 0
  }

  if (etag) {
    log.verbose(etag, "etag")
    headers[method === "GET" ? "if-none-match" : "if-match"] = etag
  }

  if (!remote.protocol) log.warn(remote, "No protocol?")

  var opts = { method: method
             , headers: headers
             , path: (remote.pathname||"/")
                   + (remote.search||"")
                   + (remote.hash||"")
             , host: remote.hostname
             , secure: remote.protocol
                       && remote.protocol.toLowerCase() === "https:"
             , port: remote.port
             }
  if (!opts.port) opts.port = opts.secure ? 443 : 80

  opts.headers.host = opts.headers.host || opts.host
  if (opts.port !== (opts.secure ? 443 : 80)) {
    opts.headers.host += ":" + opts.port
  }

  opts = proxyify(npm.config.get("proxy"), remote, opts)
  if (!opts) return cb(new Error("Bad proxy config: "+npm.config.get("proxy")))

  if (opts.secure) https = https || require("https")
  else http = http || require("http")

  var req = (opts.secure ? https : http).request(opts, function (response) {
    log.verbose(where, "response")

    var data = ""
    response.on("error", log.er(cb, "response error from "+where))
    response.on("data", function (chunk) {
      log.silly(chunk+"", "chunk")
      data += chunk
    })

    response.on("end", function () {
      if (!nofollow
          && (response.statusCode === 301 || response.statusCode === 302)) {
        // relative redirects SHOULD be disallowed, but alas...
        var newLoc = response.headers.location
        newLoc = newLoc && url.resolve(where, newLoc)
        log.verbose(newLoc, "redirect")
        if (!newLoc) return cb(new Error(
          response.statusCode + " status code with no location"))
        //FIXME: wtf? why does this timeout make it work?
        return setTimeout(function () {
          log.verbose(newLoc, "redirect fer reals")
          request(method, newLoc, what, etag,
                       log.er(cb, "Failed to fetch "+newLoc))
        }, 1000*(newloctimeout ++))
      }

      var parsed
      if (response.statusCode !== 304) {
        try {
          parsed = JSON.parse(data)
        } catch (ex) {
          ex.message += "\n" + data
          log.verbose(data, "bad json")
          log.error("error parsing json", "registry")
          return cb(ex, null, data, response)
        }
      }

      var er = null
      if (parsed && response.headers.etag) {
        parsed._etag = response.headers.etag
      }

      if (parsed && parsed.error) {
        var w = url.parse(where).pathname.substr(1)
        if (parsed.error === "not_found") {
          w = w.split("/")
          name = w[w.indexOf("_rewrite") + 1]
          er = new Error("404 Not Found: "+name)
          er.errno = npm.E404
          er.pkgid = name
        } else {
          er = new Error(
            parsed.error + " " + (parsed.reason || "") + ": " + w)
        }
      } else if (method !== "HEAD" && method !== "GET") {

        // invalidate cache
        // This is irrelevant for commands that do etag caching, but
        // ls and view also have a timed cache, so this keeps the user
        // from thinking that it didn't work when it did.
        // Note that failure is an acceptable option here, since the
        // only result will be a stale cache for some helper commands.
        var path = require("path")
          , p = url.parse(where).pathname.split("/")
          , _ = "/"
          , caches = p.map(function (part) {
              return _ = path.join(_, part)
            }).map(function (cache) {
              return path.join(npm.cache, cache, ".cache.json")
            })

        // if the method is DELETE, then also remove the thing itself.
        // Note that the search index is probably invalid.  Whatever.
        // That's what you get for deleting stuff.  Don't do that.
        if (method === "DELETE") {
          p = p.slice(0, p.indexOf("-rev"))
          caches.push(path.join(npm.cache, p.join("/")))
        }

        asyncMap(caches, rm, function () {})
      }
      return cb(er, parsed, data, response)
    })
  }).on("error", cb)

  if (what instanceof File) {
    var size = Math.min(what.length, 1024*1024*1024)
      , remaining = what.length
    log.verbose(what.length, "bytes")
    ;(function W () {
      var b = new Buffer(size)
      try {
        var bytesRead = fs.readSync(what.fd, b, 0, b.length, null)
      } catch (er) {
        return log.er(cb, "Failure to read tarball")(er)
      }
      remaining -= bytesRead
      if (bytesRead) {
        log(bytesRead, "read")
        log(remaining, "remain")
        return (
            req.write(bytesRead === b.length ? b : b.slice(0, bytesRead))
          ) ? W()
            : req.on("drain", function DRAIN () {
                log.silly(remaining, "drain")
                req.removeListener("drain", DRAIN)
                W()
              })
      }
      if (!remaining) {
        req.end()
        log.verbose(what.name, "written to uploading stream")
        log.verbose("Not done yet! If it hangs/quits now, it didn't work.", "upload")
        return
      }
      // wtf!? No bytes read, but also bytes remaining.
      return cb(new Error("Some kind of weirdness reading the file"))
    })()
    return

  } else if (typeof what === "string" || Buffer.isBuffer(what)) {
    // just a json blob
    req.write(what)
  }

  req.end()
}

function GET (where, etag, nofollow, cb) {
  request("GET", where, null, etag, nofollow, cb)
}

function PUT (where, what, etag, nofollow, cb) {
  request("PUT", where, what, etag, nofollow, cb)
}

function upload (where, filename, etag, nofollow, cb) {
  if (typeof nofollow === "function") cb = nofollow, nofollow = false
  if (typeof etag === "function") cb = etag, etag = null

  new File(filename, function (er, f) {
    if (er) return log.er(cb, "Couldn't open "+filename)(er)
    PUT(where, f, etag, nofollow, function (er) {
      log.info("done with upload", "publish")
      cb(er)
    })
  })
}

function File (name, cb) {
  var f = this
  f.name = name
  if (f.loaded) return cb(null, f)
  log.info(f.name, "stat")
  fs.stat(f.name, function (er, stat) {
    if (er) return log.er(cb, "doesn't exist "+f.name)(er)
    log.silly(stat, "stat "+name)
    f.length = stat.size
    fs.open(f.name, "r", 0666, function (er, fd) {
      if (er) return log.er(cb, "Error opening "+f.name)(er)
      f.fd = fd
      cb(null, f)
    })
  })
}

function reg () {
  var r = npm.config.get("registry")
  if (!r) {
    return new Error("Must define registry URL before accessing registry.")
  }
  if (r.substr(-1) !== "/") r += "/"
  npm.config.set("registry", r)
  return r
}
<|endoftext|>


<commit_before>Package.describe({
  summary: "Make HTTP calls to remote servers",
  version: '1.2.11'
});

Npm.depends({
  request: "2.72.0"
});

Package.onUse(function (api) {
  api.use([
    'underscore',
    'url',
    'ecmascript'
  ]);

  api.export('HTTP');
  api.export('HTTPInternals', 'server');
  api.addFiles('httpcall_common.js', ['client', 'server']);
  api.addFiles('httpcall_client.js', 'client');
  api.addFiles('httpcall_server.js', 'server');
  api.addFiles('deprecated.js', ['client', 'server']);
});

Package.onTest(function (api) {
  api.use('ecmascript');
  api.use('webapp', 'server');
  api.use('underscore');
  api.use('random');
  api.use('jquery', 'client');
  api.use('http', ['client', 'server']);
  api.use('tinytest');
  api.use('test-helpers', ['client', 'server']);

  api.addFiles('test_responder.js', 'server');
  api.addFiles('httpcall_tests.js', ['client', 'server']);

  api.addAssets('test_static.serveme', 'client');
});
<commit_msg>Remove jQuery dependency from http package (#8389)<commit_after>Package.describe({
  summary: "Make HTTP calls to remote servers",
  version: '1.2.11'
});

Npm.depends({
  request: "2.72.0"
});

Package.onUse(function (api) {
  api.use([
    'underscore',
    'url',
    'ecmascript'
  ]);

  api.export('HTTP');
  api.export('HTTPInternals', 'server');
  api.addFiles('httpcall_common.js', ['client', 'server']);
  api.addFiles('httpcall_client.js', 'client');
  api.addFiles('httpcall_server.js', 'server');
  api.addFiles('deprecated.js', ['client', 'server']);
});

Package.onTest(function (api) {
  api.use('ecmascript');
  api.use('webapp', 'server');
  api.use('underscore');
  api.use('random');
  api.use('http', ['client', 'server']);
  api.use('tinytest');
  api.use('test-helpers', ['client', 'server']);

  api.addFiles('test_responder.js', 'server');
  api.addFiles('httpcall_tests.js', ['client', 'server']);

  api.addAssets('test_static.serveme', 'client');
});
<|endoftext|>


<commit_before>/*
 * library for ajaxcourse formats, the classes and related functions for drag and drop blocks
 *
 * this library requires a 'main' object created in calling document
 *
 */

        //set Drag and Drop to Intersect mode:
        YAHOO.util.DDM.mode = YAHOO.util.DDM.INTERSECT;

/*
 * class for draggable block, extends YAHOO.util.DDProxy
 */
function block_class(id,group,config){
    this.init_block(id,group,config);
}
YAHOO.extend(block_class, YAHOO.util.DDProxy);

block_class.prototype.debug = true;

block_class.prototype.init_block = function(id, sGroup, config){
    if (!id) { return; }

    //Drag and Drop
    this.init(id, sGroup, config);
    this.initFrame();
    this.createFrame();

    this.is = 'block';
    this.instanceId = this.getEl().id.replace(/inst/i,'');

    this.addInvalidHandleType('a');

    var s = this.getEl().style;
    s.opacity = 0.76;
    s.filter = "alpha(opacity=76)";

    // specify that this is not currently a drop target
    this.isTarget = false;

        this.region = YAHOO.util.Region.getRegion(this.getEl());

    this.type = block_class.TYPE;

    //DHTML
    this.viewbutton = null;
    this.originalClass = this.getEl().className;

    this.init_buttons();
    }

block_class.prototype.startDrag = function(x, y) {
    //operates in intersect mode
    YAHOO.util.DDM.mode = YAHOO.util.DDM.INTERSECT;

    YAHOO.log(this.id + " startDrag");

    var dragEl = this.getDragEl();
    var clickEl = this.getEl();

    dragEl.innerHTML = clickEl.innerHTML;
    dragEl.className = clickEl.className;
    dragEl.style.color = this.DDM.getStyle(clickEl, "color");;
    dragEl.style.backgroundColor = this.DDM.getStyle(clickEl, "backgroundColor");
    dragEl.style.border = '0px';

    var s = clickEl.style;
    s.opacity = .1;
    s.filter = "alpha(opacity=10)";

    var targets = YAHOO.util.DDM.getRelated(this, true);
    YAHOO.log(targets.length + " targets");

    //restyle side boxes to highlight
    for (var i=0; i<targets.length; i++) {

        var targetEl = targets[i].getEl();

        targetEl.style.background = "#fefff0";
        targetEl.opacity = .3;
        targetEl.filter = "alpha(opacity=30)";
    }
    }

block_class.prototype.endDrag = function() {
    // reset the linked element styles
    var s = this.getEl().style;
    s.opacity = 1;
    s.filter = "alpha(opacity=100)";
    this.resetTargets();
    }

block_class.prototype.onDragDrop = function(e, id) {
    // get the drag and drop object that was targeted
    var oDD;

    if ("string" == typeof id) {
        oDD = YAHOO.util.DDM.getDDById(id);
    } else {
        oDD = YAHOO.util.DDM.getBestMatch(id);
    }

    var el = this.getEl();

    YAHOO.log("id="+id+" el = "+e+" x="+YAHOO.util.Dom.getXY(this.getDragEl()));

    //var collisions = this.find_collisions(e,id);

    this.move_block(id);
    //YAHOO.util.DDM.moveToEl(el, oDD.getEl());

    this.resetTargets();

    }

block_class.prototype.find_target = function(column){
        var collisions = column.find_sub_collision(YAHOO.util.Region.getRegion(this.getDragEl()));

        //determine position
        var insertbefore = null;
        if(collisions.length == 0)
          return;

       insertbefore = column.blocks[collisions[0][0]];

        return insertbefore;
    }

block_class.prototype.resetTargets = function() {
        // reset the target styles
        var targets = YAHOO.util.DDM.getRelated(this, true);
        for (var i=0; i<targets.length; i++) {
            var targetEl = targets[i].getEl();
            targetEl.style.background = "";
            targetEl.opacity = 1;
            targetEl.filter = "alpha(opacity=100)";
        }
    }

block_class.prototype.move_block = function(columnid){
        if(this.debug)YAHOO.log("Dropped on "+columnid[0]);
        //var column = YAHOO.util.DDM.getDDById(columnid[0].);
        column = columnid[0];
        var inserttarget = this.find_target(column);

        if(this.debug && inserttarget != null)YAHOO.log("moving "+this.getEl().id+" before "+inserttarget.getEl().id+" - parentNode="+this.getEl().parentNode.id);

        if(this == inserttarget){
            if(this.debug)YAHOO.log("Dropping on self, resetting");
            this.endDrag();
            return;
        }

        //remove from document
        if(this.getEl().parentNode != null)
          this.getEl().parentNode.removeChild(this.getEl());

        //insert into correct place
        if(inserttarget != null ){
            inserttarget.getEl().parentNode.insertBefore(this.getEl(),inserttarget.getEl());

        }else if(column == main.rightcolumn){//if right side insert before admin block
            column.getEl().insertBefore(this.getEl(),main.adminBlock);

        }else{
            column.getEl().appendChild(this.getEl());
        }

        this.reset_regions();

        //remove block from current array
        if(main.rightcolumn.has_block(this))
              main.rightcolumn.remove_block(this);

        else if(main.leftcolumn.has_block(this))
              main.leftcolumn.remove_block(this);

        //insert into new array
        column.insert_block(this,inserttarget);

    }

block_class.prototype.reset_regions = function(){
      var blockcount = main.blocks.length;
      for(i=0;i<blockcount;i++)
           main.blocks[i].region = YAHOO.util.Region.getRegion(main.blocks[i].getEl());
    }

block_class.prototype.init_buttons = function(){

       var viewbutton = main.mk_button('a','/pix/t/hide.gif',[['class','icon hide']]);
       YAHOO.util.Event.addListener(viewbutton,'click',this.toggle_hide,this,true);

       var deletebutton = main.mk_button('a','/pix/t/delete.gif',[['class','icon delete']]);
       YAHOO.util.Event.addListener(deletebutton,'click',this.delete_button,this,true);

       this.viewbutton = viewbutton;

       buttonCont = YAHOO.util.Dom.getElementsByClassName('commands','div',this.getEl())[0];
       buttonCont.appendChild(viewbutton);
       buttonCont.appendChild(deletebutton);

    }

block_class.prototype.toggle_hide = function(e,target,isCosmetic){
       if(YAHOO.util.Dom.hasClass(this.getEl(),'hidden')){
           this.getEl().className = this.originalClass;
           this.viewbutton.childNodes[0].src = this.viewbutton.childNodes[0].src.replace(/show.gif/i,'hide.gif');
           if(!isCosmetic)main.connect('post','class=block&field=visible',null,'value=1&instanceId='+this.instanceId);

       }else{
           this.originalClass = this.getEl().className;
           this.getEl().className = "hidden sideblock";
           this.viewbutton.childNodes[0].src = this.viewbutton.childNodes[0].src.replace(/hide.gif/i,'show.gif');
           if(!isCosmetic)main.connect('post','class=block&field=visible',null,'value=0&instanceId='+this.instanceId);

       }
    }

block_class.prototype.delete_button = function(){
        //remove from local model
        if(main.rightcolumn.has_block(this))
              main.rightcolumn.remove_block(this);

        else if(main.leftcolumn.has_block(this))
              main.leftcolumn.remove_block(this);

        //remove from remote model
        main.connect('delete','class=block&instanceId='+this.instanceId);

        //remove from view
        if(this.debug)YAHOO.log("Deleting "+this.getEl().id);
        main.blocks[main.get_block_index(this)] = null;
        this.getEl().parentNode.removeChild(this.getEl());

    }

block_class.prototype.update_index = function(index,columnId){
       //update via AJAX the db representation of page
       main.connectQueue_add('post','class=block&field=position',null,'value='+columnId+'&weight='+index+'&instanceId='+this.instanceId);
       if(this.debug)YAHOO.log("updating position of "+this.getEl().id+" to index "+index+" on column "+columnId);
    }

/*
 * column class, DD targets
 */

function column_class(id,group,config,ident){
    this.init_column(id,group,config,ident);
}
YAHOO.extend(column_class, YAHOO.util.DDTarget);

column_class.prototype.debug = true;

column_class.prototype.init_column = function(id, group,config,ident){
        if (!id) { return; }

        this.initTarget(id,group,config);
        this.blocks = new Array();
        this.ident = ident;

//      YAHOO.log("init_column "+id+"-"+el.id);
        this.region = YAHOO.util.Region.getRegion(id);

    }

column_class.prototype.find_sub_collision = function(dragRegion){
        if(this.debug)YAHOO.log("Finding Collisions on "+this.getEl().id+" with "+this.blocks.length+" blocks");
        //find collisions with sub_elements(blocks), return array of collisions with regions of collision
        var collisions = new Array();
        for(i=0;i<this.blocks.length;i++){
            if(this.debug)YAHOO.log("testing region "+this.blocks[i].region+" against" + dragRegion + "intersect ="+this.blocks[i].region.intersect(dragRegion));
            var intersect = this.blocks[i].region.intersect(dragRegion);
            if(intersect != null){
                index = collisions.length;
                collisions[index] = new Array();
                collisions[index][0] = i;
                collisions[index][1] = this.blocks[i].region.intersect(dragRegion);
                if(this.debug)YAHOO.log(index+" Collides with "+this.blocks[i].getEl().id+" area" + collisions[index][1].getArea());
            }
        }
      return collisions;
    }

column_class.prototype.add_block = function(el){
       this.blocks[this.blocks.length] = el;
     }

column_class.prototype.insert_block = function(el,targetel){
        var blockcount = this.blocks.length;
        var found = false;
        var tempStore = nextStore = null;
        for(var i=0;i<blockcount;i++){
            if(found){
                tempStore = this.blocks[i];
                this.blocks[i] = nextStore;
                nextStore = tempStore;

            }else if(this.blocks[i] == targetel){
                found = true;
                nextStore = this.blocks[i];
                this.blocks[i] = el;
                blockcount++;
            }
            this.blocks[i].update_index(i,this.ident);
        }

        if(!found){//insert at end
            this.add_block(el);
            el.update_index(this.blocks.length,this.ident);
        }
    }

column_class.prototype.has_block = function(el){
        var blockcount = this.blocks.length;
        for(var i=0;i<blockcount;i++)
            if(this.blocks[i]==el)
                 return true;
        return false;
    }

column_class.prototype.remove_block = function(el){
        var blockcount = this.blocks.length;
        var found = false;
        for(var i=0;i<blockcount;i++){
            if(this.blocks[i]==el || found){
               if(!found)
                    found = true;

               if(i < blockcount-1){
                   this.blocks[i+1].update_index(i,this.ident);
                   this.blocks[i] = this.blocks[i+1];
               }else{
                    this.blocks.pop();
               }
            }
        }
        YAHOO.log("column "+this.indent+" has "+blockcount+"blocks");
    }

    <commit_msg>updates to accommodate retooled command.php<commit_after>/*
 * library for ajaxcourse formats, the classes and related functions for drag and drop blocks
 *
 * this library requires a 'main' object created in calling document
 *
 */

        //set Drag and Drop to Intersect mode:
        YAHOO.util.DDM.mode = YAHOO.util.DDM.INTERSECT;

/*
 * class for draggable block, extends YAHOO.util.DDProxy
 */
function block_class(id,group,config){
    this.init_block(id,group,config);
}
YAHOO.extend(block_class, YAHOO.util.DDProxy);

block_class.prototype.debug = true;

block_class.prototype.init_block = function(id, sGroup, config){
    if (!id) { return; }

    //Drag and Drop
    this.init(id, sGroup, config);
    this.initFrame();
    this.createFrame();

    this.is = 'block';
    this.instanceId = this.getEl().id.replace(/inst/i,'');

    this.addInvalidHandleType('a');

    var s = this.getEl().style;
    s.opacity = 0.76;
    s.filter = "alpha(opacity=76)";

    // specify that this is not currently a drop target
    this.isTarget = false;

        this.region = YAHOO.util.Region.getRegion(this.getEl());

    this.type = block_class.TYPE;

    //DHTML
    this.viewbutton = null;
    this.originalClass = this.getEl().className;

    this.init_buttons();
    }

block_class.prototype.startDrag = function(x, y) {
    //operates in intersect mode
    YAHOO.util.DDM.mode = YAHOO.util.DDM.INTERSECT;

    YAHOO.log(this.id + " startDrag");

    var dragEl = this.getDragEl();
    var clickEl = this.getEl();

    dragEl.innerHTML = clickEl.innerHTML;
    dragEl.className = clickEl.className;
    dragEl.style.color = this.DDM.getStyle(clickEl, "color");;
    dragEl.style.backgroundColor = this.DDM.getStyle(clickEl, "backgroundColor");
    dragEl.style.border = '0px';

    var s = clickEl.style;
    s.opacity = .1;
    s.filter = "alpha(opacity=10)";

    var targets = YAHOO.util.DDM.getRelated(this, true);
    YAHOO.log(targets.length + " targets");

    //restyle side boxes to highlight
    for (var i=0; i<targets.length; i++) {

        var targetEl = targets[i].getEl();

        targetEl.style.background = "#fefff0";
        targetEl.opacity = .3;
        targetEl.filter = "alpha(opacity=30)";
    }
    }

block_class.prototype.endDrag = function() {
    // reset the linked element styles
    var s = this.getEl().style;
    s.opacity = 1;
    s.filter = "alpha(opacity=100)";
    this.resetTargets();
    }

block_class.prototype.onDragDrop = function(e, id) {
    // get the drag and drop object that was targeted
    var oDD;

    if ("string" == typeof id) {
        oDD = YAHOO.util.DDM.getDDById(id);
    } else {
        oDD = YAHOO.util.DDM.getBestMatch(id);
    }

    var el = this.getEl();

    YAHOO.log("id="+id+" el = "+e+" x="+YAHOO.util.Dom.getXY(this.getDragEl()));

    //var collisions = this.find_collisions(e,id);

    this.move_block(id);
    //YAHOO.util.DDM.moveToEl(el, oDD.getEl());

    this.resetTargets();

    }

block_class.prototype.find_target = function(column){
        var collisions = column.find_sub_collision(YAHOO.util.Region.getRegion(this.getDragEl()));

        //determine position
        var insertbefore = null;
        if(collisions.length == 0)
          return;

       insertbefore = column.blocks[collisions[0][0]];

        return insertbefore;
    }

block_class.prototype.resetTargets = function() {
        // reset the target styles
        var targets = YAHOO.util.DDM.getRelated(this, true);
        for (var i=0; i<targets.length; i++) {
            var targetEl = targets[i].getEl();
            targetEl.style.background = "";
            targetEl.opacity = 1;
            targetEl.filter = "alpha(opacity=100)";
        }
    }

block_class.prototype.move_block = function(columnid){
        if(this.debug)YAHOO.log("Dropped on "+columnid[0]);
        //var column = YAHOO.util.DDM.getDDById(columnid[0].);
        column = columnid[0];
        var inserttarget = this.find_target(column);

        if(this.debug && inserttarget != null)YAHOO.log("moving "+this.getEl().id+" before "+inserttarget.getEl().id+" - parentNode="+this.getEl().parentNode.id);

        if(this == inserttarget){
            if(this.debug)YAHOO.log("Dropping on self, resetting");
            this.endDrag();
            return;
        }

        //remove from document
        if(this.getEl().parentNode != null)
          this.getEl().parentNode.removeChild(this.getEl());

        //insert into correct place
        if(inserttarget != null ){
            inserttarget.getEl().parentNode.insertBefore(this.getEl(),inserttarget.getEl());

        }else if(column == main.rightcolumn){//if right side insert before admin block
            column.getEl().insertBefore(this.getEl(),main.adminBlock);

        }else{
            column.getEl().appendChild(this.getEl());
        }

        this.reset_regions();

        //remove block from current array
        if(main.rightcolumn.has_block(this))
              main.rightcolumn.remove_block(this);

        else if(main.leftcolumn.has_block(this))
              main.leftcolumn.remove_block(this);

        //insert into new array
        column.insert_block(this,inserttarget);

    }

block_class.prototype.reset_regions = function(){
      var blockcount = main.blocks.length;
      for(i=0;i<blockcount;i++)
           main.blocks[i].region = YAHOO.util.Region.getRegion(main.blocks[i].getEl());
    }

block_class.prototype.init_buttons = function(){

       var viewbutton = main.mk_button('a','/pix/t/hide.gif',[['class','icon hide']]);
       YAHOO.util.Event.addListener(viewbutton,'click',this.toggle_hide,this,true);

       var deletebutton = main.mk_button('a','/pix/t/delete.gif',[['class','icon delete']]);
       YAHOO.util.Event.addListener(deletebutton,'click',this.delete_button,this,true);

       this.viewbutton = viewbutton;

       buttonCont = YAHOO.util.Dom.getElementsByClassName('commands','div',this.getEl())[0];
       buttonCont.appendChild(viewbutton);
       buttonCont.appendChild(deletebutton);

    }

block_class.prototype.toggle_hide = function(e,target,isCosmetic){
       if(YAHOO.util.Dom.hasClass(this.getEl(),'hidden')){
           this.getEl().className = this.originalClass;
           this.viewbutton.childNodes[0].src = this.viewbutton.childNodes[0].src.replace(/show.gif/i,'hide.gif');
           if(!isCosmetic)main.connect('post','class=block&field=visible',null,'value=1&instanceId='+this.instanceId);

       }else{
           this.originalClass = this.getEl().className;
           this.getEl().className = "hidden sideblock";
           this.viewbutton.childNodes[0].src = this.viewbutton.childNodes[0].src.replace(/hide.gif/i,'show.gif');
           if(!isCosmetic)main.connect('post','class=block&field=visible',null,'value=0&instanceId='+this.instanceId);

       }
    }

block_class.prototype.delete_button = function(){
        //remove from local model
        if(main.rightcolumn.has_block(this))
              main.rightcolumn.remove_block(this);

        else if(main.leftcolumn.has_block(this))
              main.leftcolumn.remove_block(this);

        //remove from remote model
        main.connect('delete','class=block&instanceId='+this.instanceId);

        //remove from view
        if(this.debug)YAHOO.log("Deleting "+this.getEl().id);
        main.blocks[main.get_block_index(this)] = null;
        this.getEl().parentNode.removeChild(this.getEl());

    }

block_class.prototype.update_index = function(index,columnId){
       //update via AJAX the db representation of page
       main.connectQueue_add('post','class=block&field=position',null,'value='+index+'&column='+columnId+'&instanceId='+this.instanceId);
       if(this.debug)YAHOO.log("updating position of "+this.getEl().id+" to index "+index+" on column "+columnId);
    }

/*
 * column class, DD targets
 */

function column_class(id,group,config,ident){
    this.init_column(id,group,config,ident);
}
YAHOO.extend(column_class, YAHOO.util.DDTarget);

column_class.prototype.debug = true;

column_class.prototype.init_column = function(id, group,config,ident){
        if (!id) { return; }

        this.initTarget(id,group,config);
        this.blocks = new Array();
        this.ident = ident;

//      YAHOO.log("init_column "+id+"-"+el.id);
        this.region = YAHOO.util.Region.getRegion(id);

    }

column_class.prototype.find_sub_collision = function(dragRegion){
        if(this.debug)YAHOO.log("Finding Collisions on "+this.getEl().id+" with "+this.blocks.length+" blocks");
        //find collisions with sub_elements(blocks), return array of collisions with regions of collision
        var collisions = new Array();
        for(i=0;i<this.blocks.length;i++){
            if(this.debug)YAHOO.log("testing region "+this.blocks[i].region+" against" + dragRegion + "intersect ="+this.blocks[i].region.intersect(dragRegion));
            var intersect = this.blocks[i].region.intersect(dragRegion);
            if(intersect != null){
                index = collisions.length;
                collisions[index] = new Array();
                collisions[index][0] = i;
                collisions[index][1] = this.blocks[i].region.intersect(dragRegion);
                if(this.debug)YAHOO.log(index+" Collides with "+this.blocks[i].getEl().id+" area" + collisions[index][1].getArea());
            }
        }
      return collisions;
    }

column_class.prototype.add_block = function(el){
       this.blocks[this.blocks.length] = el;
     }

column_class.prototype.insert_block = function(el,targetel){
        var blockcount = this.blocks.length;
        var found = false;
        var tempStore = nextStore = null;
        for(var i=0;i<blockcount;i++){
            if(found){
                tempStore = this.blocks[i];
                this.blocks[i] = nextStore;
                nextStore = tempStore;

            }else if(this.blocks[i] == targetel){
                found = true;
                nextStore = this.blocks[i];
                this.blocks[i] = el;
                blockcount++;
            }
            this.blocks[i].update_index(i,this.ident);
        }

        if(!found){//insert at end
            this.add_block(el);
            el.update_index(this.blocks.length,this.ident);
        }
    }

column_class.prototype.has_block = function(el){
        var blockcount = this.blocks.length;
        for(var i=0;i<blockcount;i++)
            if(this.blocks[i]==el)
                 return true;
        return false;
    }

column_class.prototype.remove_block = function(el){
        var blockcount = this.blocks.length;
        var found = false;
        for(var i=0;i<blockcount;i++){
            if(this.blocks[i]==el || found){
               if(!found)
                    found = true;

               if(i < blockcount-1){
                   this.blocks[i+1].update_index(i,this.ident);
                   this.blocks[i] = this.blocks[i+1];
               }else{
                    this.blocks.pop();
               }
            }
        }
        YAHOO.log("column "+this.indent+" has "+blockcount+"blocks");
    }

    <|endoftext|>


<commit_before>/**
 * Falsy Values: false, 0, "", null, undefined, NaN
 */

/**
 * Augments the Function type object's prototype with a method called "method."
 * This method allows any Constructor Function, that prototypally inherits from
 * Function.prototype (so all Constructor Functions), to easily add methods to
 * their own prototype objects.
 *
 * @param name is the name of the method passed as a String object argument.
 * @param func is the first-class function object passed in as an argument.
 * @return {*} a reference to your respective Constructor Function.
 */
Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

/**
 * Psychologist James J. Gibson originally introduced the term in his 1977 article "The Theory of Affordances"[1] and
 * explored it more fully in his book The Ecological Approach to Visual Perception[2] in 1979. He defined affordances
 * as all "action possibilities" latent in the environment, objectively measurable and independent of the individual's
 * ability to recognize them, but always in relation to agents and therefore dependent on their capabilities.
 * For instance, a set of steps which rises four feet high does not afford the act of climbing if the actor is a
 * crawling infant. Gibson's is the prevalent definition in cognitive psychology.
 *
 * Fielding mentioned hypermedia as the way to modify state on a distributed network. In this he meant “the simultaneous
 * presentation of information and controls such that the information becomes the affordance through which the user
 * obtains choices and selects actions.”
 *
 * “REST is defined by four interface constraints: identification of resources; manipulation of resources through
 * representations; self-descriptive messages; and, hypermedia as the engine of application state.” In the context of
 * this application, a Message is a collection of "action possibilities" from a Resource-Oriented
 * Architecture web API that facilitates the fourth interface constraint of REST.
 *
 * The "action possibilities" are Affordance identifier Strings.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {*}
 * @constructor
 */
function Message(request, response, message) {
    this._request = (typeof request === 'string') ? request : '';
    this._response = (typeof response === 'string') ? response : '';
    this._message = Array.isArray(message) ? message : [];
    return this;
}

/**
 * Accessor method to allow the setting of the request field.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @return {*} for chaining.
 */
Message.method('setRequest', function (request) {
    this._request = (typeof request === 'string') ? request : this._request;
    return this;
});

/**
 * Accessor method to allow the setting of the response field.
 *
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @return {*} for chaining.
 */
Message.method('setResponse', function (response) {
    this._response = (typeof response === 'string') ? response : this._response;
    return this;
});

/**
 * Accessor method to allow the setting of the message field.
 *
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {*} for chaining.
 */
Message.method('setMessage', function (message) {
    this._message = Array.isArray(message) ? message : this._message;
    return this;
});

/**
 * Allows for the addition of a String identifier of an Affordance to the Affordance-Rich Message.
 *
 * @param id should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @return {*} for chaining.
 */
Message.method('addAffordance', function (id) {
    var message = this._message;
    if (typeof id === 'string' && message.lastIndexOf(id) === -1) {
        message.push(id);
    }
    return this;
});

/**
 * Convenience method to enforce safe method invocations without the use of the new operator.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {Message}
 */
function message(request, response, message) {
    return new Message(request, response, message);
}

/**
 * Convenience method to allow for type checking outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to test the prototypal inheritance chain on.
 * @return {Boolean}
 */
message.isPrototypeOf = function (object) {
    return object instanceof Message;
};

/**
 * Convenience method to allow for duck-type checking of a generic Object outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to duck-type check as a Message.
 * @return {Boolean}
 */
message.canRevive = function (object) {
    /**
     * Is the argument an Object, and does it have the minimum properties necessary to be duck-typed a Message?
     */
    return !!object &&
        typeof object === 'object' &&
        typeof object._request === 'string' &&
        typeof object._response === 'string' &&
        Array.isArray(object._message);
};

/**
 * Convenience method to allow for reviving a JSON encoded Message outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to revive to a Message instance.
 * @return {Message}
 */
message.revive = function (object) {
    /**
     * If the argument is an Object, then create an instance of Message and attempt to copy the arguments fields to
     * the instance.
     */
    if (!!object && typeof object === 'object') {
        var instance =
                new Message()
                    .setRequest(object._request)
                    .setResponse(object._response),
            message = object._message;

        if (Array.isArray(message)) {
            for (var id in message) {
                id = message[id];
                instance.addAffordance(id);
            }
        }
        return instance;
    }
    /**
     * If the argument is not an Object, then return null as it cannot be revived.
     */
    return null;
};

module.exports = exports = message;
<commit_msg>modified:   message.js<commit_after>/**
 * Falsy Values: false, 0, "", null, undefined, NaN
 */

/**
 * Augments the Function type object's prototype with a method called "method."
 * This method allows any Constructor Function, that prototypally inherits from
 * Function.prototype (so all Constructor Functions), to easily add methods to
 * their own prototype objects.
 *
 * @param name is the name of the method passed as a String object argument.
 * @param func is the first-class function object passed in as an argument.
 * @return {*} a reference to your respective Constructor Function.
 */
Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

/**
 * Psychologist James J. Gibson originally introduced the term in his 1977 article "The Theory of Affordances"[1] and
 * explored it more fully in his book The Ecological Approach to Visual Perception[2] in 1979. He defined affordances
 * as all "action possibilities" latent in the environment, objectively measurable and independent of the individual's
 * ability to recognize them, but always in relation to agents and therefore dependent on their capabilities.
 * For instance, a set of steps which rises four feet high does not afford the act of climbing if the actor is a
 * crawling infant. Gibson's is the prevalent definition in cognitive psychology.
 *
 * Fielding mentioned hypermedia as the way to modify state on a distributed network. In this he meant “the simultaneous
 * presentation of information and controls such that the information becomes the affordance through which the user
 * obtains choices and selects actions.”
 *
 * “REST is defined by four interface constraints: identification of resources; manipulation of resources through
 * representations; self-descriptive messages; and, hypermedia as the engine of application state.” In the context of
 * this application, a Message is a collection of "action possibilities" from a Resource-Oriented
 * Architecture web API that facilitates the fourth interface constraint of REST.
 *
 * The "action possibilities" are Affordance identifier Strings.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {*}
 * @constructor
 */
function Message(request, response, message) {
    this._request = (typeof request === 'string') ? request : '';
    this._response = (typeof response === 'string') ? response : '';
    this._message = Array.isArray(message) ? message : [];
    return this;
}

/**
 * Accessor method to allow the setting of the request field.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @return {*} for chaining.
 */
Message.method('setRequest', function (request) {
    this._request = (typeof request === 'string') ? request : this._request;
    return this;
});

/**
 * Accessor method to allow the setting of the response field.
 *
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @return {*} for chaining.
 */
Message.method('setResponse', function (response) {
    this._response = (typeof response === 'string') ? response : this._response;
    return this;
});

/**
 * Accessor method to allow the setting of the message field.
 *
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {*} for chaining.
 */
Message.method('setMessage', function (message) {
    this._message = Array.isArray(message) ? message : this._message;
    return this;
});

/**
 * Allows for the addition of a String identifier of an Affordance to the Affordance-Rich Message.
 *
 * @param id should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @return {*} for chaining.
 */
Message.method('addAffordance', function (id) {
    var message = this._message;
    if (typeof id === 'string' && message.lastIndexOf(id) === -1) {
        message.push(id);
    }
    return this;
});

/**
 * Convenience method to enforce safe method invocations without the use of the new operator.
 *
 * @param request should be a String identifier from an Affordance. These are arbitrary, but globally unique Strings
 * from the Affordances collection representing an API.
 * Example: 'entry', or 'get-users'
 * @param response should be a String representative of a response code from a client-server protocol capable of
 * facilitating REST.
 * Example: '200', or '400' given HTTP as the transfer protocol.
 * @param message should be an Array of String identifiers representing the Affordances allowed within the message of
 * the request-response transaction.
 * Example: ['entry'], or ['entry', 'get-users']
 * @return {Message}
 */
function message(request, response, message) {
    return new Message(request, response, message);
}

/**
 * Convenience method to allow for type checking outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to test the prototypal inheritance chain on.
 * @return {Boolean}
 */
message.isPrototypeOf = function (object) {
    return object instanceof Message;
};

/**
 * Convenience method to allow for duck-type checking of a generic Object outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to duck-type check as a Message.
 * @return {Boolean}
 */
message.canRevive = function (object) {
    /**
     * Is the argument an Object, and does it have the minimum properties necessary to be duck-typed a Message?
     */
    return !!object &&
        typeof object === 'object' &&
        typeof object._request === 'string' &&
        typeof object._response === 'string' &&
        Array.isArray(object._message);
};

/**
 * Convenience method to allow for reviving a JSON encoded Message outside of the scope of this module.
 *
 * @param object is a reference to an object you would like to revive to a Message instance.
 * @return {Message} if revivable; null, otherwise.
 */
message.revive = function (object) {
    if (!!object &&
        typeof object === 'object' &&
        typeof object._request === 'string' &&
        typeof object._response === 'string' &&
        Array.isArray(object._message)) {
        var instance =
                new Message()
                    .setRequest(object._request)
                    .setResponse(object._response),
            message = object._message;

        for (var id in message) {
            id = message[id];
            instance.addAffordance(id);
        }
        return instance;
    }
    return null;
};

module.exports = exports = message;
<|endoftext|>


<commit_before>/**
 * Dropdown module.
 * @module foundation.dropdown
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.triggers
 */
!function($, Foundation){
  'use strict';
  /**
   * Creates a new instance of a dropdown.
   * @class
   * @param {jQuery} element - jQuery object to make into a dropdown.
   *        Object should be of the dropdown panel, rather than its anchor.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  function Dropdown(element, options){
    this.$element = element;
    this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
    this._init();

    Foundation.registerPlugin(this, 'Dropdown');
    Foundation.Keyboard.register('Dropdown', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close',
      'TAB': 'tab_forward',
      'SHIFT_TAB': 'tab_backward'
    });
  }

  Dropdown.defaults = {
    /**
     * Amount of time to delay opening a submenu on hover event.
     * @option
     * @example 250
     */
    hoverDelay: 250,
    /**
     * Allow submenus to open on hover events
     * @option
     * @example false
     */
    hover: false,
    /**
     * Don't close dropdown when hovering over dropdown pane
     * @option
     * @example true
     */
    hoverPane: false,
    /**
     * Number of pixels between the dropdown pane and the triggering element on open.
     * @option
     * @example 1
     */
    vOffset: 1,
    /**
     * Number of pixels between the dropdown pane and the triggering element on open.
     * @option
     * @example 1
     */
    hOffset: 1,
    /**
     * Class applied to adjust open position. JS will test and fill this in.
     * @option
     * @example 'top'
     */
    positionClass: '',
    /**
     * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
     * @option
     * @example false
     */
    trapFocus: false,
    /**
     * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
     * @option
     * @example true
     */
    autoFocus: false,
    /**
     * Allows a click on the body to close the dropdown.
     * @option
     * @example false
     */
    closeOnClick: false
  };
  /**
   * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
   * @function
   * @private
   */
  Dropdown.prototype._init = function(){
    var $id = this.$element.attr('id');

    this.$anchor = $(`[data-toggle="${$id}"]`) || $(`[data-open="${$id}"]`);
    this.$anchor.attr({
      'aria-controls': $id,
      'data-is-focus': false,
      'data-yeti-box': $id,
      'aria-haspopup': true,
      'aria-expanded': false
      // 'data-resize': $id
    });

    this.options.positionClass = this.getPositionClass();
    this.counter = 4;
    this.usedPositions = [];
    this.$element.attr({
      'aria-hidden': 'true',
      'data-yeti-box': $id,
      'data-resize': $id,
      'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor')
    });
    this._events();
  };
  /**
   * Helper function to determine current orientation of dropdown pane.
   * @function
   * @returns {String} position - string value of a position class.
   */
  Dropdown.prototype.getPositionClass = function(){
    var position = this.$element[0].className.match(/\b(top|left|right)\b/g);
        position = position ? position[0] : '';
    return position;
  };
  /**
   * Adjusts the dropdown panes orientation by adding/removing positioning classes.
   * @function
   * @private
   * @param {String} position - position class to remove.
   */
  Dropdown.prototype._reposition = function(position){
    this.usedPositions.push(position ? position : 'bottom');
    //default, try switching to opposite side
    if(!position && (this.usedPositions.indexOf('top') < 0)){
      this.$element.addClass('top');
    }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){
      this.$element.removeClass(position)
          .addClass('right');
    }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){
      this.$element.removeClass(position)
          .addClass('left');
    }

    //if default change didn't work, try bottom or left first
    else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){
      this.$element.addClass('left');
    }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){
      this.$element.removeClass(position)
          .addClass('left');
    }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }
    //if nothing cleared, set to bottom
    else{
      this.$element.removeClass(position);
    }
    this.classChanged = true;
    this.counter--;
  };
  /**
   * Sets the position and orientation of the dropdown pane, checks for collisions.
   * Recursively calls itself if a collision is detected, with a new position class.
   * @function
   * @private
   */
  Dropdown.prototype._setPosition = function(){
    if(this.$anchor.attr('aria-expanded') === 'false'){ return false; }
    var position = this.getPositionClass(),
        $eleDims = Foundation.Box.GetDimensions(this.$element),
        $anchorDims = Foundation.Box.GetDimensions(this.$anchor),
        _this = this,
        direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')),
        param = (direction === 'top') ? 'height' : 'width',
        offset = (param === 'height') ? this.options.vOffset : this.options.hOffset;

    if(($eleDims.width >= $eleDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.$element))){
      this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
        'width': $eleDims.windowDims.width - (this.options.hOffset * 2),
        'height': 'auto'
      });
      this.classChanged = true;
      return false;
    }

    this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset));

    while(!Foundation.Box.ImNotTouchingYou(this.$element) && this.counter){
      this._reposition(position);
      this._setPosition();
    }
  };
  /**
   * Adds event listeners to the element utilizing the triggers utility library.
   * @function
   * @private
   */
  Dropdown.prototype._events = function(){
    var _this = this;
    this.$element.on({
      'open.zf.trigger': this.open.bind(this),
      'close.zf.trigger': this.close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': this._setPosition.bind(this)
    });

    if(this.options.hover){
      this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
          .on('mouseenter.zf.dropdown', function(){
            clearTimeout(_this.timeout);
            _this.timeout = setTimeout(function(){
              _this.open();
              _this.$anchor.data('hover', true);
            }, _this.options.hoverDelay);
          }).on('mouseleave.zf.dropdown', function(){
            clearTimeout(_this.timeout);
            _this.timeout = setTimeout(function(){
              _this.close();
              _this.$anchor.data('hover', false);
            }, _this.options.hoverDelay);
          });
      if(this.options.hoverPane){
        this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
            .on('mouseenter.zf.dropdown', function(){
              clearTimeout(_this.timeout);
            }).on('mouseleave.zf.dropdown', function(){
              clearTimeout(_this.timeout);
              _this.timeout = setTimeout(function(){
                _this.close();
                _this.$anchor.data('hover', false);
              }, _this.options.hoverDelay);
            });
      }
    }
    this.$anchor.add(this.$element).on('keydown.zf.dropdown', function(e) {

      var $target = $(this),
        visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element);

      Foundation.Keyboard.handleKey(e, 'Dropdown', {
        tab_forward: function() {
          if (_this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) { // left modal downwards, setting focus to first element
            if (_this.options.trapFocus) { // if focus shall be trapped
              visibleFocusableElements.eq(0).focus();
              e.preventDefault();
            } else { // if focus is not trapped, close dropdown on focus out
              _this.close();
            }
          }
        },
        tab_backward: function() {
          if (_this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || _this.$element.is(':focus')) { // left modal upwards, setting focus to last element
            if (_this.options.trapFocus) { // if focus shall be trapped
              visibleFocusableElements.eq(-1).focus();
              e.preventDefault();
            } else { // if focus is not trapped, close dropdown on focus out
              _this.close();
            }
          }
        },
        open: function() {
          if ($target.is(_this.$anchor)) {
            _this.open();
            _this.$element.attr('tabindex', -1).focus();
            e.preventDefault();
          }
        },
        close: function() {
          _this.close();
          _this.$anchor.focus();
        }
      });
    });
  };
  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  Dropdown.prototype._addBodyHandler = function(){
     var $body = $(document.body).not(this.$element),
         _this = this;
     $body.off('click.zf.dropdown')
          .on('click.zf.dropdown', function(e){
            if(_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) {
              return;
            }
            if(_this.$element.find(e.target).length) {
              return;
            }
            _this.close();
            $body.off('click.zf.dropdown');
          });
  };
  /**
   * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
   * @function
   * @fires Dropdown#closeme
   * @fires Dropdown#show
   */
  Dropdown.prototype.open = function(){
    // var _this = this;
    /**
     * Fires to close other open dropdowns
     * @event Dropdown#closeme
     */
    this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
    this.$anchor.addClass('hover')
        .attr({'aria-expanded': true});
    // this.$element/*.show()*/;
    this._setPosition();
    this.$element.addClass('is-open')
        .attr({'aria-hidden': false});

    if(this.options.autoFocus){
      var $focusable = Foundation.Keyboard.findFocusable(this.$element);
      if($focusable.length){
        $focusable.eq(0).focus();
      }
    }

    if(this.options.closeOnClick){ this._addBodyHandler(); }

    /**
     * Fires once the dropdown is visible.
     * @event Dropdown#show
     */
     this.$element.trigger('show.zf.dropdown', [this.$element]);
    //why does this not work correctly for this plugin?
    // Foundation.reflow(this.$element, 'dropdown');
    // Foundation._reflow(this.$element.attr('data-dropdown'));
  };

  /**
   * Closes the open dropdown pane.
   * @function
   * @fires Dropdown#hide
   */
  Dropdown.prototype.close = function(){
    if(!this.$element.hasClass('is-open')){
      return false;
    }
    this.$element.removeClass('is-open')
        .attr({'aria-hidden': true});

    this.$anchor.removeClass('hover')
        .attr('aria-expanded', false);

    if(this.classChanged){
      var curPositionClass = this.getPositionClass();
      if(curPositionClass){
        this.$element.removeClass(curPositionClass);
      }
      this.$element.addClass(this.options.positionClass)
          /*.hide()*/.css({height: '', width: ''});
      this.classChanged = false;
      this.counter = 4;
      this.usedPositions.length = 0;
    }
    this.$element.trigger('hide.zf.dropdown', [this.$element]);
    // Foundation.reflow(this.$element, 'dropdown');
  };
  /**
   * Toggles the dropdown pane's visibility.
   * @function
   */
  Dropdown.prototype.toggle = function(){
    if(this.$element.hasClass('is-open')){
      if(this.$anchor.data('hover')) return;
      this.close();
    }else{
      this.open();
    }
  };
  /**
   * Destroys the dropdown.
   * @function
   */
  Dropdown.prototype.destroy = function(){
    this.$element.off('.zf.trigger').hide();
    this.$anchor.off('.zf.dropdown');

    Foundation.unregisterPlugin(this);
  };

  Foundation.plugin(Dropdown, 'Dropdown');
}(jQuery, window.Foundation);
<commit_msg>Convert dropdown to ES6 class<commit_after>'use strict';

/**
 * Dropdown module.
 * @module foundation.dropdown
 * @requires foundation.util.keyboard
 * @requires foundation.util.box
 * @requires foundation.util.triggers
 */

export default class Dropdown {
  /**
   * Creates a new instance of a dropdown.
   * @class
   * @param {jQuery} element - jQuery object to make into a dropdown.
   *        Object should be of the dropdown panel, rather than its anchor.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  constructor(element, options) {
    this.$element = element;
    this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
    this._init();

    Foundation.registerPlugin(this, 'Dropdown');
    Foundation.Keyboard.register('Dropdown', {
      'ENTER': 'open',
      'SPACE': 'open',
      'ESCAPE': 'close',
      'TAB': 'tab_forward',
      'SHIFT_TAB': 'tab_backward'
    });
  }

  /**
   * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
   * @function
   * @private
   */
  _init() {
    var $id = this.$element.attr('id');

    this.$anchor = $(`[data-toggle="${$id}"]`) || $(`[data-open="${$id}"]`);
    this.$anchor.attr({
      'aria-controls': $id,
      'data-is-focus': false,
      'data-yeti-box': $id,
      'aria-haspopup': true,
      'aria-expanded': false
      // 'data-resize': $id
    });

    this.options.positionClass = this.getPositionClass();
    this.counter = 4;
    this.usedPositions = [];
    this.$element.attr({
      'aria-hidden': 'true',
      'data-yeti-box': $id,
      'data-resize': $id,
      'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor')
    });
    this._events();
  }

  /**
   * Helper function to determine current orientation of dropdown pane.
   * @function
   * @returns {String} position - string value of a position class.
   */
  getPositionClass() {
    var position = this.$element[0].className.match(/\b(top|left|right)\b/g);
        position = position ? position[0] : '';
    return position;
  }

  /**
   * Adjusts the dropdown panes orientation by adding/removing positioning classes.
   * @function
   * @private
   * @param {String} position - position class to remove.
   */
  _reposition(position) {
    this.usedPositions.push(position ? position : 'bottom');
    //default, try switching to opposite side
    if(!position && (this.usedPositions.indexOf('top') < 0)){
      this.$element.addClass('top');
    }else if(position === 'top' && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }else if(position === 'left' && (this.usedPositions.indexOf('right') < 0)){
      this.$element.removeClass(position)
          .addClass('right');
    }else if(position === 'right' && (this.usedPositions.indexOf('left') < 0)){
      this.$element.removeClass(position)
          .addClass('left');
    }

    //if default change didn't work, try bottom or left first
    else if(!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)){
      this.$element.addClass('left');
    }else if(position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)){
      this.$element.removeClass(position)
          .addClass('left');
    }else if(position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }else if(position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)){
      this.$element.removeClass(position);
    }
    //if nothing cleared, set to bottom
    else{
      this.$element.removeClass(position);
    }
    this.classChanged = true;
    this.counter--;
  }

  /**
   * Sets the position and orientation of the dropdown pane, checks for collisions.
   * Recursively calls itself if a collision is detected, with a new position class.
   * @function
   * @private
   */
  _setPosition() {
    if(this.$anchor.attr('aria-expanded') === 'false'){ return false; }
    var position = this.getPositionClass(),
        $eleDims = Foundation.Box.GetDimensions(this.$element),
        $anchorDims = Foundation.Box.GetDimensions(this.$anchor),
        _this = this,
        direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')),
        param = (direction === 'top') ? 'height' : 'width',
        offset = (param === 'height') ? this.options.vOffset : this.options.hOffset;

    if(($eleDims.width >= $eleDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.$element))){
      this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
        'width': $eleDims.windowDims.width - (this.options.hOffset * 2),
        'height': 'auto'
      });
      this.classChanged = true;
      return false;
    }

    this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset));

    while(!Foundation.Box.ImNotTouchingYou(this.$element) && this.counter){
      this._reposition(position);
      this._setPosition();
    }
  }

  /**
   * Adds event listeners to the element utilizing the triggers utility library.
   * @function
   * @private
   */
  _events() {
    var _this = this;
    this.$element.on({
      'open.zf.trigger': this.open.bind(this),
      'close.zf.trigger': this.close.bind(this),
      'toggle.zf.trigger': this.toggle.bind(this),
      'resizeme.zf.trigger': this._setPosition.bind(this)
    });

    if(this.options.hover){
      this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
          .on('mouseenter.zf.dropdown', function(){
            clearTimeout(_this.timeout);
            _this.timeout = setTimeout(function(){
              _this.open();
              _this.$anchor.data('hover', true);
            }, _this.options.hoverDelay);
          }).on('mouseleave.zf.dropdown', function(){
            clearTimeout(_this.timeout);
            _this.timeout = setTimeout(function(){
              _this.close();
              _this.$anchor.data('hover', false);
            }, _this.options.hoverDelay);
          });
      if(this.options.hoverPane){
        this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
            .on('mouseenter.zf.dropdown', function(){
              clearTimeout(_this.timeout);
            }).on('mouseleave.zf.dropdown', function(){
              clearTimeout(_this.timeout);
              _this.timeout = setTimeout(function(){
                _this.close();
                _this.$anchor.data('hover', false);
              }, _this.options.hoverDelay);
            });
      }
    }
    this.$anchor.add(this.$element).on('keydown.zf.dropdown', function(e) {

      var $target = $(this),
        visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element);

      Foundation.Keyboard.handleKey(e, 'Dropdown', {
        tab_forward: function() {
          if (_this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) { // left modal downwards, setting focus to first element
            if (_this.options.trapFocus) { // if focus shall be trapped
              visibleFocusableElements.eq(0).focus();
              e.preventDefault();
            } else { // if focus is not trapped, close dropdown on focus out
              _this.close();
            }
          }
        },
        tab_backward: function() {
          if (_this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || _this.$element.is(':focus')) { // left modal upwards, setting focus to last element
            if (_this.options.trapFocus) { // if focus shall be trapped
              visibleFocusableElements.eq(-1).focus();
              e.preventDefault();
            } else { // if focus is not trapped, close dropdown on focus out
              _this.close();
            }
          }
        },
        open: function() {
          if ($target.is(_this.$anchor)) {
            _this.open();
            _this.$element.attr('tabindex', -1).focus();
            e.preventDefault();
          }
        },
        close: function() {
          _this.close();
          _this.$anchor.focus();
        }
      });
    });
  }

  /**
   * Adds an event handler to the body to close any dropdowns on a click.
   * @function
   * @private
   */
  _addBodyHandler() {
     var $body = $(document.body).not(this.$element),
         _this = this;
     $body.off('click.zf.dropdown')
          .on('click.zf.dropdown', function(e){
            if(_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) {
              return;
            }
            if(_this.$element.find(e.target).length) {
              return;
            }
            _this.close();
            $body.off('click.zf.dropdown');
          });
  }

  /**
   * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
   * @function
   * @fires Dropdown#closeme
   * @fires Dropdown#show
   */
  open() {
    // var _this = this;
    /**
     * Fires to close other open dropdowns
     * @event Dropdown#closeme
     */
    this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
    this.$anchor.addClass('hover')
        .attr({'aria-expanded': true});
    // this.$element/*.show()*/;
    this._setPosition();
    this.$element.addClass('is-open')
        .attr({'aria-hidden': false});

    if(this.options.autoFocus){
      var $focusable = Foundation.Keyboard.findFocusable(this.$element);
      if($focusable.length){
        $focusable.eq(0).focus();
      }
    }

    if(this.options.closeOnClick){ this._addBodyHandler(); }

    /**
     * Fires once the dropdown is visible.
     * @event Dropdown#show
     */
     this.$element.trigger('show.zf.dropdown', [this.$element]);
    //why does this not work correctly for this plugin?
    // Foundation.reflow(this.$element, 'dropdown');
    // Foundation._reflow(this.$element.attr('data-dropdown'));
  }

  /**
   * Closes the open dropdown pane.
   * @function
   * @fires Dropdown#hide
   */
  close() {
    if(!this.$element.hasClass('is-open')){
      return false;
    }
    this.$element.removeClass('is-open')
        .attr({'aria-hidden': true});

    this.$anchor.removeClass('hover')
        .attr('aria-expanded', false);

    if(this.classChanged){
      var curPositionClass = this.getPositionClass();
      if(curPositionClass){
        this.$element.removeClass(curPositionClass);
      }
      this.$element.addClass(this.options.positionClass)
          /*.hide()*/.css({height: '', width: ''});
      this.classChanged = false;
      this.counter = 4;
      this.usedPositions.length = 0;
    }
    this.$element.trigger('hide.zf.dropdown', [this.$element]);
    // Foundation.reflow(this.$element, 'dropdown');
  }

  /**
   * Toggles the dropdown pane's visibility.
   * @function
   */
  toggle() {
    if(this.$element.hasClass('is-open')){
      if(this.$anchor.data('hover')) return;
      this.close();
    }else{
      this.open();
    }
  }

  /**
   * Destroys the dropdown.
   * @function
   */
  destroy() {
    this.$element.off('.zf.trigger').hide();
    this.$anchor.off('.zf.dropdown');

    Foundation.unregisterPlugin(this);
  }
}

Dropdown.defaults = {
  /**
   * Amount of time to delay opening a submenu on hover event.
   * @option
   * @example 250
   */
  hoverDelay: 250,
  /**
   * Allow submenus to open on hover events
   * @option
   * @example false
   */
  hover: false,
  /**
   * Don't close dropdown when hovering over dropdown pane
   * @option
   * @example true
   */
  hoverPane: false,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @example 1
   */
  vOffset: 1,
  /**
   * Number of pixels between the dropdown pane and the triggering element on open.
   * @option
   * @example 1
   */
  hOffset: 1,
  /**
   * Class applied to adjust open position. JS will test and fill this in.
   * @option
   * @example 'top'
   */
  positionClass: '',
  /**
   * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
   * @option
   * @example false
   */
  trapFocus: false,
  /**
   * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
   * @option
   * @example true
   */
  autoFocus: false,
  /**
   * Allows a click on the body to close the dropdown.
   * @option
   * @example false
   */
  closeOnClick: false
};

// Window exports
if (window.Foundation) {
  window.Foundation.plugin(Dropdown, 'Dropdown');
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015-2016 Actor LLC. <https://actor.im>
 */

import { forEach, map, throttle } from 'lodash';

import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import { Container } from 'flux/utils';
import Scrollbar from '../common/Scrollbar.react';

import CreateGroupActionCreators from '../../actions/CreateGroupActionCreators';
import ContactActionCreators from '../../actions/ContactActionCreators';
import GroupListActionCreators from '../../actions/GroupListActionCreators';
import AddContactActionCreators from '../../actions/AddContactActionCreators';

import AllDialogsStore from '../../stores/AllDialogsStore';

import RecentItem from './RecentItem.react';

class Recent extends Component {
  constructor(props) {
    super(props);
  }

  static contextTypes = {
    intl: PropTypes.object
  };

  static getStores = () => [AllDialogsStore];

  static calculateState() {
    return {
      allDialogs: AllDialogsStore.getAllDialogs()
    };
  }

  componentDidUpdate() {
    setTimeout(() => this.checkInvisibleCounters(), 1)
  }

  handleCreateGroup = () => CreateGroupActionCreators.open();

  handleCreatePrivate = () => AddContactActionCreators.open();

  handleGroupListClick = () => GroupListActionCreators.open();

  handlePrivateListClick = () => ContactActionCreators.open();

  handleRecentScroll = throttle((event) => {
    this.checkInvisibleCounters();
  }, 100, {trailing: true});

  checkInvisibleCounters = () => {
    const unreadNodes = document.getElementsByClassName('sidebar__list__item--unread');
    const scrollNode = findDOMNode(this.refs.container);
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    let haveUnreadAbove = false,
        haveUnreadBelow = false,
        lastUnreadBelow,
        firstUnreadAbove;

    forEach(unreadNodes, (node) => {
      const rect = node.getBoundingClientRect();
      if ((scrollNodeRect.top + scrollNodeRect.height) < rect.top) {
        haveUnreadBelow = true;
        lastUnreadBelow = node;
      }
      if (scrollNodeRect.top > (rect.top + rect.height)) {
        haveUnreadAbove = true;
        if (!firstUnreadAbove) {
          firstUnreadAbove = node;
        }
      }
    });

    this.setState({haveUnreadAbove, haveUnreadBelow, firstUnreadAbove, lastUnreadBelow})
  };

  scrollToFirstHiddenAbove = () => {
    const { firstUnreadAbove } = this.state;
    const rect = firstUnreadAbove.getBoundingClientRect();
    const scrollNode = findDOMNode(this.refs.container).getElementsByClassName('ss-content')[0];
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    this.refs.container.scrollTo(scrollNode.scrollTop + rect.top - scrollNodeRect.top)
  };

  scrollToLastHiddenBelow = () => {
    const { lastUnreadBelow } = this.state;
    const rect = lastUnreadBelow.getBoundingClientRect();
    const scrollNode = findDOMNode(this.refs.container).getElementsByClassName('ss-content')[0];
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    this.refs.container.scrollTo(scrollNode.scrollTop + rect.top - (scrollNodeRect.top + scrollNodeRect.height - rect.height));
  };

  render() {
    const { allDialogs, haveUnreadAbove, haveUnreadBelow } = this.state;
    const { intl } = this.context;

    const recentGroups = map(allDialogs, (dialogGroup, index) => {
      let groupTitle;
      switch (dialogGroup.key) {
        case 'groups':
          groupTitle = (

              {intl.messages[`sidebar.recents.${dialogGroup.key}`]}
              add_circle_outline

          );
          break;
        case 'privates':
          groupTitle = (

              {intl.messages[`sidebar.recents.${dialogGroup.key}`]}
              add_circle_outline

          );
          break;
        default:
          groupTitle =
{intl.messages[`sidebar.recents.${dialogGroup.key}`]}
;
      }

      const groupList = map(dialogGroup.shorts, (dialog, index) => <RecentItem dialog={dialog}
                                                                               key={index}
                                                                               type={dialogGroup.key}/>);

      return (
        <ul className={`sidebar__list sidebar__list--${dialogGroup.key}`} key={index}>
          {groupTitle}
          {groupList}

      )
    });

    return (

        {
          haveUnreadAbove
            ?

            keyboard_arrow_up

            : null
        }
        <Scrollbar ref="container" onScroll={this.handleRecentScroll}>

{recentGroups}

        </Scrollbar>
        {
          haveUnreadBelow
            ?

            keyboard_arrow_down

            : null
        }

    );
  }
}

export default Container.create(Recent, {pure: false});
<commit_msg>Update Recent.react.js<commit_after>/*
 * Copyright (C) 2015-2016 Actor LLC. <https://actor.im>
 */

import { forEach, map, throttle } from 'lodash';

import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import { Container } from 'flux/utils';
import Scrollbar from '../common/Scrollbar.react';

import CreateGroupActionCreators from '../../actions/CreateGroupActionCreators';
import ContactActionCreators from '../../actions/ContactActionCreators';
import GroupListActionCreators from '../../actions/GroupListActionCreators';
import AddContactActionCreators from '../../actions/AddContactActionCreators';

import AllDialogsStore from '../../stores/AllDialogsStore';

import RecentItem from './RecentItem.react';

class Recent extends Component {
  constructor(props) {
    super(props);
  }

  static contextTypes = {
    intl: PropTypes.object
  };

  static getStores = () => [AllDialogsStore];

  static calculateState() {
    return {
      allDialogs: AllDialogsStore.getAllDialogs()
    };
  }

  componentDidUpdate() {
    setTimeout(() => this.checkInvisibleCounters(), 500)
  }

  handleCreateGroup = () => CreateGroupActionCreators.open();

  handleCreatePrivate = () => AddContactActionCreators.open();

  handleGroupListClick = () => GroupListActionCreators.open();

  handlePrivateListClick = () => ContactActionCreators.open();

  handleRecentScroll = throttle((event) => {
    this.checkInvisibleCounters();
  }, 100, {trailing: true});

  checkInvisibleCounters = () => {
    const unreadNodes = document.getElementsByClassName('sidebar__list__item--unread');
    const scrollNode = findDOMNode(this.refs.container);
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    let haveUnreadAbove = false,
        haveUnreadBelow = false,
        lastUnreadBelow,
        firstUnreadAbove;

    forEach(unreadNodes, (node) => {
      const rect = node.getBoundingClientRect();
      if ((scrollNodeRect.top + scrollNodeRect.height) < rect.top) {
        haveUnreadBelow = true;
        lastUnreadBelow = node;
      }
      if (scrollNodeRect.top > (rect.top + rect.height)) {
        haveUnreadAbove = true;
        if (!firstUnreadAbove) {
          firstUnreadAbove = node;
        }
      }
    });

    this.setState({haveUnreadAbove, haveUnreadBelow, firstUnreadAbove, lastUnreadBelow})
  };

  scrollToFirstHiddenAbove = () => {
    const { firstUnreadAbove } = this.state;
    const rect = firstUnreadAbove.getBoundingClientRect();
    const scrollNode = findDOMNode(this.refs.container).getElementsByClassName('ss-content')[0];
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    this.refs.container.scrollTo(scrollNode.scrollTop + rect.top - scrollNodeRect.top)
  };

  scrollToLastHiddenBelow = () => {
    const { lastUnreadBelow } = this.state;
    const rect = lastUnreadBelow.getBoundingClientRect();
    const scrollNode = findDOMNode(this.refs.container).getElementsByClassName('ss-content')[0];
    const scrollNodeRect = scrollNode.getBoundingClientRect();

    this.refs.container.scrollTo(scrollNode.scrollTop + rect.top - (scrollNodeRect.top + scrollNodeRect.height - rect.height));
  };

  render() {
    const { allDialogs, haveUnreadAbove, haveUnreadBelow } = this.state;
    const { intl } = this.context;

    const recentGroups = map(allDialogs, (dialogGroup, index) => {
      let groupTitle;
      switch (dialogGroup.key) {
        case 'groups':
          groupTitle = (

              {intl.messages[`sidebar.recents.${dialogGroup.key}`]}
              add_circle_outline

          );
          break;
        case 'privates':
          groupTitle = (

              {intl.messages[`sidebar.recents.${dialogGroup.key}`]}
              add_circle_outline

          );
          break;
        default:
          groupTitle =
{intl.messages[`sidebar.recents.${dialogGroup.key}`]}
;
      }

      const groupList = map(dialogGroup.shorts, (dialog, index) => <RecentItem dialog={dialog}
                                                                               key={index}
                                                                               type={dialogGroup.key}/>);

      return (
        <ul className={`sidebar__list sidebar__list--${dialogGroup.key}`} key={index}>
          {groupTitle}
          {groupList}

      )
    });

    return (

        {
          haveUnreadAbove
            ?

            keyboard_arrow_up

            : null
        }
        <Scrollbar ref="container" onScroll={this.handleRecentScroll}>

{recentGroups}

        </Scrollbar>
        {
          haveUnreadBelow
            ?

            keyboard_arrow_down

            : null
        }

    );
  }
}

export default Container.create(Recent, {pure: false});
<|endoftext|>