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

/**
 * API for invoking Jalangi programatically from node.js code
 */

// places required functions on J$
require('./../Constants.js');
require('./../Config.js');
require('./../instrument/astUtil.js');
require('./../instrument/esnstrument.js');
// TODO making globals here is kind of gross, but esnstrument relies on it
acorn = require('acorn');
esotope = require('esotope');

var path = require('path');
var temp = require('temp');
var Q = require("q");
var instDir = require('./../commands/instrument');
var cp = require('child_process');

function getInstOutputFile(filePath) {
    if (filePath) {
        return path.resolve(filePath);
    } else {
        return temp.path({suffix: '.js'});
    }
}

/**
 * setup the global Config object based on the given instrumentation handler object
 * @param instHandler
 */
function setupConfig(instHandler) {
    var conf = J$.Config;
    conf.INSTR_READ = instHandler.instrRead;
    conf.INSTR_WRITE = instHandler.instrWrite;
    conf.INSTR_GETFIELD = instHandler.instrGetfield;
    conf.INSTR_PUTFIELD = instHandler.instrPutfield;
    conf.INSTR_BINARY = instHandler.instrBinary;
    conf.INSTR_PROPERTY_BINARY_ASSIGNMENT = instHandler.instrPropBinaryAssignment;
    conf.INSTR_UNARY = instHandler.instrUnary;
    conf.INSTR_LITERAL = instHandler.instrLiteral;
    conf.INSTR_CONDITIONAL = instHandler.instrConditional;
}

/**
 * clear any configured instrumentation control functions from the global Config object
 */
function clearConfig() {
    var conf = J$.Config;
    conf.INSTR_READ = null;
    conf.INSTR_WRITE = null;
    conf.INSTR_GETFIELD = null;
    conf.INSTR_PUTFIELD = null;
    conf.INSTR_BINARY = null;
    conf.INSTR_PROPERTY_BINARY_ASSIGNMENT = null;
    conf.INSTR_UNARY = null;
    conf.INSTR_LITERAL = null;
    conf.INSTR_CONDITIONAL = null;
}

/**
 * instruments a code string, returning an object with the following fields:
 * - code: the instrumented code string
 * - instAST: AST for the instrumented code
 * - sourceMapObject: mapping from IIDs to source info
 *
 * The options parameter can contain the following parameters:
 *
 * - inputFileName: this name will be associated
 * with the original code in the source map.
 * - outputFile: file name for instrumented code.  note that this
 * method does *not* write the file; this information is just for
 * the source map
 * - inlineSourceMap: should source map information be embedded in the
 * instrumented source code?
 * - inlineSource: should the original source code be embedded in the
 * instrument source code?
 * - instHandler: An instrumentation handler object, for controlling which
 * constructs get instrumented.  Possible properties are instrRead, instrWrite, instrGetfield,
 * instrPutfield, instrBinary, instrPropBinaryAssignment, instrUnary, instrLiteral, and instrConditional,
 * corresponding to the similarly-named properties documented in Config.js.
 * - astHandler: a function that takes the instrumented AST as a parameter and returns a JSON
 * object.  The instrumented code will store the result object in J$.ast_info, so it will be
 * available to analyses at the scriptEnter() callback.
 *
 * @param code
 */
function instrumentString(code, options) {
    if (!options) {
        options = {};
    }
    var outputFileName = getInstOutputFile(options.outputFile);
    var instCodeOptions = {
        code: code,
        origCodeFileName: options.inputFileName,
        instCodeFileName: outputFileName,
        inlineSourceMap: options.inlineSourceMap,
        inlineSource: options.inlineSource
    };
    if (options.instHandler) {
        setupConfig(options.instHandler);
    }
    var result = J$.instrumentCode(instCodeOptions);
    clearConfig();
    if (options.astHandler) {
        var info = options.astHandler(result.instAST);
        if (info) {
            result.code = J$.Constants.JALANGI_VAR + ".ast_info = " + JSON.stringify(info) + ";\n" + result.code;
        }
    }
    return result;
}

/**
 * instruments a directory.  see src/js/commands/instrument.js for details.
 * creates a temporary output directory if none specified.
 * @param options instrument options.  see src/js/commands/instrument.js
 * @return promise|Q.promise promise that gets resolved at the end of instrumentation
 */
function instrumentDir(options) {
    if (!options.outputDir) {
        options.outputDir = temp.mkdirSync();
    }
    if (options.instHandler) {
        setupConfig(options.instHandler);
    }
    var deferred = Q.defer();
    instDir.instrument(options, function (err) {
        clearConfig();
        if (err) {
            deferred.reject(err);
        } else {
            deferred.resolve({ outputDir: options.outputDir});
        }
    });
    return deferred.promise;
}

function convertToString(buffer_parts, bufferLength) {
    var buf = new Buffer(bufferLength);
    var pos = 0;
    for (var i = 0; i < buffer_parts.length; i++) {
        buffer_parts[i].copy(buf, pos, 0, buffer_parts[i].length);
        pos += buffer_parts[i].length;
    }
    return buf.toString();
}

/**
 * Runs a process created via the node child_process API and captures its output.
 *
 * @param forkedProcess the process
 * @returns {promise|Q.promise} A promise that, when process execution completes normally, is
 * resolved with an object with the following properties:
 *     'stdout': the stdout output of the process
 *     'stderr': the stderr output of the process
 *
 *     If process completes abnormally (with non-zero exit code), the promise is rejected
 *     with a value similar to above, except that 'exitCode' holds the exit code.
 *
 */
function runChildAndCaptureOutput(forkedProcess) {
    var stdout_parts = [], stdoutLength = 0, stderr_parts = [], stderrLength = 0,
        deferred = Q.defer();
    forkedProcess.stdout.on('data', function (data) {
        stdout_parts.push(data);
        stdoutLength += data.length;
    });
    forkedProcess.stderr.on('data', function (data) {
        stderr_parts.push(data);
        stderrLength += data.length;
    });
    forkedProcess.on('close', function (code) {
        var child_stdout = convertToString(stdout_parts, stdoutLength),
            child_stderr = convertToString(stderr_parts, stderrLength);
        var resultVal = {
            exitCode: code,
            stdout: child_stdout,
            stderr: child_stderr,
            toString: function () {
                return child_stderr;
            }
        };
        if (code !== 0) {
            deferred.reject(resultVal);
        } else {
            deferred.resolve(resultVal);
        }
    });
    return deferred.promise;

}

/**
 * direct analysis of an instrumented file using analysis2 engine
 * @param {string} script the instrumented script to analyze
 * @param {string[]} clientAnalyses the analyses to run
 * @param {object} [initParam] parameter to pass to client init() function
 * @return promise|Q.promise promise that gets resolved at the end of analysis.  The promise
 * is resolved with an object with properties:
 *     'exitCode': the exit code from the process doing replay
 *     'stdout': the stdout of the replay process
 *     'stderr': the stderr of the replay process
 *     'result': the result returned by the analysis, if any
 */
function analyze(script, clientAnalyses, initParam) {
    // TODO allow for configuration of whether --harmony flag is used
    var cliArgs = ['--harmony', path.resolve(__dirname, "../commands/direct.js")];
    if (!script) {
        throw new Error("must provide a script to analyze");
    }
    if (!clientAnalyses) {
        throw new Error("must provide an analysis to run");
    }
    clientAnalyses.forEach(function (analysis) {
        cliArgs.push('--analysis');
        cliArgs.push(analysis);
    });
    if (initParam) {
        Object.keys(initParam).forEach(function (k) {
            cliArgs.push('--initParam')
            cliArgs.push(k + ':' + initParam[k]);
        });
    }
    cliArgs.push(script);
    var proc = cp.spawn('node', cliArgs);
    return runChildAndCaptureOutput(proc);
}

exports.instrumentString = instrumentString;
exports.instrumentDir = instrumentDir;
exports.analyze = analyze;<commit_msg>better fix: use fork()<commit_after>/*
 * Copyright 2014 Samsung Information Systems America, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * API for invoking Jalangi programatically from node.js code
 */

// places required functions on J$
require('./../Constants.js');
require('./../Config.js');
require('./../instrument/astUtil.js');
require('./../instrument/esnstrument.js');
// TODO making globals here is kind of gross, but esnstrument relies on it
acorn = require('acorn');
esotope = require('esotope');

var path = require('path');
var temp = require('temp');
var Q = require("q");
var instDir = require('./../commands/instrument');
var cp = require('child_process');

function getInstOutputFile(filePath) {
    if (filePath) {
        return path.resolve(filePath);
    } else {
        return temp.path({suffix: '.js'});
    }
}

/**
 * setup the global Config object based on the given instrumentation handler object
 * @param instHandler
 */
function setupConfig(instHandler) {
    var conf = J$.Config;
    conf.INSTR_READ = instHandler.instrRead;
    conf.INSTR_WRITE = instHandler.instrWrite;
    conf.INSTR_GETFIELD = instHandler.instrGetfield;
    conf.INSTR_PUTFIELD = instHandler.instrPutfield;
    conf.INSTR_BINARY = instHandler.instrBinary;
    conf.INSTR_PROPERTY_BINARY_ASSIGNMENT = instHandler.instrPropBinaryAssignment;
    conf.INSTR_UNARY = instHandler.instrUnary;
    conf.INSTR_LITERAL = instHandler.instrLiteral;
    conf.INSTR_CONDITIONAL = instHandler.instrConditional;
}

/**
 * clear any configured instrumentation control functions from the global Config object
 */
function clearConfig() {
    var conf = J$.Config;
    conf.INSTR_READ = null;
    conf.INSTR_WRITE = null;
    conf.INSTR_GETFIELD = null;
    conf.INSTR_PUTFIELD = null;
    conf.INSTR_BINARY = null;
    conf.INSTR_PROPERTY_BINARY_ASSIGNMENT = null;
    conf.INSTR_UNARY = null;
    conf.INSTR_LITERAL = null;
    conf.INSTR_CONDITIONAL = null;
}

/**
 * instruments a code string, returning an object with the following fields:
 * - code: the instrumented code string
 * - instAST: AST for the instrumented code
 * - sourceMapObject: mapping from IIDs to source info
 *
 * The options parameter can contain the following parameters:
 *
 * - inputFileName: this name will be associated
 * with the original code in the source map.
 * - outputFile: file name for instrumented code.  note that this
 * method does *not* write the file; this information is just for
 * the source map
 * - inlineSourceMap: should source map information be embedded in the
 * instrumented source code?
 * - inlineSource: should the original source code be embedded in the
 * instrument source code?
 * - instHandler: An instrumentation handler object, for controlling which
 * constructs get instrumented.  Possible properties are instrRead, instrWrite, instrGetfield,
 * instrPutfield, instrBinary, instrPropBinaryAssignment, instrUnary, instrLiteral, and instrConditional,
 * corresponding to the similarly-named properties documented in Config.js.
 * - astHandler: a function that takes the instrumented AST as a parameter and returns a JSON
 * object.  The instrumented code will store the result object in J$.ast_info, so it will be
 * available to analyses at the scriptEnter() callback.
 *
 * @param code
 */
function instrumentString(code, options) {
    if (!options) {
        options = {};
    }
    var outputFileName = getInstOutputFile(options.outputFile);
    var instCodeOptions = {
        code: code,
        origCodeFileName: options.inputFileName,
        instCodeFileName: outputFileName,
        inlineSourceMap: options.inlineSourceMap,
        inlineSource: options.inlineSource
    };
    if (options.instHandler) {
        setupConfig(options.instHandler);
    }
    var result = J$.instrumentCode(instCodeOptions);
    clearConfig();
    if (options.astHandler) {
        var info = options.astHandler(result.instAST);
        if (info) {
            result.code = J$.Constants.JALANGI_VAR + ".ast_info = " + JSON.stringify(info) + ";\n" + result.code;
        }
    }
    return result;
}

/**
 * instruments a directory.  see src/js/commands/instrument.js for details.
 * creates a temporary output directory if none specified.
 * @param options instrument options.  see src/js/commands/instrument.js
 * @return promise|Q.promise promise that gets resolved at the end of instrumentation
 */
function instrumentDir(options) {
    if (!options.outputDir) {
        options.outputDir = temp.mkdirSync();
    }
    if (options.instHandler) {
        setupConfig(options.instHandler);
    }
    var deferred = Q.defer();
    instDir.instrument(options, function (err) {
        clearConfig();
        if (err) {
            deferred.reject(err);
        } else {
            deferred.resolve({ outputDir: options.outputDir});
        }
    });
    return deferred.promise;
}

function convertToString(buffer_parts, bufferLength) {
    var buf = new Buffer(bufferLength);
    var pos = 0;
    for (var i = 0; i < buffer_parts.length; i++) {
        buffer_parts[i].copy(buf, pos, 0, buffer_parts[i].length);
        pos += buffer_parts[i].length;
    }
    return buf.toString();
}

/**
 * Runs a process created via the node child_process API and captures its output.
 *
 * @param forkedProcess the process
 * @returns {promise|Q.promise} A promise that, when process execution completes normally, is
 * resolved with an object with the following properties:
 *     'stdout': the stdout output of the process
 *     'stderr': the stderr output of the process
 *
 *     If process completes abnormally (with non-zero exit code), the promise is rejected
 *     with a value similar to above, except that 'exitCode' holds the exit code.
 *
 */
function runChildAndCaptureOutput(forkedProcess) {
    var stdout_parts = [], stdoutLength = 0, stderr_parts = [], stderrLength = 0,
        deferred = Q.defer();
    forkedProcess.stdout.on('data', function (data) {
        stdout_parts.push(data);
        stdoutLength += data.length;
    });
    forkedProcess.stderr.on('data', function (data) {
        stderr_parts.push(data);
        stderrLength += data.length;
    });
    forkedProcess.on('close', function (code) {
        var child_stdout = convertToString(stdout_parts, stdoutLength),
            child_stderr = convertToString(stderr_parts, stderrLength);
        var resultVal = {
            exitCode: code,
            stdout: child_stdout,
            stderr: child_stderr,
            toString: function () {
                return child_stderr;
            }
        };
        if (code !== 0) {
            deferred.reject(resultVal);
        } else {
            deferred.resolve(resultVal);
        }
    });
    return deferred.promise;

}

/**
 * direct analysis of an instrumented file using analysis2 engine
 * @param {string} script the instrumented script to analyze
 * @param {string[]} clientAnalyses the analyses to run
 * @param {object} [initParam] parameter to pass to client init() function
 * @return promise|Q.promise promise that gets resolved at the end of analysis.  The promise
 * is resolved with an object with properties:
 *     'exitCode': the exit code from the process doing replay
 *     'stdout': the stdout of the replay process
 *     'stderr': the stderr of the replay process
 *     'result': the result returned by the analysis, if any
 */
function analyze(script, clientAnalyses, initParam) {
    var directJSScript = path.resolve(__dirname, "../commands/direct.js");
    var cliArgs = [];
    if (!script) {
        throw new Error("must provide a script to analyze");
    }
    if (!clientAnalyses) {
        throw new Error("must provide an analysis to run");
    }
    clientAnalyses.forEach(function (analysis) {
        cliArgs.push('--analysis');
        cliArgs.push(analysis);
    });
    if (initParam) {
        Object.keys(initParam).forEach(function (k) {
            cliArgs.push('--initParam')
            cliArgs.push(k + ':' + initParam[k]);
        });
    }
    cliArgs.push(script);
    var proc = cp.fork(directJSScript, cliArgs);
    return runChildAndCaptureOutput(proc);
}

exports.instrumentString = instrumentString;
exports.instrumentDir = instrumentDir;
exports.analyze = analyze;<|endoftext|>


<commit_before>/**
 *
 * BarrierChart
 *
 */

import React from 'react';
import PropTypes from 'prop-types';

import Flexbox from 'flexbox-react';
import GeometryCanvasUuid from 'components/GeometryCanvasUuid';

import ScatterPlot from './scatterPlot';

const styles = {
  width: 500,
  height: 420,
  padding: 30,
};

class BarrierChart extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      reactantUUIDs: [],
      productUUIDs: [],
    };
    this.clickThumbnail = this.clickThumbnail.bind(this);
  }
  componentDidMount() {
    const products = (JSON.parse(this.props.selectedReaction.products).filter((x) => !x.endsWith('gas')));
    const reactants = (JSON.parse(this.props.selectedReaction.reactants).filter((x) => !x.endsWith('gas')));
    const aseIds = (JSON.parse(this.props.selectedReaction.aseIds));
    const reactantUUIDs = [];
    const productUUIDs = [];
    products.map((x) => productUUIDs.push(aseIds[x]));
    reactants.map((x) => reactantUUIDs.push(aseIds[x]));

    this.setState({ // eslint-disable-line react/no-did-mount-set-state
      reactantUUIDs, // eslint-disable-line react/no-did-mount-set-state
      productUUIDs, // eslint-disable-line react/no-did-mount-set-state
    }); // eslint-disable-line react/no-did-mount-set-state
  }

  clickThumbnail = (uuid) => {
    console.log(uuid);
  }

  render() {
    return (

        <Flexbox flexDirection="row" justifyContent="space-around">
          <Flexbox
            flexDirection="column"
            style={{ marginRight: '0vw' }}
            justifyContent="space-around"
            onClick={() => this.clickThumbnail('reactants')}
          >
            {this.state.reactantUUIDs.map((uuid, i) =>
              <GeometryCanvasUuid
                onClick={(uuid_arg) => this.clickThumbnail(uuid_arg)}
                key={`reactant_${i}`}
                id={`reactant_${i}`}
                uuid={uuid}
                width={this.props.thumbnailSize}
                height={this.props.thumbnailSize}
                showDownload={false}
                showCompass={false}
                showLabels={false}
                x={1}
                y={1}
                borderWidth={0.0}
              />
            )}
          </Flexbox>
          <ScatterPlot {...this.state} {...styles} {...this.props} />
          <Flexbox
            flexDirection="column"
            style={{ marginLeft: '0vw' }}
            justifyContent="space-around"
            onClick={() => this.clickThumbnail('products')}
          >
            {this.state.productUUIDs.map((uuid, i) =>
              <GeometryCanvasUuid
                uuid={uuid}
                key={`product_${i}`}
                id={`product_${i}`}
                width={this.props.thumbnailSize}
                height={this.props.thumbnailSize}
                showDownload={false}
                showCompass={false}
                showLabels={false}
                x={1}
                y={1}
                borderWidth={0.0}
                onClick={() => this.clickThumbnail(uuid)}
              />
            )}
          </Flexbox>
        </Flexbox>

    );
  }
}

BarrierChart.propTypes = {
  selectedReaction: PropTypes.object,
// The number of data points for the chart.
  thumbnailSize: PropTypes.number,
};

BarrierChart.defaultProps = {
  thumbnailSize: 200,
};

export default BarrierChart;
<commit_msg>Use camel-case<commit_after>/**
 *
 * BarrierChart
 *
 */

import React from 'react';
import PropTypes from 'prop-types';

import Flexbox from 'flexbox-react';
import GeometryCanvasUuid from 'components/GeometryCanvasUuid';

import ScatterPlot from './scatterPlot';

const styles = {
  width: 500,
  height: 420,
  padding: 30,
};

class BarrierChart extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      reactantUUIDs: [],
      productUUIDs: [],
    };
    this.clickThumbnail = this.clickThumbnail.bind(this);
  }
  componentDidMount() {
    const products = (JSON.parse(this.props.selectedReaction.products).filter((x) => !x.endsWith('gas')));
    const reactants = (JSON.parse(this.props.selectedReaction.reactants).filter((x) => !x.endsWith('gas')));
    const aseIds = (JSON.parse(this.props.selectedReaction.aseIds));
    const reactantUUIDs = [];
    const productUUIDs = [];
    products.map((x) => productUUIDs.push(aseIds[x]));
    reactants.map((x) => reactantUUIDs.push(aseIds[x]));

    this.setState({ // eslint-disable-line react/no-did-mount-set-state
      reactantUUIDs, // eslint-disable-line react/no-did-mount-set-state
      productUUIDs, // eslint-disable-line react/no-did-mount-set-state
    }); // eslint-disable-line react/no-did-mount-set-state
  }

  clickThumbnail = (uuid) => {
    console.log(uuid);
  }

  render() {
    return (

        <Flexbox flexDirection="row" justifyContent="space-around">
          <Flexbox
            flexDirection="column"
            style={{ marginRight: '0vw' }}
            justifyContent="space-around"
            onClick={() => this.clickThumbnail('reactants')}
          >
            {this.state.reactantUUIDs.map((uuid, i) =>
              <GeometryCanvasUuid
                onClick={(uuidArg) => this.clickThumbnail(uuidArg)}
                key={`reactant_${i}`}
                id={`reactant_${i}`}
                uuid={uuid}
                width={this.props.thumbnailSize}
                height={this.props.thumbnailSize}
                showDownload={false}
                showCompass={false}
                showLabels={false}
                x={1}
                y={1}
                borderWidth={0.0}
              />
            )}
          </Flexbox>
          <ScatterPlot {...this.state} {...styles} {...this.props} />
          <Flexbox
            flexDirection="column"
            style={{ marginLeft: '0vw' }}
            justifyContent="space-around"
            onClick={() => this.clickThumbnail('products')}
          >
            {this.state.productUUIDs.map((uuid, i) =>
              <GeometryCanvasUuid
                uuid={uuid}
                key={`product_${i}`}
                id={`product_${i}`}
                width={this.props.thumbnailSize}
                height={this.props.thumbnailSize}
                showDownload={false}
                showCompass={false}
                showLabels={false}
                x={1}
                y={1}
                borderWidth={0.0}
                onClick={() => this.clickThumbnail(uuid)}
              />
            )}
          </Flexbox>
        </Flexbox>

    );
  }
}

BarrierChart.propTypes = {
  selectedReaction: PropTypes.object,
// The number of data points for the chart.
  thumbnailSize: PropTypes.number,
};

BarrierChart.defaultProps = {
  thumbnailSize: 200,
};

export default BarrierChart;
<|endoftext|>


<commit_before>/*jslint nomen: true */
/*globals define: true */

define([
  // Libraries
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/leaflet/leaflet.tilejson',
  'moment',

  // LocalData
  'settings',
  'util',
  'api',

  // Templates
  'text!templates/responses/map.html'
],

function($, _, Backbone, L, moment, settings, util, api, template) {
  'use strict';

  function flip(a) {
    return [a[1], a[0]];
  }

  function zoneStyle(feature) {
    return {
      color: feature.properties.color,
      opacity: 0.8,
      fillOpacity: 0.0,
      weight: 3
    };
  }

  var MapView = Backbone.View.extend({
    BASEMAP_URL: 'http://a.tiles.mapbox.com/v3/matth.map-n9bps30s/{z}/{x}/{y}.png',

    filtered: false,
    filter: null,
    gridLayer: null,
    map: null,
    markers: {},
    responses: null,
    selectedLayer: null,
    survey: null,
    template: _.template(template),

    initialize: function(options) {
      L.Icon.Default.imagePath = '/js/lib/leaflet/images';
      _.bindAll(this,
        'render',
        'controlUTFZoom',
        'update',
        'fitBounds',
        'selectObject',
        'deselectObject',
        'addTileLayer',
        'pdf'
      );

      this.survey = options.survey;
      this.clickHandler = options.clickHandler;

      this.parcelIdsOnTheMap = {};
      this.objectsOnTheMap = new L.FeatureGroup();
      this.zoneLayer = new L.FeatureGroup();

      this.defaultStyle = settings.farZoomStyle;
      this.defaultPointToLayer = function (feature, latlng) {
        return L.circleMarker(latlng, settings.circleMarker);
      };

      this.delayFitBounds = _.debounce(this.fitBounds, 250);

      // Listen for new responses
      this.listenTo(this.survey, 'change', this.update);
      this.render();
    },

    /**
     * Hide or show the UTF Grid based on zoom level.
     */
    controlUTFZoom: function() {
      var zoom = this.map.getZoom();
      var hasGridLayer = this.map.hasLayer(this.gridLayer);

      if (zoom < settings.MIN_GRID_ZOOM && hasGridLayer) {
        this.map.removeLayer(this.gridLayer);
      }

      if (zoom >= settings.MIN_GRID_ZOOM && !hasGridLayer) {
        this.map.addLayer(this.gridLayer);
      }

      util.track('survey.map.zoom', {
        zoom: zoom
      });
    },

    /**
     * Given tilejson data, add the tiles and the UTFgrid
     * @param  {Object} tilejson
     */
    addTileLayer: function(tilejson) {
      if (this.tileLayer) {
        this.map.removeLayer(this.tileLayer);
      }
      this.tileLayer = new L.TileJSON.createTileLayer(tilejson);

      // Listen to see if we're loading the map
      this.tileLayer.on('loading', this.loading);
      this.tileLayer.on('load', this.done);

      this.map.addLayer(this.tileLayer);

      // Create the grid layer & handle clicks
      if (this.gridLayer) {
        this.map.removeLayer(this.gridLayer);
      }
      this.gridLayer = new L.UtfGrid(tilejson.grids[0], {
        resolution: 4
      });

      // Make sure the grid layer is on top.
      if (this.map.getZoom() >= settings.MIN_GRID_ZOOM) {
        this.map.addLayer(this.gridLayer);
      }

      if (this.clickHandler) {
        this.gridLayer.on('click', this.clickHandler);
      }
    },

    loading: function() {
      $('.map-loading').show();
    },

    done: function() {
      $('.map-loading').hide();
    },

    fitBounds: function() {
      var bounds = this.survey.get('responseBounds');
      if (bounds) {
        bounds = [flip(bounds[0]), flip(bounds[1])];
        if (bounds[0][0] === bounds[1][0] || bounds[0][1] === bounds[1][1]) {
          this.map.setView(bounds[0], 15);
        } else {
          this.map.fitBounds(bounds, {
            reset: true,
            maxZoom: 18
          });
        }

        return;
      }

      // If there are no bounds, geocode the survey location and zoom there.
      this.survey.getLocation(function(error, results) {
        this.map.setView(results.coords, settings.TYPICAL_CITYWIDE_ZOOM);
      }.bind(this));
    },

    /**
     * Updates the map.
     * Triggered when we know there are changes to the data.
     * Usually, we'd use Leaflet's redraw() function rather than removing
     * and re-adding layers. In this case, we can't do that, because gridLayer
     * doesn't have redraw().
     */
    update: function() {
      if(this.tileLayer) {
        this.map.removeLayer(this.tileLayer);
      }
      if(this.gridLayer) {
        this.map.removeLayer(this.gridLayer);
      }
      this.selectDataMap();
    },

    // Use Lascaux to create a PDF of the focused map area at the current zoom.
    pdf: function(event) {
      event.preventDefault();

      // Workaround: Lascaux doesn't handle protocol-relative URLs correctly
      var tileURL = this.tileLayer._url;
      if (_.startsWith(tileURL, '//')) {
        tileURL = 'https:' + tileURL;
      }

      var options = {
        overlay_tiles: tileURL,
        base_tiles: settings.printLayer,
        zoom: this.map.getZoom(),
        dimensions: '8.5,11', // default unit is inches; tiles are 150dpi
        center: this.map.getCenter().lng + ',' + this.map.getCenter().lat,
        output_format: 'pdf'
      };

      var url = settings.api.lascaux + '?' + $.param(options);
      window.open(url);
    },

    render: function() {
      if (this.map === null) {
        // Initialize the map
        this.map = new L.map('map', {
          zoom: 15,
          center: [37.77585785035733, -122.413628113516],
          zoomControl: false
        });

        this.map.addControl(L.control.zoom({ position: 'topright' }));

        // Add a control to print the map
        var PrintControl = L.Control.extend({
            options: {
                position: 'topright'
            },

            onAdd: function (map) {
                var container = L.DomUtil.create('div', 'localdata-control');

                $(container).html('PDF');
                $(container).on('click', this.pdf);

                return container;
            }.bind({ pdf: this.pdf })
        });
        this.map.addControl(new PrintControl());

        // Set up the base maps
        this.baseLayer = L.tileLayer(settings.baseLayer);
        this.satelliteLayer = L.tileLayer(settings.satelliteLayer);
        this.printLayer = L.tileLayer(settings.printLayer);
        this.map.addLayer(this.baseLayer);
        var baseMaps = {
          "Streets": this.baseLayer,
          "Satellite": this.satelliteLayer,
          "Print": this.printLayer
        };

        // Make sure we don't show the UTF grid too far out.
        this.map.on('zoomend', this.controlUTFZoom);

        // Add the layer control
        // Make sure the layer stays in the background
        L.control.layers(baseMaps).addTo(this.map);
        this.baseLayer.bringToBack();
        this.map.on('baselayerchange', function(event) {
          event.layer.bringToBack();
        });

        // FIXME: This is a hack. The map element doesn't have a size yet, so
        // Leaflet doesn't know how to set the view properly. If we wait until
        // the next tick and call invalidateSize, then the map will know its
        // size.
        setTimeout(function () {
          this.map.addLayer(this.zoneLayer);

          // Center the map
          this.fitBounds();
        }.bind(this), 0);

        // Handle zones
        if (this.survey.has('zones')) {
          this.plotZones();
        }

        this.listenTo(this.survey, 'change', function () {
          if (this.survey.hasChanged('zones')) {
            this.plotZones();
          }
        });

        this.selectDataMap();
      }

      return this;
    },

    /**
     * Get the appropriate survey tiles.
     */
    selectDataMap: function () {
      // Build the appropriate TileJSON URL.
      var url = '/tiles/' + this.survey.get('id');
      if (this.filter) {
        if(this.filter.question) {
          url = url + '/filter/' + this.filter.question;
        }
        if(this.filter.answer) {
          url = url + '/' + this.filter.answer;
        }
      }
      url = url + '/tile.json';

      // Get TileJSON
      $.ajax({
        url: url,
        dataType: 'json',
        data: this.mapOptions,
        cache: false
      }).done(this.addTileLayer)
      .fail(function(jqXHR, textStatus, errorThrown) {
        console.log("Error fetching tilejson", jqXHR, textStatus, errorThrown);
      });
    },

    /**
     * Set filter parameters for displaying results on the map
     * The response collection will activate this if we have an active filter
     * @param {String} question Name of the question, eg 'vacant'
     * @param {Object} answers  Possible answers to the question
     */
    setFilter: function (question, answer) {
      this.filter = {
        question: question,
        answer: answer
      };
      this.map.invalidateSize();
      this.selectDataMap();
    },

    setOptions: function(options) {
      this.mapOptions = options;
      this.map.invalidateSize();
      this.selectDataMap();
    },

    clearFilter: function () {
      this.filter = null;
      this.selectDataMap();
    },

    /**
     * Plot survey zones on the map
     */
    plotZones: function () {
      if (!this.survey.has('zones')) {
        return;
      }

      var zones = this.survey.get('zones');
      this.zoneLayer.clearLayers();
      this.zoneLayer.addLayer(new L.geoJson(zones, {
        style: zoneStyle,
        clickable: false
      }));

      // This is a leaflet workaround. Leaflet doesn't allow tiles on top of objects
      // When there are zones drawn on the map, they are on top, which means the
      // grid layer can't be clicked.
      // Hopefully will be fixed in 0.8 or 1.0
      this.zoneLayer.on('click', function(e) {
        this.gridLayer._click(e);
      }.bind(this));

      this.delayFitBounds();
      this.objectsOnTheMap.bringToFront();
    },

    /**
     * Highlight a selected object
     * @param  {Object} event
     */
    selectObject: function(feature) {
      this.deselectObject();

      // Add a layer with a visual selection
      this.selectedLayer = new L.GeoJSON(feature, {
        pointToLayer: this.defaultPointToLayer,
        style: settings.selectedStyle
      });

      this.map.addLayer(this.selectedLayer);
      this.selectedLayer.bringToFront();
    },

    /**
     * Un-highlight any previously selected object
     */
    deselectObject: function() {
      if (this.selectedLayer !== null) {
        this.map.removeLayer(this.selectedLayer);
        delete this.selectedLayer;
      }
    },

    goToLatLng: function (latlng) {
      // Remove any existing location marker
      if (this.markers.location !== undefined) {
        this.map.removeLayer(this.markers.location);
      }

      this.map.setView(latlng, 18);
      var marker = L.marker(latlng).addTo(this.map);
      this.markers.location = marker;
    }
  });

  return MapView;
});
<commit_msg>Allow standard survey maps to use multiple tile/grid paths<commit_after>/*jslint nomen: true */
/*globals define: true */

define([
  // Libraries
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/leaflet/leaflet.tilejson',
  'moment',

  // LocalData
  'settings',
  'util',
  'api',

  // Templates
  'text!templates/responses/map.html'
],

function($, _, Backbone, L, moment, settings, util, api, template) {
  'use strict';

  function flip(a) {
    return [a[1], a[0]];
  }

  function zoneStyle(feature) {
    return {
      color: feature.properties.color,
      opacity: 0.8,
      fillOpacity: 0.0,
      weight: 3
    };
  }

  var MapView = Backbone.View.extend({
    BASEMAP_URL: 'http://a.tiles.mapbox.com/v3/matth.map-n9bps30s/{z}/{x}/{y}.png',

    filtered: false,
    filter: null,
    gridLayer: null,
    map: null,
    markers: {},
    responses: null,
    selectedLayer: null,
    survey: null,
    template: _.template(template),

    initialize: function(options) {
      L.Icon.Default.imagePath = '/js/lib/leaflet/images';
      _.bindAll(this,
        'render',
        'controlUTFZoom',
        'update',
        'fitBounds',
        'selectObject',
        'deselectObject',
        'addTileLayer',
        'pdf'
      );

      this.survey = options.survey;
      this.clickHandler = options.clickHandler;

      this.parcelIdsOnTheMap = {};
      this.objectsOnTheMap = new L.FeatureGroup();
      this.zoneLayer = new L.FeatureGroup();

      this.defaultStyle = settings.farZoomStyle;
      this.defaultPointToLayer = function (feature, latlng) {
        return L.circleMarker(latlng, settings.circleMarker);
      };

      this.delayFitBounds = _.debounce(this.fitBounds, 250);

      // Listen for new responses
      this.listenTo(this.survey, 'change', this.update);
      this.render();
    },

    /**
     * Hide or show the UTF Grid based on zoom level.
     */
    controlUTFZoom: function() {
      var zoom = this.map.getZoom();
      var hasGridLayer = this.map.hasLayer(this.gridLayer);

      if (zoom < settings.MIN_GRID_ZOOM && hasGridLayer) {
        this.map.removeLayer(this.gridLayer);
      }

      if (zoom >= settings.MIN_GRID_ZOOM && !hasGridLayer) {
        this.map.addLayer(this.gridLayer);
      }

      util.track('survey.map.zoom', {
        zoom: zoom
      });
    },

    /**
     * Given tilejson data, add the tiles and the UTFgrid
     * @param  {Object} tilejson
     */
    addTileLayer: function(tilejson) {
      if (this.tileLayer) {
        this.map.removeLayer(this.tileLayer);
      }

      var tileConfig = util.templatizeURLs(tilejson.tiles);
      this.tileLayer = L.tileLayer(tileConfig.template, {
        subdomains: tileConfig.subs
      });

      // Listen to see if we're loading the map
      this.tileLayer.on('loading', this.loading);
      this.tileLayer.on('load', this.done);

      this.map.addLayer(this.tileLayer);

      // Create the grid layer & handle clicks
      if (this.gridLayer) {
        this.map.removeLayer(this.gridLayer);
      }
      var gridConfig = util.templatizeURLs(tilejson.grids);
      this.gridLayer = new L.UtfGrid(gridConfig.template, {
        resolution: 4,
        subdomains: gridConfig.subs
      });

      // Make sure the grid layer is on top.
      if (this.map.getZoom() >= settings.MIN_GRID_ZOOM) {
        this.map.addLayer(this.gridLayer);
      }

      if (this.clickHandler) {
        this.gridLayer.on('click', this.clickHandler);
      }
    },

    loading: function() {
      $('.map-loading').show();
    },

    done: function() {
      $('.map-loading').hide();
    },

    fitBounds: function() {
      var bounds = this.survey.get('responseBounds');
      if (bounds) {
        bounds = [flip(bounds[0]), flip(bounds[1])];
        if (bounds[0][0] === bounds[1][0] || bounds[0][1] === bounds[1][1]) {
          this.map.setView(bounds[0], 15);
        } else {
          this.map.fitBounds(bounds, {
            reset: true,
            maxZoom: 18
          });
        }

        return;
      }

      // If there are no bounds, geocode the survey location and zoom there.
      this.survey.getLocation(function(error, results) {
        this.map.setView(results.coords, settings.TYPICAL_CITYWIDE_ZOOM);
      }.bind(this));
    },

    /**
     * Updates the map.
     * Triggered when we know there are changes to the data.
     * Usually, we'd use Leaflet's redraw() function rather than removing
     * and re-adding layers. In this case, we can't do that, because gridLayer
     * doesn't have redraw().
     */
    update: function() {
      if(this.tileLayer) {
        this.map.removeLayer(this.tileLayer);
      }
      if(this.gridLayer) {
        this.map.removeLayer(this.gridLayer);
      }
      this.selectDataMap();
    },

    // Use Lascaux to create a PDF of the focused map area at the current zoom.
    pdf: function(event) {
      event.preventDefault();

      // Workaround: Lascaux doesn't handle protocol-relative URLs correctly
      var tileURL = this.tileLayer._url;
      if (_.startsWith(tileURL, '//')) {
        tileURL = 'https:' + tileURL;
      }

      var options = {
        overlay_tiles: tileURL,
        base_tiles: settings.printLayer,
        zoom: this.map.getZoom(),
        dimensions: '8.5,11', // default unit is inches; tiles are 150dpi
        center: this.map.getCenter().lng + ',' + this.map.getCenter().lat,
        output_format: 'pdf'
      };

      var url = settings.api.lascaux + '?' + $.param(options);
      window.open(url);
    },

    render: function() {
      if (this.map === null) {
        // Initialize the map
        this.map = new L.map('map', {
          zoom: 15,
          center: [37.77585785035733, -122.413628113516],
          zoomControl: false
        });

        this.map.addControl(L.control.zoom({ position: 'topright' }));

        // Add a control to print the map
        var PrintControl = L.Control.extend({
            options: {
                position: 'topright'
            },

            onAdd: function (map) {
                var container = L.DomUtil.create('div', 'localdata-control');

                $(container).html('PDF');
                $(container).on('click', this.pdf);

                return container;
            }.bind({ pdf: this.pdf })
        });
        this.map.addControl(new PrintControl());

        // Set up the base maps
        this.baseLayer = L.tileLayer(settings.baseLayer);
        this.satelliteLayer = L.tileLayer(settings.satelliteLayer);
        this.printLayer = L.tileLayer(settings.printLayer);
        this.map.addLayer(this.baseLayer);
        var baseMaps = {
          "Streets": this.baseLayer,
          "Satellite": this.satelliteLayer,
          "Print": this.printLayer
        };

        // Make sure we don't show the UTF grid too far out.
        this.map.on('zoomend', this.controlUTFZoom);

        // Add the layer control
        // Make sure the layer stays in the background
        L.control.layers(baseMaps).addTo(this.map);
        this.baseLayer.bringToBack();
        this.map.on('baselayerchange', function(event) {
          event.layer.bringToBack();
        });

        // FIXME: This is a hack. The map element doesn't have a size yet, so
        // Leaflet doesn't know how to set the view properly. If we wait until
        // the next tick and call invalidateSize, then the map will know its
        // size.
        setTimeout(function () {
          this.map.addLayer(this.zoneLayer);

          // Center the map
          this.fitBounds();
        }.bind(this), 0);

        // Handle zones
        if (this.survey.has('zones')) {
          this.plotZones();
        }

        this.listenTo(this.survey, 'change', function () {
          if (this.survey.hasChanged('zones')) {
            this.plotZones();
          }
        });

        this.selectDataMap();
      }

      return this;
    },

    /**
     * Get the appropriate survey tiles.
     */
    selectDataMap: function () {
      // Build the appropriate TileJSON URL.
      var url = '/tiles/' + this.survey.get('id');
      if (this.filter) {
        if(this.filter.question) {
          url = url + '/filter/' + this.filter.question;
        }
        if(this.filter.answer) {
          url = url + '/' + this.filter.answer;
        }
      }
      url = url + '/tile.json';

      // Get TileJSON
      $.ajax({
        url: url,
        dataType: 'json',
        data: this.mapOptions,
        cache: false
      }).done(this.addTileLayer)
      .fail(function(jqXHR, textStatus, errorThrown) {
        console.log("Error fetching tilejson", jqXHR, textStatus, errorThrown);
      });
    },

    /**
     * Set filter parameters for displaying results on the map
     * The response collection will activate this if we have an active filter
     * @param {String} question Name of the question, eg 'vacant'
     * @param {Object} answers  Possible answers to the question
     */
    setFilter: function (question, answer) {
      this.filter = {
        question: question,
        answer: answer
      };
      this.map.invalidateSize();
      this.selectDataMap();
    },

    setOptions: function(options) {
      this.mapOptions = options;
      this.map.invalidateSize();
      this.selectDataMap();
    },

    clearFilter: function () {
      this.filter = null;
      this.selectDataMap();
    },

    /**
     * Plot survey zones on the map
     */
    plotZones: function () {
      if (!this.survey.has('zones')) {
        return;
      }

      var zones = this.survey.get('zones');
      this.zoneLayer.clearLayers();
      this.zoneLayer.addLayer(new L.geoJson(zones, {
        style: zoneStyle,
        clickable: false
      }));

      // This is a leaflet workaround. Leaflet doesn't allow tiles on top of objects
      // When there are zones drawn on the map, they are on top, which means the
      // grid layer can't be clicked.
      // Hopefully will be fixed in 0.8 or 1.0
      this.zoneLayer.on('click', function(e) {
        this.gridLayer._click(e);
      }.bind(this));

      this.delayFitBounds();
      this.objectsOnTheMap.bringToFront();
    },

    /**
     * Highlight a selected object
     * @param  {Object} event
     */
    selectObject: function(feature) {
      this.deselectObject();

      // Add a layer with a visual selection
      this.selectedLayer = new L.GeoJSON(feature, {
        pointToLayer: this.defaultPointToLayer,
        style: settings.selectedStyle
      });

      this.map.addLayer(this.selectedLayer);
      this.selectedLayer.bringToFront();
    },

    /**
     * Un-highlight any previously selected object
     */
    deselectObject: function() {
      if (this.selectedLayer !== null) {
        this.map.removeLayer(this.selectedLayer);
        delete this.selectedLayer;
      }
    },

    goToLatLng: function (latlng) {
      // Remove any existing location marker
      if (this.markers.location !== undefined) {
        this.map.removeLayer(this.markers.location);
      }

      this.map.setView(latlng, 18);
      var marker = L.marker(latlng).addTo(this.map);
      this.markers.location = marker;
    }
  });

  return MapView;
});
<|endoftext|>


<commit_before>// @flow
import React, { Component } from 'react';
import electron from 'electron';
import $ from 'jquery';
// import PropTypes from 'prop-types';
//  import { Link } from 'react-router-dom';
//  import styles from './LoginForm.css';

const app = electron.remote;
const dialog = app.dialog;

class SelectWbtStatement extends Component {
  state: {
    metaDataFolder: string,
    metaDataFolderSelected: boolean,
    jobFilepath: string,
    jobFilepathSelected: boolean
  }
  constructor() {
    super();
    this.state = {
      metaDataFolder: 'heyhey',
      metaDataFolderSelected: false,
      jobFilepath: 'jobFilepath',
      jobFilepathSelected: false
    };
  }
  selectJobSpecsFile = () => {
    console.log('called selectJobSpecsFile');
    const fileNames = dialog.showOpenDialog();
    if (fileNames === undefined) {
      console.log('No file selected');
      this.setState({ jobFilepathSelected: false });
      this.setState({ jobFilepath: '' });
    } else {
      // console.log('going to set the filename and boolean' + fileNames);
      this.setState({ jobFilepathSelected: true });
      this.setState({ jobFilepath: fileNames[0] });
    }
    // NOTE put this back in when the first part is working
    // persistData('jobFilePersistKey', fileNames[0]);
    console.log('end of selectJobSpecsFile');
  }
  selectMetaDataFile = () => {
    console.log('called selectMetaDataFile');
    const path = dialog.showOpenDialog(
      { title: 'Select a folder', properties: ['openDirectory'] }
    );
    console.log('called showOpenDialog and returned');
    console.log('path is =====>' + path);
    if (path === undefined) {
      console.log('No destination folder selected');
      this.setState({ metaDataFolderSelected: false });
      this.setState({ metaDataFolder: '' });
    } else {
      console.log('path returned from dialog.showOpenDialog select a folder is....> ' + path);
      // console.log('path[0] is....> ' + path[0]);
      this.setState({ metaDataFolderSelected: true });
      this.setState({ metaDataFolder: path[0] });
    }
    // NOTE put this back in when the first part is working
    // persistData('metaDataPersistKey', path[0]);
    console.log('end of selectMetaDataFile');
  }
  render() {
    /*
    if (this.state.jobFilepathSelected && this.state.metaDataFolderSelected) {
      $('#sendButton').removeAttr('disabled');
    } else {
      $('#sendButton').attr('disabled', 'true');
    }
    */
    let itemButton;
    if (this.state.jobFilepathSelected && this.state.metaDataFolderSelected) {
      itemButton = <button type="submit" id="sendButton" className="btn btn-primary pull-left">Send</button>
    } else {
      itemButton = <button disabled="true" type="submit" id="sendButton" className="btn btn-primary pull-left">Send</button>
    }
    return (

Job

          <form className="form" onSubmit={this.localHandleSend}>

Job

              Metadata File

{this.state.metaDataFolder}

                  <button
                    type="button"
                    className="btn btn-primary"
                    onClick={this.selectMetaDataFile}
                  >Select metadata.xml</button>

              Job Spec

{this.state.jobFilepath}

                  <button
                    type="button"
                    className="btn btn-primary"
                    onClick={this.selectJobSpecsFile}
                  >Select job specification</button>

                {itemButton}

          </form>

    );
  }
}

export default SelectWbtStatement;
<commit_msg>make changes for selection of WBT statement file html<commit_after>// @flow
import React, { Component } from 'react';
import electron from 'electron';
import $ from 'jquery';
// import PropTypes from 'prop-types';
//  import { Link } from 'react-router-dom';
//  import styles from './LoginForm.css';

const app = electron.remote;
const dialog = app.dialog;

class SelectWbtStatement extends Component {
  state: {
    wbtStatementFile: string,
  }
  constructor() {
    super();
    this.state = {
      wbtStatementFile: 'wbtStatementFile',
    };
  }
  selectWbtStatementFile = () => {
    console.log('called selectWbtStatementFile');
    const fileNames = dialog.showOpenDialog();
    if (fileNames === undefined) {
      console.log('No file selected');
      this.setState({ wbtStatementFile: '' });
    } else {
      // console.log('going to set the filename and boolean' + fileNames);
      this.setState({ wbtStatementFile: fileNames[0] });
    }
    // NOTE put this back in when the first part is working
    // persistData('jobFilePersistKey', fileNames[0]);
    console.log('end of selectWbtStatementFile');
  }
  render() {
    /*
    if (this.state.jobFilepathSelected && this.state.metaDataFolderSelected) {
      $('#sendButton').removeAttr('disabled');
    } else {
      $('#sendButton').attr('disabled', 'true');
    }
    */
    return (

Select WBT Statement

          <form className="form" onSubmit={this.localHandleSend}>

              WBT Statement

{this.state.wbtStatementFile}

                  <button
                    type="button"
                    className="btn btn-primary"
                    onClick={this.selectWbtStatementFile}
                  >Select WBT Statement html file</button>

          </form>

    );
  }
}

export default SelectWbtStatement;
<|endoftext|>


<commit_before>define(function(require, exports, module){
    var Dnd = null ;

    var $ = require('$'),
        Base = require('base') ;

    var uid = 0, // 标识dnd instance的id
        dndArray = [], // 存储dnd instance的数组
        draggingPre = false, // 标识预拖放
        dragging = null, // 当前拖放的代理元素
        dropping = null, // 当前拖放的目标元素
        diffX = 0,
        diffY = 0, // diffX, diffY记录鼠标点击离源节点的距离
        obj = null, // 存储当前拖放的dnd
        dataTransfer = {} ; // 存储拖放信息, 在dragstart可设置,在drop中可读取

    Dnd = Base.extend({
        attrs: {
            element: {
                value: null,
                readOnly: true,
                getter: function(val){
                    return $(val).eq(0) ;
                }
            },
            containment: {
                value: document,
                getter: function(val){
                    return $(val).eq(0) ;
                }
            },
            proxy: {
                value: null,
                getter: function(val){
                    if(val === null){
                        return null ;
                    } else{
                        return $(val).eq(0) ;
                    }
                },
                setter: function(val){
                    if(val === null){
                        return this.get('element').clone() ;
                    } else{
                        return val ;
                    }
                }
            },
            drop: {
                value: null,
                getter: function(val){
                    if(val === null){
                        return null ;
                    } else{
                        return $(val).eq(0) ;
                    }
                }
            },
            disabled: false,
            visible: false,
            axis: false,
            revert: false,
            revertDuration: 500,
            dragCursor: 'move',
            dropCursor: 'copy',
            zIndex: 9999
        },

        initialize: function(elem, config){
            var element = null ;

            // 检查源节点合法性，初始化
            if($(elem).length === 0 || $(elem).get(0).nodeType !== 1){
                $.error('element error!') ;
            }
            config = $.extend({element: elem}, config) ;
            Dnd.superclass.initialize.call(this, config) ;
            element = this.get('element') ;
            if(this.get('proxy') === null){
                this.set('proxy', element.clone()) ;
            }

            // 记录下源节点初始style
            element.data('style', element.attr('style') || '') ;

            // 在源节点上存储dnd uid
            element.data('dnd', ++uid) ;
            dndArray[uid] = this ;
        }
    }) ;

    /*
     * 开启页面Dnd功能,绑定鼠标按下、移动、释放以及ecs按下事件
    */
    Dnd.open = function(){
        $(document).on('mousedown mousemove mouseup keydown',
                handleEvent) ;
    } ;

    /*
     * 关闭页面Dnd功能,解绑鼠标按下、移动、释放以及ecs按下事件
    */
    Dnd.close = function(){
        $(document).off('mousedown mousemove mouseup keydown',
                handleEvent) ;
    } ;

    /*
     * 核心部分, 处理鼠标按下、移动、释放以及ecs按下事件, 实现拖放逻辑
    */
    function handleEvent(event){
        switch(event.type){
            case 'mousedown':
                if(event.which === 1){

                    // 检测并执行预拖放
                    executeDragPre({target: event.target, pageX: event.pageX,
                            pageY:event.pageY}) ;

                    // 阻止默认选中文本
                    if(draggingPre === true){
                        event.preventDefault() ;
                    }
                }
                break ;

            case 'mousemove':
                if(draggingPre === true){

                    // 开始拖放
                    executeDragStart() ;
                }
                if(dragging !== null){

                    // 根据边界和方向一起判断是否drag并执行
                    executeDrag({pageX: event.pageX, pageY: event.pageY}) ;

                    // 根据dragging和dropping位置来判断
                    // 是否要dragenter, dragleave和dragover并执行
                    executeDragEnterLeaveOver() ;

                    // 阻止默认选中文本
                    event.preventDefault() ;
                }
                break ;

            case 'mouseup':
                if(dragging !== null){
                    dragging.css('cursor', 'default') ;
                    dragging.focus() ;
                    dragging = null ;

                    // 根据dropping判断是否drop并执行
                    executeDrop() ;

                    // 根据revert判断是否要返回并执行
                    executeRevert() ;

                    // 此处传递的dragging为源节点element
                    obj.trigger('dragend', obj.get('element'), dropping) ;
                    obj = null ;
                    dropping = null ;
                } else if(draggingPre === true){

                    // 点击而非拖放时
                    obj.get('proxy').remove() ;
                    draggingPre = false ;
                    obj = null ;
                }
                break ;

            case 'keydown':
                if(dragging !== null && event.which === 27){
                    dragging.css('cursor', 'default') ;
                    dragging.focus() ;
                    dragging = null ;

                    // 返回源节点初始位置
                    executeRevert(true) ;

                    // 此处传递的dragging为源节点element
                    obj.trigger('dragend', obj.get('element'), dropping) ;
                    obj = null ;
                    dropping = null ;
                }
                break ;
        }
    }

    /*
     * 鼠标按下触发预拖放
    */
    function executeDragPre(event){
        var element = null,
            proxy = null,
            dnd = null,
            targetArray = $(event.target).parents().toArray() ;

        // 查找自身和父元素，判断是否为可拖放元素
        targetArray.unshift(event.target) ;
        $.each(targetArray, function(index, elem){
            if($(elem).data('dnd') !== undefined){
                dnd = $(elem).data('dnd') ;
                if(dnd === true){
                    dnd = new Dnd(elem, $(elem).data('config')) ;
                } else if(dnd === false){
                    // dnd为false标识禁止该元素触发拖放
                    dnd = null ;
                } else if(isNaN(parseInt(dnd)) === false && parseInt(dnd) > 0){
                    dnd = dndArray[parseInt(dnd)] ;
                } else{
                    return true ;
                }
                return false ;
            }
        }) ;

        // 不允许拖放则返回
        if(dnd === null || dnd.get('disabled') === true) return ;

        // 将dnd对象传给当前拖放对象obj
        obj = dnd ;
        element = obj.get('element') ;
        proxy = obj.get('proxy') ;

        // 设置代理元素proxy，并将其插入文档中
        proxy.css({position:'absolute', margin:0, left:0, top:0,
        visibility:'hidden'}) ;
        proxy.appendTo(element.parent()) ;
        proxy.data('originx', proxy.offset().left) ;
        proxy.data('originy', proxy.offset().top) ;
        proxy.css({left: element.offset().left - proxy.data('originx'),
                top: element.offset().top - proxy.data('originy'),
                width: element.css('width')}) ;
        diffX = event.pageX - element.offset().left ;
        diffY = event.pageY - element.offset().top ;
        draggingPre = true ;
    }

    /*
     * 鼠标拖动触发拖放
    */
    function executeDragStart(){
        var element = obj.get('element'),
            proxy = obj.get('proxy'),
            visible = obj.get('visible'),
            dragCursor = obj.get('dragCursor'),
            zIndex = obj.get('zIndex') ;

        // 按照设置显示或隐藏element
        if(visible !== true){
            element.css('visibility', 'hidden') ;
        }
        proxy.css({'z-index':zIndex, visibility:'visible', cursor:dragCursor}) ;
        proxy.focus() ;

        dataTransfer = {} ;
        draggingPre = false ;
        dragging = proxy ;
        obj.trigger('dragstart', dataTransfer, dragging, dropping) ;
    }

    /*
     * 根据边界和方向一起判断是否drag并执行
    */
    function executeDrag(event){
        var containment = obj.get('containment'),
            axis = obj.get('axis'),
            xleft = event.pageX - diffX,
            xtop = event.pageY - diffY,
            originx = dragging.data('originx'),
            originy = dragging.data('originy'),
            offset = containment.offset() ;

        // containment is document
        if(offset === null){
            offset = {left:0, top:0} ;
        }

        // 是否在x方向上移动并执行
        if(axis !== 'y'){
            if(xleft >= offset.left &&
                    xleft + dragging.outerWidth() <= offset.left +
                    containment.outerWidth()){
                dragging.css('left', xleft - originx) ;
            } else{
                if(xleft <= offset.left){
                    dragging.css('left', offset.left - originx) ;
                } else{
                    dragging.css('left',
                            offset.left + containment.outerWidth() -
                            dragging.outerWidth() - originx) ;
                }
            }
        }

        // 是否在y方向上移动并执行
        if(axis !== 'x'){
            if(xtop >= offset.top &&
                    xtop + dragging.outerHeight() <= offset.top +
                    containment.outerHeight()){
                dragging.css('top', xtop - originy) ;
            } else{
                if(xtop <= offset.top){
                    dragging.css('top', offset.top - originy) ;
                } else{
                    dragging.css('top',
                            offset.top + containment.outerHeight() -
                            dragging.outerHeight() - originy) ;
                }
            }
        }

        obj.trigger('drag', dragging, dropping) ;
    }

    /*
     * 根据dragging和dropping位置来判断是否dragenter, dragleave和dragover并执行
    */
    function executeDragEnterLeaveOver(){
        var element = obj.get('element'),
            drop = obj.get('drop'),
            dragCursor = obj.get('dragCursor'),
            dropCursor = obj.get('dropCursor'),
            xleft = dragging.offset().left + diffX,
            xtop = dragging.offset().top + diffY ;

        if(drop === null){
            return ;
        }

        if(dropping === null){
            $.each(drop, function(index, elem){
                if(isContain(elem, xleft, xtop) === true){
                    dragging.css('cursor', dropCursor) ;
                    dragging.focus() ;

                    dropping = $(elem) ;
                    obj.trigger('dragenter', dragging, dropping) ;
                    return false ; // 跳出each
                }
            }) ;
        } else{
            if(isContain(dropping, xleft, xtop) === false){
                dragging.css('cursor', dragCursor) ;
                dragging.focus() ;

                obj.trigger('dragleave', dragging, dropping) ;
                dropping = null ;
            } else{
                obj.trigger('dragover', dragging, dropping) ;
            }
        }
    }

    /*
     * 根据dropping判断是否drop并执行
     * 当dragging不在dropping内且不需要revert时, 将dragging置于dropping中央
    */
    function executeDrop(){
        var element = obj.get('element'),
            xdragging = obj.get('proxy'),
            revert = obj.get('revert'),
            originx = xdragging.data('originx'),
            originy = xdragging.data('originy') ;

        if(dropping === null){
            return ;
        }

        // 放置时不完全在drop中并且不需要返回的则放置中央
        if(isContain(dropping, xdragging) === false && revert === false){
            xdragging.css('left', dropping.offset().left +
                    (dropping.outerWidth() -
                    xdragging.outerWidth()) / 2 - originx) ;
            xdragging.css('top', dropping.offset().top +
                    (dropping.outerHeight() -
                    xdragging.outerHeight()) / 2 - originy) ;
        }

        // 此处传递的dragging为源节点element
        obj.trigger('drop', dataTransfer, element, dropping) ;
    }

    /*
     * 根据revert判断是否要返回并执行
     * 若drop(目标元素)不为null且dropping(当前目标元素)为null, 则自动回到原处
     * flag为true表示必须返回的,目前用于esc按下触发返回
     * 处理完移除代理元素
    */
    function executeRevert(flag){
        var element = obj.get('element'),
            xdragging = obj.get('proxy'),
            drop = obj.get('drop'),
            revert = obj.get('revert'),
            revertDuration = obj.get('revertDuration'),
            visible = obj.get('visible'),
            xleft = xdragging.offset().left - element.offset().left,
            xtop = xdragging.offset().top -  element.offset().top,
            originx = xdragging.data('originx'),
            originy = xdragging.data('originy') ;

        if(revert === true || flag === true ||
                (dropping === null && drop !== null)){

            //代理元素返回源节点初始位置
            element.attr('style', element.data('style')) ;
            if(visible === false){
                element.css('visibility', 'hidden') ;
            }

            xdragging.animate({left: element.offset().left - originx,
                    top: element.offset().top - originy},
            revertDuration, function(){
                element.css('visibility', '') ;
                xdragging.remove() ;
            }) ;
        } else{

            // 源节点移动到代理元素处
            if(element.css('position') === 'relative'){
                xleft = (isNaN(parseInt(element.css('left'))) ? 0 :
                        parseInt(element.css('left'))) + xleft ;
                xtop = (isNaN(parseInt(element.css('top'))) ? 0 :
                        parseInt(element.css('top'))) + xtop ;
            } else if(element.css('position') === 'absolute'){
                xleft = xdragging.offset().left ;
                xtop = xdragging.offset().top ;
            } else{
                element.css('position', 'relative') ;
            }

            if(visible === false){
                element.css({left:xleft, top:xtop, visibility:''}) ;
                xdragging.remove() ;
            } else{

                // 源节点显示时动画移动到代理元素处
                element.animate({left: xleft, top: xtop}, revertDuration,
                        function(){
                    xdragging.remove() ;
                }) ;
            }
        }
    }

    /*
     * 判断元素B是否位于元素A内部
     * or 点(B, C)是否位于A内
    */
    function isContain(A, B, C){
        var offset = $(A).offset() ;

        // A is document
        if(offset === null){
            offset = {left:0, top:0} ;
        }

        if(arguments.length === 2){
            return offset.left <= $(B).offset().left &&
                    offset.left + $(A).outerWidth() >=
                    $(B).offset().left + $(B).outerWidth() &&
                    offset.top <= $(B).offset().top &&
                    offset.top + $(A).outerHeight() >=
                    $(B).offset().top + $(B).outerHeight() ;
        }
        if(arguments.length === 3){
            return offset.left <= B &&
                    offset.left + $(A).outerWidth() >= B &&
                    offset.top <= C &&
                    offset.top + $(A).outerHeight() >= C ;
        }
    }

    Dnd.open() ;
    module.exports = Dnd ;
}) ;
<commit_msg>normal<commit_after>define(function(require, exports, module){
    var Dnd = null ;

    var $ = require('$'),
        Base = require('base') ;

    var uid = 0, // 标识dnd instance的id
        dndArray = [], // 存储dnd instance的数组
        draggingPre = false, // 标识预拖放
        dragging = null, // 当前拖放的代理元素
        dropping = null, // 当前拖放的目标元素
        diffX = 0,
        diffY = 0, // diffX, diffY记录鼠标点击离源节点的距离
        obj = null, // 存储当前拖放的dnd
        dataTransfer = {} ; // 存储拖放信息, 在dragstart可设置,在drop中可读取

    Dnd = Base.extend({
        attrs: {
            element: {
                value: null,
                readOnly: true,
                getter: function(val){
                    return $(val).eq(0) ;
                }
            },
            containment: {
                value: document,
                getter: function(val){
                    return $(val).eq(0) ;
                }
            },
            proxy: {
                value: null,
                getter: function(val){
                    if(val === null){
                        return null ;
                    } else{
                        return $(val).eq(0) ;
                    }
                },
                setter: function(val){
                    if(val === null){
                        return this.get('element').clone() ;
                    } else{
                        return val ;
                    }
                }
            },
            drop: {
                value: null,
                getter: function(val){
                    if(val === null){
                        return null ;
                    } else{
                        return $(val).eq(0) ;
                    }
                }
            },
            disabled: false,
            visible: false,
            axis: false,
            revert: false,
            revertDuration: 500,
            dragCursor: 'move',
            dropCursor: 'copy',
            zIndex: 9999
        },

        initialize: function(elem, config){
            var element = null ;

            // 检查源节点合法性，初始化
            if($(elem).length === 0 || $(elem).get(0).nodeType !== 1){
                $.error('element error!') ;
            }
            config = $.extend({element: elem}, config) ;
            Dnd.superclass.initialize.call(this, config) ;
            element = this.get('element') ;
            if(this.get('proxy') === null){
                this.set('proxy', element.clone()) ;
            }

            // 记录下源节点初始style
            element.data('style', element.attr('style') || '') ;

            // 在源节点上存储dnd uid
            element.data('dnd', ++uid) ;
            dndArray[uid] = this ;
        }
    }) ;

    /*
     * 开启页面Dnd功能,绑定鼠标按下、移动、释放以及ecs按下事件
    */
    Dnd.open = function(){
        $(document).on('mousedown mousemove mouseup keydown',
                handleEvent) ;
    } ;

    /*
     * 关闭页面Dnd功能,解绑鼠标按下、移动、释放以及ecs按下事件
    */
    Dnd.close = function(){
        $(document).off('mousedown mousemove mouseup keydown',
                handleEvent) ;
    } ;

    /*
     * 核心部分, 处理鼠标按下、移动、释放以及ecs按下事件, 实现拖放逻辑
    */
    function handleEvent(event){
        switch(event.type){
            case 'mousedown':
                if(event.which === 1){

                    // 检测并执行预拖放
                    executeDragPre({target: event.target, pageX: event.pageX,
                            pageY:event.pageY}) ;

                    // 阻止默认选中文本
                    if(draggingPre === true){
                        event.preventDefault() ;
                    }
                }
                break ;

            case 'mousemove':
                if(draggingPre === true){

                    // 开始拖放
                    executeDragStart() ;
                }
                if(dragging !== null){

                    // 根据边界和方向一起判断是否drag并执行
                    executeDrag({pageX: event.pageX, pageY: event.pageY}) ;

                    // 根据dragging和dropping位置来判断
                    // 是否要dragenter, dragleave和dragover并执行
                    executeDragEnterLeaveOver() ;

                    // 阻止默认选中文本
                    event.preventDefault() ;
                }
                break ;

            case 'mouseup':
                if(dragging !== null){
                    dragging.css('cursor', 'default') ;
                    dragging.focus() ;
                    dragging = null ;

                    // 根据dropping判断是否drop并执行
                    executeDrop() ;

                    // 根据revert判断是否要返回并执行
                    executeRevert() ;

                    // 此处传递的dragging为源节点element
                    obj.trigger('dragend', obj.get('element'), dropping) ;
                    obj = null ;
                    dropping = null ;
                } else if(draggingPre === true){

                    // 点击而非拖放时
                    obj.get('proxy').remove() ;
                    draggingPre = false ;
                    obj = null ;
                }
                break ;

            case 'keydown':
                if(dragging !== null && event.which === 27){
                    dragging.css('cursor', 'default') ;
                    dragging.focus() ;
                    dragging = null ;

                    // 返回源节点初始位置
                    executeRevert(true) ;

                    // 此处传递的dragging为源节点element
                    obj.trigger('dragend', obj.get('element'), dropping) ;
                    obj = null ;
                    dropping = null ;
                }
                break ;
        }
    }

    /*
     * 鼠标按下触发预拖放
    */
    function executeDragPre(event){
        var element = null,
            proxy = null,
            dnd = null,
            targetArray = $(event.target).parents().toArray() ;

        // 查找自身和父元素，判断是否为可拖放元素
        targetArray.unshift(event.target) ;
        $.each(targetArray, function(index, elem){
            if($(elem).data('dnd') !== undefined){
                dnd = $(elem).data('dnd') ;
                if(dnd === true){
                    dnd = new Dnd(elem, $(elem).data('config')) ;
                } else if(dnd === false){

                    // dnd为false标识禁止该元素触发拖放
                    dnd = null ;
                } else if(isNaN(parseInt(dnd)) === false && parseInt(dnd) > 0){
                    dnd = dndArray[parseInt(dnd)] ;
                } else{
                    return true ;
                }
                return false ;
            }
        }) ;

        // 不允许拖放则返回
        if(dnd === null || dnd.get('disabled') === true) return ;

        // 将dnd对象传给当前拖放对象obj
        obj = dnd ;
        element = obj.get('element') ;
        proxy = obj.get('proxy') ;

        // 设置代理元素proxy，并将其插入文档中
        proxy.css({position:'absolute', margin:0, left:0, top:0,
        visibility:'hidden'}) ;
        proxy.appendTo(element.parent()) ;
        proxy.data('originx', proxy.offset().left) ;
        proxy.data('originy', proxy.offset().top) ;
        proxy.css({left: element.offset().left - proxy.data('originx'),
                top: element.offset().top - proxy.data('originy'),
                width: element.css('width')}) ;
        diffX = event.pageX - element.offset().left ;
        diffY = event.pageY - element.offset().top ;
        draggingPre = true ;
    }

    /*
     * 鼠标拖动触发拖放
    */
    function executeDragStart(){
        var element = obj.get('element'),
            proxy = obj.get('proxy'),
            visible = obj.get('visible'),
            dragCursor = obj.get('dragCursor'),
            zIndex = obj.get('zIndex') ;

        // 按照设置显示或隐藏element
        if(visible !== true){
            element.css('visibility', 'hidden') ;
        }
        proxy.css({'z-index':zIndex, visibility:'visible', cursor:dragCursor}) ;
        proxy.focus() ;

        dataTransfer = {} ;
        draggingPre = false ;
        dragging = proxy ;
        obj.trigger('dragstart', dataTransfer, dragging, dropping) ;
    }

    /*
     * 根据边界和方向一起判断是否drag并执行
    */
    function executeDrag(event){
        var containment = obj.get('containment'),
            axis = obj.get('axis'),
            xleft = event.pageX - diffX,
            xtop = event.pageY - diffY,
            originx = dragging.data('originx'),
            originy = dragging.data('originy'),
            offset = containment.offset() ;

        // containment is document
        if(offset === null){
            offset = {left:0, top:0} ;
        }

        // 是否在x方向上移动并执行
        if(axis !== 'y'){
            if(xleft >= offset.left &&
                    xleft + dragging.outerWidth() <= offset.left +
                    containment.outerWidth()){
                dragging.css('left', xleft - originx) ;
            } else{
                if(xleft <= offset.left){
                    dragging.css('left', offset.left - originx) ;
                } else{
                    dragging.css('left',
                            offset.left + containment.outerWidth() -
                            dragging.outerWidth() - originx) ;
                }
            }
        }

        // 是否在y方向上移动并执行
        if(axis !== 'x'){
            if(xtop >= offset.top &&
                    xtop + dragging.outerHeight() <= offset.top +
                    containment.outerHeight()){
                dragging.css('top', xtop - originy) ;
            } else{
                if(xtop <= offset.top){
                    dragging.css('top', offset.top - originy) ;
                } else{
                    dragging.css('top',
                            offset.top + containment.outerHeight() -
                            dragging.outerHeight() - originy) ;
                }
            }
        }

        obj.trigger('drag', dragging, dropping) ;
    }

    /*
     * 根据dragging和dropping位置来判断是否dragenter, dragleave和dragover并执行
    */
    function executeDragEnterLeaveOver(){
        var element = obj.get('element'),
            drop = obj.get('drop'),
            dragCursor = obj.get('dragCursor'),
            dropCursor = obj.get('dropCursor'),
            xleft = dragging.offset().left + diffX,
            xtop = dragging.offset().top + diffY ;

        if(drop === null){
            return ;
        }

        if(dropping === null){
            $.each(drop, function(index, elem){
                if(isContain(elem, xleft, xtop) === true){
                    dragging.css('cursor', dropCursor) ;
                    dragging.focus() ;

                    dropping = $(elem) ;
                    obj.trigger('dragenter', dragging, dropping) ;
                    return false ; // 跳出each
                }
            }) ;
        } else{
            if(isContain(dropping, xleft, xtop) === false){
                dragging.css('cursor', dragCursor) ;
                dragging.focus() ;

                obj.trigger('dragleave', dragging, dropping) ;
                dropping = null ;
            } else{
                obj.trigger('dragover', dragging, dropping) ;
            }
        }
    }

    /*
     * 根据dropping判断是否drop并执行
     * 当dragging不在dropping内且不需要revert时, 将dragging置于dropping中央
    */
    function executeDrop(){
        var element = obj.get('element'),
            xdragging = obj.get('proxy'),
            revert = obj.get('revert'),
            originx = xdragging.data('originx'),
            originy = xdragging.data('originy') ;

        if(dropping === null){
            return ;
        }

        // 放置时不完全在drop中并且不需要返回的则放置中央
        if(isContain(dropping, xdragging) === false && revert === false){
            xdragging.css('left', dropping.offset().left +
                    (dropping.outerWidth() -
                    xdragging.outerWidth()) / 2 - originx) ;
            xdragging.css('top', dropping.offset().top +
                    (dropping.outerHeight() -
                    xdragging.outerHeight()) / 2 - originy) ;
        }

        // 此处传递的dragging为源节点element
        obj.trigger('drop', dataTransfer, element, dropping) ;
    }

    /*
     * 根据revert判断是否要返回并执行
     * 若drop(目标元素)不为null且dropping(当前目标元素)为null, 则自动回到原处
     * flag为true表示必须返回的,目前用于esc按下触发返回
     * 处理完移除代理元素
    */
    function executeRevert(flag){
        var element = obj.get('element'),
            xdragging = obj.get('proxy'),
            drop = obj.get('drop'),
            revert = obj.get('revert'),
            revertDuration = obj.get('revertDuration'),
            visible = obj.get('visible'),
            xleft = xdragging.offset().left - element.offset().left,
            xtop = xdragging.offset().top -  element.offset().top,
            originx = xdragging.data('originx'),
            originy = xdragging.data('originy') ;

        if(revert === true || flag === true ||
                (dropping === null && drop !== null)){

            //代理元素返回源节点初始位置
            element.attr('style', element.data('style')) ;
            if(visible === false){
                element.css('visibility', 'hidden') ;
            }

            xdragging.animate({left: element.offset().left - originx,
                    top: element.offset().top - originy},
            revertDuration, function(){
                element.css('visibility', '') ;
                xdragging.remove() ;
            }) ;
        } else{

            // 源节点移动到代理元素处
            if(element.css('position') === 'relative'){
                xleft = (isNaN(parseInt(element.css('left'))) ? 0 :
                        parseInt(element.css('left'))) + xleft ;
                xtop = (isNaN(parseInt(element.css('top'))) ? 0 :
                        parseInt(element.css('top'))) + xtop ;
            } else if(element.css('position') === 'absolute'){
                xleft = xdragging.offset().left ;
                xtop = xdragging.offset().top ;
            } else{
                element.css('position', 'relative') ;
            }

            if(visible === false){
                element.css({left:xleft, top:xtop, visibility:''}) ;
                xdragging.remove() ;
            } else{

                // 源节点显示时动画移动到代理元素处
                element.animate({left: xleft, top: xtop}, revertDuration,
                        function(){
                    xdragging.remove() ;
                }) ;
            }
        }
    }

    /*
     * 判断元素B是否位于元素A内部
     * or 点(B, C)是否位于A内
    */
    function isContain(A, B, C){
        var offset = $(A).offset() ;

        // A is document
        if(offset === null){
            offset = {left:0, top:0} ;
        }

        if(arguments.length === 2){
            return offset.left <= $(B).offset().left &&
                    offset.left + $(A).outerWidth() >=
                    $(B).offset().left + $(B).outerWidth() &&
                    offset.top <= $(B).offset().top &&
                    offset.top + $(A).outerHeight() >=
                    $(B).offset().top + $(B).outerHeight() ;
        }
        if(arguments.length === 3){
            return offset.left <= B &&
                    offset.left + $(A).outerWidth() >= B &&
                    offset.top <= C &&
                    offset.top + $(A).outerHeight() >= C ;
        }
    }

    Dnd.open() ;
    module.exports = Dnd ;
}) ;
<|endoftext|>