<commit_before>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { formProps } from '../prop-types/form_props';
import { responseSetsProps } from '../prop-types/response_set_props';
import { questionsProps } from '../prop-types/question_props';
import { Button } from 'react-bootstrap';
import QuestionItem from './QuestionItem';
import ModalDialog  from './ModalDialog';
import Errors from './Errors';
import _ from 'lodash';

class FormEdit extends Component {

  stateForRevise(form) {
    var state = this.stateForEdit(form);
    state.version = state.version + 1;
    return state;
  }

  stateForEdit(form) {
    const id = form.id;
    const versionIndependentId = form.versionIndependentId;
    const version = form.version;
    const name = form.name || '';
    const description = form.description || '';
    const formQuestions = form.formQuestions || [];
    const controlNumber = form.controlNumber;
    const showWarningModal = false;
    return {formQuestions, name, id, version, versionIndependentId, controlNumber, description, showWarningModal};
  }

  constructor(props) {
    super(props);
    // This switch is currently effectively a no-op but I retained the structure
    //  from ResponseSetForm to make it easier to adapt in the future and be
    //  consistent.
    switch (this.props.action) {
      case 'revise':
        this.state = this.stateForRevise(props.form);
        break;
      case 'edit':
        this.state = this.stateForEdit(props.form);
        break;
      default:
        this.state = this.stateForRevise({});
    }
    this.unsavedState = false;
    this.addedResponseSets = _.compact(this.state.formQuestions.map((fq) => fq.responseSetId));
  }

  componentDidMount() {
    this.unbindHook = this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this));
    window.onbeforeunload = this.windowWillUnload.bind(this);
  }

  componentWillUnmount() {
    this.unsavedState = false;
    this.unbindHook();
  }

  routerWillLeave(nextLocation) {
    this.setState({ showWarningModal: this.unsavedState });
    this.nextLocation = nextLocation;
    return !this.unsavedState;
  }

  handleModalResponse(leavePage){
    this.setState({ showWarningModal: false });
    if(leavePage){
      this.unsavedState = false;
      this.props.router.push(this.nextLocation.pathname);
    }else{
      let form = Object.assign({}, this.state);
      form.linkedQuestions = this.state.formQuestions.map((q) => q.questionId);
      form.linkedResponseSets = this.state.formQuestions.map((q) => q.responseSetId);
      this.props.formSubmitter(form, (response) => {
        // TODO: Handle when the saving form fails.
        this.unsavedState = false;
        if (response.status === 201) {
          this.props.router.push(this.nextLocation.pathname);
        }
      });
    }
  }

  windowWillUnload() {
    return (this.unsavedState || null);
  }

  handleResponseSetChange(questionIndex, event) {
    var responseSetId = parseInt(event.target.value);
    if(isNaN(responseSetId)){
      responseSetId = '';
    }
    let newState = Object.assign({}, this.state);
    newState.formQuestions[questionIndex].responseSetId = responseSetId;
    this.setState(newState);
    this.unsavedState = true;
  }

  handleChange(field) {
    return (event) => {
      let newState = {};
      newState[field] = event.target.value;
      this.setState(newState);
      this.unsavedState = true;
    };
  }

  handleSubmit(event) {
    event.preventDefault();
    // Because of the way we have to pass the current questions in we have to manually sync props and state for submit
    let form = Object.assign({}, this.state);
    form.linkedQuestions = this.state.formQuestions.map((q) => q.questionId);
    form.linkedResponseSets = this.state.formQuestions.map((q) => q.responseSetId);
    this.props.formSubmitter(form, (response) => {
      this.unsavedState = false;
      this.props.router.push(`/forms/${response.data.id}`);
    }, (failureResponse) => {
      this.setState({errors: failureResponse.response.data});
    });
  }

  cancelButton() {
    if(this.props.form && this.props.form.id) {
      return(<Link className="btn btn-default pull-right" to={`/forms/${this.props.form.id}`}>Cancel</Link>);
    }
    return(<Link className="btn btn-default pull-right" to='/'>Cancel</Link>);
  }

  addLinkedResponseSet(questionIndex, responseSet){
    if(this.state.formQuestions[questionIndex].responseSetId == responseSet.id){
      return;
    }
    this.addedResponseSets = _.union(this.addedResponseSets, [responseSet.id]);
    var newState = Object.assign({}, this.state);
    newState.formQuestions[questionIndex].responseSetId = responseSet.id;
    this.setState(newState);
  }

  linkedResponseSets(qId) {
    var linkedResponseSets = [];
    if(this.props.questions[qId] && this.props.questions[qId].responseSets && this.props.questions[qId].responseSets.length > 0) {
      linkedResponseSets = this.props.questions[qId].responseSets || [];
    }
    linkedResponseSets = _.union(linkedResponseSets, this.addedResponseSets, this.state.formQuestions.map((fq) => fq.responseSetId));
    return _.compact(linkedResponseSets.map((rsId) => this.props.responseSets[rsId]));
  }

  addedQuestions() {
    var form = this.state;
    return (

Questions

              Response Sets
              <Button onClick={this.props.showResponseSetModal} bsStyle="primary">Add New Response Set</Button>

          {form.formQuestions.map((q, i) =>

                <QuestionItem index={i}
                              question={this.props.questions[q.questionId]}
                              responseSets={this.linkedResponseSets(q.questionId)}
                              selectedResponseSet={q.responseSetId}
                              removeQuestion ={this.props.removeQuestion}
                              reorderQuestion={this.props.reorderQuestion}
                              handleResponseSetChange ={(responseSet) => this.handleResponseSetChange(i, responseSet)}
                              handleSelectSearchResult={(responseSet) => {
                                this.addLinkedResponseSet(i, responseSet);
                                this.handleResponseSetChange(i, responseSet.id);
                              }} />

 this.props.reorderQuestion(form, i, 1)}>

 this.props.reorderQuestion(form, i, -1)}>

 this.props.removeQuestion(form, i)}>

          )}

    );
  }

  render() {
    if(!this.props.questions || !this.props.responseSets){
      return (

Loading...

      );
    }
    return (

      <ModalDialog show ={this.state.showWarningModal}
                   title="Warning"
                   subTitle="Unsaved Changes"
                   warning={true}
                   message="You are about to leave a page with unsaved changes. How would you like to proceed?"
                   secondaryButtonMessage="Continue Without Saving"
                   primaryButtonMessage="Save & Leave"
                   cancelButtonMessage="Cancel"
                   primaryButtonAction={()=> this.handleModalResponse(false)}
                   cancelButtonAction ={()=> {
                     this.props.router.push(this.props.route.path);
                     this.setState({ showWarningModal: false });
                   }}
                   secondaryButtonAction={()=> this.handleModalResponse(true)} />
      <form onSubmit={(e) => this.handleSubmit(e)}>
        <Errors errors={this.state.errors} />

            <button className="btn btn-default btn-sm" disabled></button>
            <input  className='btn btn-default pull-right' name="Save Form" type="submit" value={`Save`}/>
            <button className="btn btn-default pull-right" disabled>Export</button>
            {this.cancelButton()}

                Name
                <input className="input-format" placeholder="Name" type="text" value={this.state.name} name="name" id="name" onChange={this.handleChange('name')}/>

                Description
                <input className="input-format" placeholder="Enter a description here..." type="text" value={this.state.description || ''} name="description" id="description" onChange={this.handleChange('description')}/>

                OMB Approval
                <input className="input-format" placeholder="XXXX-XXXX" type="text" value={this.state.controlNumber || ''} name="controlNumber" id="controlNumber" onChange={this.handleChange('controlNumber')}/>

        {this.addedQuestions()}
      </form>

    );
  }
}

FormEdit.propTypes = {
  form:   formProps,
  action: PropTypes.string.isRequired,
  formSubmitter:   PropTypes.func.isRequired,
  reorderQuestion: PropTypes.func.isRequired,
  removeQuestion:  PropTypes.func.isRequired,
  route:  PropTypes.object.isRequired,
  router: PropTypes.object.isRequired,
  responseSets: responseSetsProps,
  questions: questionsProps.isRequired,
  showResponseSetModal: PropTypes.func.isRequired
};

export default FormEdit;
<commit_msg>Fixing issue when adding response set from modal<commit_after>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { formProps } from '../prop-types/form_props';
import { responseSetsProps } from '../prop-types/response_set_props';
import { questionsProps } from '../prop-types/question_props';
import { Button } from 'react-bootstrap';
import QuestionItem from './QuestionItem';
import ModalDialog  from './ModalDialog';
import Errors from './Errors';
import _ from 'lodash';

class FormEdit extends Component {

  stateForRevise(form) {
    var state = this.stateForEdit(form);
    state.version = state.version + 1;
    return state;
  }

  stateForEdit(form) {
    const id = form.id;
    const versionIndependentId = form.versionIndependentId;
    const version = form.version;
    const name = form.name || '';
    const description = form.description || '';
    const formQuestions = form.formQuestions || [];
    const controlNumber = form.controlNumber;
    const showWarningModal = false;
    return {formQuestions, name, id, version, versionIndependentId, controlNumber, description, showWarningModal};
  }

  constructor(props) {
    super(props);
    // This switch is currently effectively a no-op but I retained the structure
    //  from ResponseSetForm to make it easier to adapt in the future and be
    //  consistent.
    switch (this.props.action) {
      case 'revise':
        this.state = this.stateForRevise(props.form);
        break;
      case 'edit':
        this.state = this.stateForEdit(props.form);
        break;
      default:
        this.state = this.stateForRevise({});
    }
    this.unsavedState = false;
    this.addedResponseSets = _.compact(this.state.formQuestions.map((fq) => fq.responseSetId));
  }

  componentDidMount() {
    this.unbindHook = this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this));
    window.onbeforeunload = this.windowWillUnload.bind(this);
  }

  componentWillUnmount() {
    this.unsavedState = false;
    this.unbindHook();
  }

  routerWillLeave(nextLocation) {
    this.setState({ showWarningModal: this.unsavedState });
    this.nextLocation = nextLocation;
    return !this.unsavedState;
  }

  handleModalResponse(leavePage){
    this.setState({ showWarningModal: false });
    if(leavePage){
      this.unsavedState = false;
      this.props.router.push(this.nextLocation.pathname);
    }else{
      let form = Object.assign({}, this.state);
      form.linkedQuestions = this.state.formQuestions.map((q) => q.questionId);
      form.linkedResponseSets = this.state.formQuestions.map((q) => q.responseSetId);
      this.props.formSubmitter(form, (response) => {
        // TODO: Handle when the saving form fails.
        this.unsavedState = false;
        if (response.status === 201) {
          this.props.router.push(this.nextLocation.pathname);
        }
      });
    }
  }

  windowWillUnload() {
    return (this.unsavedState || null);
  }

  handleResponseSetChange(questionIndex, responseSetId) {
    if(isNaN(responseSetId)){
      responseSetId = '';
    }
    let newState = Object.assign({}, this.state);
    newState.formQuestions[questionIndex].responseSetId = responseSetId;
    this.setState(newState);
    this.unsavedState = true;
  }

  handleChange(field) {
    return (event) => {
      let newState = {};
      newState[field] = event.target.value;
      this.setState(newState);
      this.unsavedState = true;
    };
  }

  handleSubmit(event) {
    event.preventDefault();
    // Because of the way we have to pass the current questions in we have to manually sync props and state for submit
    let form = Object.assign({}, this.state);
    form.linkedQuestions = this.state.formQuestions.map((q) => q.questionId);
    form.linkedResponseSets = this.state.formQuestions.map((q) => q.responseSetId);
    this.props.formSubmitter(form, (response) => {
      this.unsavedState = false;
      this.props.router.push(`/forms/${response.data.id}`);
    }, (failureResponse) => {
      this.setState({errors: failureResponse.response.data});
    });
  }

  cancelButton() {
    if(this.props.form && this.props.form.id) {
      return(<Link className="btn btn-default pull-right" to={`/forms/${this.props.form.id}`}>Cancel</Link>);
    }
    return(<Link className="btn btn-default pull-right" to='/'>Cancel</Link>);
  }

  addLinkedResponseSet(questionIndex, responseSet){
    if(this.state.formQuestions[questionIndex].responseSetId == responseSet.id){
      return;
    }
    this.addedResponseSets = _.union(this.addedResponseSets, [responseSet.id]);
    var newState = Object.assign({}, this.state);
    newState.formQuestions[questionIndex].responseSetId = responseSet.id;
    this.setState(newState);
  }

  linkedResponseSets(qId) {
    var linkedResponseSets = [];
    if(this.props.questions[qId] && this.props.questions[qId].responseSets && this.props.questions[qId].responseSets.length > 0) {
      linkedResponseSets = this.props.questions[qId].responseSets || [];
    }
    linkedResponseSets = _.union(linkedResponseSets, this.addedResponseSets, this.state.formQuestions.map((fq) => fq.responseSetId));
    return _.compact(linkedResponseSets.map((rsId) => this.props.responseSets[rsId]));
  }

  addedQuestions() {
    var form = this.state;
    return (

Questions

              Response Sets
              <Button onClick={this.props.showResponseSetModal} bsStyle="primary">Add New Response Set</Button>

          {form.formQuestions.map((q, i) =>

                <QuestionItem index={i}
                              question={this.props.questions[q.questionId]}
                              responseSets={this.linkedResponseSets(q.questionId)}
                              selectedResponseSet={q.responseSetId}
                              removeQuestion ={this.props.removeQuestion}
                              reorderQuestion={this.props.reorderQuestion}
                              handleResponseSetChange ={(event) => this.handleResponseSetChange(i, parseInt(event.target.value))}
                              handleSelectSearchResult={(responseSet) => {
                                this.addLinkedResponseSet(i, responseSet);
                                this.handleResponseSetChange(i, responseSet.id);
                              }} />

 this.props.reorderQuestion(form, i, 1)}>

 this.props.reorderQuestion(form, i, -1)}>

 this.props.removeQuestion(form, i)}>

          )}

    );
  }

  render() {
    if(!this.props.questions || !this.props.responseSets){
      return (

Loading...

      );
    }
    return (

      <ModalDialog show ={this.state.showWarningModal}
                   title="Warning"
                   subTitle="Unsaved Changes"
                   warning={true}
                   message="You are about to leave a page with unsaved changes. How would you like to proceed?"
                   secondaryButtonMessage="Continue Without Saving"
                   primaryButtonMessage="Save & Leave"
                   cancelButtonMessage="Cancel"
                   primaryButtonAction={()=> this.handleModalResponse(false)}
                   cancelButtonAction ={()=> {
                     this.props.router.push(this.props.route.path);
                     this.setState({ showWarningModal: false });
                   }}
                   secondaryButtonAction={()=> this.handleModalResponse(true)} />
      <form onSubmit={(e) => this.handleSubmit(e)}>
        <Errors errors={this.state.errors} />

            <button className="btn btn-default btn-sm" disabled></button>
            <input  className='btn btn-default pull-right' name="Save Form" type="submit" value={`Save`}/>
            <button className="btn btn-default pull-right" disabled>Export</button>
            {this.cancelButton()}

                Name
                <input className="input-format" placeholder="Name" type="text" value={this.state.name} name="name" id="name" onChange={this.handleChange('name')}/>

                Description
                <input className="input-format" placeholder="Enter a description here..." type="text" value={this.state.description || ''} name="description" id="description" onChange={this.handleChange('description')}/>

                OMB Approval
                <input className="input-format" placeholder="XXXX-XXXX" type="text" value={this.state.controlNumber || ''} name="controlNumber" id="controlNumber" onChange={this.handleChange('controlNumber')}/>

        {this.addedQuestions()}
      </form>

    );
  }
}

FormEdit.propTypes = {
  form:   formProps,
  action: PropTypes.string.isRequired,
  formSubmitter:   PropTypes.func.isRequired,
  reorderQuestion: PropTypes.func.isRequired,
  removeQuestion:  PropTypes.func.isRequired,
  route:  PropTypes.object.isRequired,
  router: PropTypes.object.isRequired,
  responseSets: responseSetsProps,
  questions: questionsProps.isRequired,
  showResponseSetModal: PropTypes.func.isRequired
};

export default FormEdit;
<|endoftext|>


<commit_before>/**
 * Created by ouyangshan09 on 2017/3/30.
 * 设计模式测试
 */

describe("JavaScript 设计模式", function () {
    describe.skip("工厂设计模式", function () {
        it("结果", function () {
            const p1 = new createPerson('lian', 28, '男');
            const p2 = new createPerson('ll', 13, '女');
            console.log(`p1-name:${p1.name}, p1-age:${p1.age}`);
            console.log(`p2-name:${p2.name}, p2-age:${p2.age}`);
            console.log(typeof p1);
            console.log(typeof p2);
        });
    });
    describe.skip("复杂工厂设计模式", function () {
        it("结果", function () {
            // var childClass = new BicycleChild("龙恩");
            // console.log(childClass.sellBicycle("model"));
            let byChild = new ByChild("ouyang");
            console.log(byChild.sellBicycle());
        });
    });
    describe("单例", function () {
        it("结果", function () {
            let a = Singleton.getInstance("a");
            let b = Singleton.getInstance("b");
            console.log(a.getName());
            console.log(b.getName());
            console.log(a === b);
        });
    });
});
// class Singleton{
//     constructor(name){
//         this.name = name;
//     }
//     getName(){
//         return this.name;
//     }
//     test;
//     static getInstance(name){
//         this.te
//         let instance = null;
//         return !instance ? instance = new Singleton(name) : instance;
//         // return function (name) {
//         //     if(!instance){
//         //         instance = new Singleton(name);
//         //     }
//         //     return instance;
//         // }
//     };
// }
// 单体模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
};
Singleton.getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();

function createPerson(name, age, sex) {
    let obj = Object.create({});
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    return obj;
}

class ByShop {
    constructor(name){
        this.name = name;
        this.method = function () {
            return this.name;
        }
    }
    sellBicycle(model){
        let bicycle = this.createBicycle(model);
        bicycle.A();
        bicycle.B();
        return bicycle;
    }
    //子类重写该方法
    createBicycle(model){
        return new Error("父类是抽象类不能直接调用，需要子类重写该方法");
    }
}
class ByChild extends ByShop{
    constructor(name){
        super();
        this.name = name;
    }
    createBicycle(){
        let A = function () {
            console.log("执行A业务操作");
        };
        let B = function () {
            console.log("执行B业务操作");
        };
        return {
            A: A,
            B: B,
        }
    }
}

// 定义自行车的构造函数
var BicycleShop = function(name){
    this.name = name;
    this.method = function(){
        return this.name;
    }
};
BicycleShop.prototype = {
    constructor: BicycleShop,
    /*
     * 买自行车这个方法
     * @param {model} 自行车型号
     */
    sellBicycle: function(model){
        var bicycle = this.createBicycle(model);
        // 执行A业务逻辑
        bicycle.A();

        // 执行B业务逻辑
        bicycle.B();

        return bicycle;
    },
    createBicycle: function(model){
        throw new Error("父类是抽象类不能直接调用，需要子类重写该方法");
    }
};
// 实现原型继承
function extend(Sub,Sup) {
    //Sub表示子类，Sup表示超类
    // 首先定义一个空函数
    var F = function(){};

    // 设置空函数的原型为超类的原型
    F.prototype = Sup.prototype;

    // 实例化空函数，并把超类原型引用传递给子类
    Sub.prototype = new F();

    // 重置子类原型的构造器为子类自身
    Sub.prototype.constructor = Sub;

    // 在子类中保存超类的原型,避免子类与超类耦合
    Sub.sup = Sup.prototype;

    if(Sup.prototype.constructor === Object.prototype.constructor) {
        // 检测超类原型的构造器是否为原型自身
        Sup.prototype.constructor = Sup;
    }
}
var BicycleChild = function(name){
    this.name = name;
// 继承构造函数父类中的属性和方法
    BicycleShop.call(this,name);
};
// 子类继承父类原型方法
extend(BicycleChild,BicycleShop);
// BicycleChild 子类重写父类的方法
BicycleChild.prototype.createBicycle = function(){
    var A = function(){
        console.log("执行A业务操作");
    };
    var B = function(){
        console.log("执行B业务操作");
    };
    return {
        A: A,
        B: B
    }
};
<commit_msg>1、设计模式<commit_after>/**
 * Created by ouyangshan09 on 2017/3/30.
 * 设计模式测试
 */

describe("JavaScript 设计模式", function () {
    describe.skip("工厂设计模式", function () {
        it("结果", function () {
            const p1 = new createPerson('lian', 28, '男');
            const p2 = new createPerson('ll', 13, '女');
            console.log(`p1-name:${p1.name}, p1-age:${p1.age}`);
            console.log(`p2-name:${p2.name}, p2-age:${p2.age}`);
            console.log(typeof p1);
            console.log(typeof p2);
        });
    });
    describe.skip("复杂工厂设计模式", function () {
        it("结果", function () {
            // var childClass = new BicycleChild("龙恩");
            // console.log(childClass.sellBicycle("model"));
            let byChild = new ByChild("ouyang");
            console.log(byChild.sellBicycle());
        });
    });
    describe("单例", function () {
        it("结果", function () {
            let a = Singleton.getInstance("a");
            let b = Singleton.getInstance("b");
            console.log(a.getName());
            console.log(b.getName());
            console.log(a === b);
        });
    });
});
// class Singleton{
//     constructor(name){
//         this.name = name;
//     }
//     getName(){
//         return this.name;
//     }
//     test;
//     static getInstance(name){
//         this.te
//         let instance = null;
//         return !instance ? instance = new Singleton(name) : instance;
//         // return function (name) {
//         //     if(!instance){
//         //         instance = new Singleton(name);
//         //     }
//         //     return instance;
//         // }
//     };
// }
// 单体模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
};
Singleton.getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();

//工厂模式
function createPerson(name, age, sex) {
    let obj = Object.create({});
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    return obj;
}
//复杂工厂模式-ES6
class ByShop {
    constructor(name){
        this.name = name;
        this.method = function () {
            return this.name;
        }
    }
    sellBicycle(model){
        let bicycle = this.createBicycle(model);
        bicycle.A();
        bicycle.B();
        return bicycle;
    }
    //子类重写该方法
    createBicycle(model){
        return new Error("父类是抽象类不能直接调用，需要子类重写该方法");
    }
}
class ByChild extends ByShop{
    constructor(name){
        super();
        this.name = name;
    }
    createBicycle(){
        let A = function () {
            console.log("执行A业务操作");
        };
        let B = function () {
            console.log("执行B业务操作");
        };
        return {
            A: A,
            B: B,
        }
    }
}

//复杂工厂模式-
// 定义自行车的构造函数
var BicycleShop = function(name){
    this.name = name;
    this.method = function(){
        return this.name;
    }
};
BicycleShop.prototype = {
    constructor: BicycleShop,
    /*
     * 买自行车这个方法
     * @param {model} 自行车型号
     */
    sellBicycle: function(model){
        var bicycle = this.createBicycle(model);
        // 执行A业务逻辑
        bicycle.A();

        // 执行B业务逻辑
        bicycle.B();

        return bicycle;
    },
    createBicycle: function(model){
        throw new Error("父类是抽象类不能直接调用，需要子类重写该方法");
    }
};
// 实现原型继承
function extend(Sub,Sup) {
    //Sub表示子类，Sup表示超类
    // 首先定义一个空函数
    var F = function(){};

    // 设置空函数的原型为超类的原型
    F.prototype = Sup.prototype;

    // 实例化空函数，并把超类原型引用传递给子类
    Sub.prototype = new F();

    // 重置子类原型的构造器为子类自身
    Sub.prototype.constructor = Sub;

    // 在子类中保存超类的原型,避免子类与超类耦合
    Sub.sup = Sup.prototype;

    if(Sup.prototype.constructor === Object.prototype.constructor) {
        // 检测超类原型的构造器是否为原型自身
        Sup.prototype.constructor = Sup;
    }
}
var BicycleChild = function(name){
    this.name = name;
// 继承构造函数父类中的属性和方法
    BicycleShop.call(this,name);
};
// 子类继承父类原型方法
extend(BicycleChild,BicycleShop);
// BicycleChild 子类重写父类的方法
BicycleChild.prototype.createBicycle = function(){
    var A = function(){
        console.log("执行A业务操作");
    };
    var B = function(){
        console.log("执行B业务操作");
    };
    return {
        A: A,
        B: B
    }
};
<|endoftext|>


<commit_before>var Tooltip = function()
{
    var body = d3.select('body').node()

    // internatls
    var element     // currently shown element (or undefined)
    var tooltip     // tooltip d3js selection object
    var tooltip_content     // inner tooltip HTML container (place where content - result of templating - will be inserted)

    // configurable
    var selection
    var viewport

    // pointer offsets
    var pointerOffsetX = 0
    var pointerOffsetY = 0

    // state
    var stuck = false

    var _template = function(d)
    {
        return d.title
    }

    function _move(left, top)
    {
        var size = tooltip.node().getBoundingClientRect()

        var viewport_size = viewport.getBoundingClientRect()

        left = Math.min(left, viewport_size.right - size.width)
        left = Math.max(left, viewport_size.left)
        top = Math.max(top, viewport_size.top)
        top = Math.min(top, viewport_size.bottom - size.height)

        var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft

        tooltip
            .style('left', scrollLeft + left + 'px')
            .style('top', scrollTop + top + 'px')
    }

    var publicSpace = {
        init: function(custom_template, id, custom_viewport)
        {
            tooltip = d3.select('body')
                .append('div')
                .attr('class', 'tooltip popover')
                .style('opacity', 0)
                .style('pointer-events', 'none')

            var wrapper = tooltip
                .append('div')
                .attr('class', 'wrapper')

            tooltip_content = wrapper
                .append('div')
                .attr('class', 'popover-content')

            if(custom_template !== undefined)
            {
                _template = custom_template
            }
            d3.select('body')
                .on('click.' + id, publicSpace.unstick)

            // create a close button
            wrapper.append('button')
                .attr('class', 'close')
                .html('x')
                .on('mouseup', publicSpace.unstick)

            if(custom_viewport)
                viewport = custom_viewport
            else
                viewport = body
        },
        show: function(d)
        {
            if(stuck)
                return

            element = selection
                .filter(function(el){ return checkEquality(el, d) })
                .node()

            tooltip.transition()
                .duration(50)
                .style('opacity', 1)
            tooltip_content.html(_template(d))

            publicSpace.moveToPointer()
        },
        hide: function(v)
        {
            if(stuck)
                return

            element = null

            tooltip.transition()
                .duration(200)
                .style('opacity', 0)
        },
        stick: function(d)
        {
            publicSpace.unstick()
            publicSpace.show(d)
            tooltip.style('pointer-events', 'auto')
            stuck = true
            d3.event.stopPropagation()
        },
        unstick: function()
        {
            stuck = false
            publicSpace.hide(true)
            tooltip.style('pointer-events', 'none')
        },
        moveToPointer: function()
        {
            if(stuck)
                return

            size = element.getBoundingClientRect()
            pointerOffsetX = d3.event.clientX - size.left
            pointerOffsetY = d3.event.clientY - size.top

            // move to pointer coordinates, as provided by d3 event
            _move(d3.event.clientX, d3.event.clientY)
        },
        moveToElement: function()
        {
            if(!element)
                return

            size = element.getBoundingClientRect()
            _move(size.left + pointerOffsetX, size.top + pointerOffsetY)
        },
        bind: function(new_selection)
        {
            selection = new_selection

            var old_click_event = selection.on('click')
            selection
                .on('click', function(e)
                    {
                        publicSpace.stick(e)
                        old_click_event(e)
                    }
                )
                .on('mouseover', publicSpace.show)
                .on('mousemove', publicSpace.moveToPointer)
                .on('mouseout', publicSpace.hide)

            // do not close the tooltip when selecting
            tooltip
                .on('click', function(){ d3.event.stopPropagation() })

        }
    }

    return publicSpace
}
<commit_msg>Do not fire old event if it does not exist<commit_after>var Tooltip = function()
{
    var body = d3.select('body').node()

    // internatls
    var element     // currently shown element (or undefined)
    var tooltip     // tooltip d3js selection object
    var tooltip_content     // inner tooltip HTML container (place where content - result of templating - will be inserted)

    // configurable
    var selection
    var viewport

    // pointer offsets
    var pointerOffsetX = 0
    var pointerOffsetY = 0

    // state
    var stuck = false

    var _template = function(d)
    {
        return d.title
    }

    function _move(left, top)
    {
        var size = tooltip.node().getBoundingClientRect()

        var viewport_size = viewport.getBoundingClientRect()

        left = Math.min(left, viewport_size.right - size.width)
        left = Math.max(left, viewport_size.left)
        top = Math.max(top, viewport_size.top)
        top = Math.min(top, viewport_size.bottom - size.height)

        var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft

        tooltip
            .style('left', scrollLeft + left + 'px')
            .style('top', scrollTop + top + 'px')
    }

    var publicSpace = {
        init: function(custom_template, id, custom_viewport)
        {
            tooltip = d3.select('body')
                .append('div')
                .attr('class', 'tooltip popover')
                .style('opacity', 0)
                .style('pointer-events', 'none')

            var wrapper = tooltip
                .append('div')
                .attr('class', 'wrapper')

            tooltip_content = wrapper
                .append('div')
                .attr('class', 'popover-content')

            if(custom_template !== undefined)
            {
                _template = custom_template
            }
            d3.select('body')
                .on('click.' + id, publicSpace.unstick)

            // create a close button
            wrapper.append('button')
                .attr('class', 'close')
                .html('x')
                .on('mouseup', publicSpace.unstick)

            if(custom_viewport)
                viewport = custom_viewport
            else
                viewport = body
        },
        show: function(d)
        {
            if(stuck)
                return

            element = selection
                .filter(function(el){ return checkEquality(el, d) })
                .node()

            tooltip.transition()
                .duration(50)
                .style('opacity', 1)
            tooltip_content.html(_template(d))

            publicSpace.moveToPointer()
        },
        hide: function(v)
        {
            if(stuck)
                return

            element = null

            tooltip.transition()
                .duration(200)
                .style('opacity', 0)
        },
        stick: function(d)
        {
            publicSpace.unstick()
            publicSpace.show(d)
            tooltip.style('pointer-events', 'auto')
            stuck = true
            d3.event.stopPropagation()
        },
        unstick: function()
        {
            stuck = false
            publicSpace.hide(true)
            tooltip.style('pointer-events', 'none')
        },
        moveToPointer: function()
        {
            if(stuck)
                return

            size = element.getBoundingClientRect()
            pointerOffsetX = d3.event.clientX - size.left
            pointerOffsetY = d3.event.clientY - size.top

            // move to pointer coordinates, as provided by d3 event
            _move(d3.event.clientX, d3.event.clientY)
        },
        moveToElement: function()
        {
            if(!element)
                return

            size = element.getBoundingClientRect()
            _move(size.left + pointerOffsetX, size.top + pointerOffsetY)
        },
        bind: function(new_selection)
        {
            selection = new_selection

            var old_click_event = selection.on('click')
            selection
                .on('click', function(e)
                    {
                        publicSpace.stick(e)
                        if(old_click_event)
                            old_click_event(e)
                    }
                )
                .on('mouseover', publicSpace.show)
                .on('mousemove', publicSpace.moveToPointer)
                .on('mouseout', publicSpace.hide)

            // do not close the tooltip when selecting
            tooltip
                .on('click', function(){ d3.event.stopPropagation() })

        }
    }

    return publicSpace
}
<|endoftext|>


<commit_before>/**
 * @ngdoc directive
 * @name angulargm.directive:gmMap
 * @element ANY
 *
 * @description
 * A directive for embedding google maps into your app.
 *
 * `gm-map-id` is required. The `gm-center`, `gm-zoom`, `gm-bounds`, and
 * `gm-map-type-id` variables do not have to exist in the current scope--they
 * will be created if necessary. All three have bi-directional association,
 * i.e.  drag or zoom the map and they will update, update them and the map
 * will change.  However, any initial state of these three variables will be
 * ignored.
 *
 * If you need to get a handle on the google.maps.Map object, see
 * {@link angulargm.service:angulargmContainer angulargmContainer}
 *
 * @param {expression} gm-map-id angular expression that evaluates to a unique
 * string id for the map, e.g. `'map_canvas'` or `myMapId` where myMapId is a
 * variable in the current scope. This allows you to have multiple
 * maps/instances of the directive.
 *
 *
 * @param {expression} gm-center center variable in the current scope.  The
 * value will be a google.maps.LatLng object.
 *
 *
 * @param {expression} gm-zoom zoom variable in the current scope.  Value will
 * be an integer.
 *
 *
 * @param {expression} gm-bounds bounds variable in the current scope.  Value
 * will be a google.maps.LatLngBounds object.
 *
 *
 * @param {expression} gm-map-type-id mapTypeId variable in the current scope.
 * Value will be a string.
 *
 *
 * @param {expression} gm-map-options object in the current scope that is a
 * google.maps.MapOptions object. If unspecified, will use the values in
 * angulargmDefaults.mapOptions. {@link angulargm.service:angulargmDefaults angulargmDefaults} is a service, so it is
 * both injectable and overrideable (using $provide.decorator).
 *
 * @param {expression} gm-on-*event* an angular expression which evaluates to
 * an event handler. This handler will be attached to each marker's \*event\*
 * event.  The variables 'map' and 'event' evaluate to the map and the
 * [google.maps.MouseEvent](https://developers.google.com/maps/documentation/javascript/reference#MouseEvent),
 * respectively. The map is always passed in, but the MouseEvent is only passed in if the event emits it.  For example:
 * ```html
 * gm-on-click="myClickFn(map, event)"
 * ```
 * will call your `myClickFn` whenever the map is clicked.  You may have
 * multiple `gm-on-*event*` handlers, but only one for each type of event.  For events that have an underscore in their
 * name, such as 'center_changed', write it as 'gm-on-center-changed'.
 *
 *
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMap#gmMapResize
 * @eventOf angulargm.directive:gmMap
 * @eventType listen on current gmMap scope
 *
 * @description Alias for google.maps.event.trigger(map, 'resize')
 *
 * @param {string} mapId Required. The id of your map.
 * @example
 * ```js
 * $scope.$broadcast('gmMapResize', 'myMapId')
 * ```
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMap#gmMapIdle
 * @eventOf angulargm.directive:gmMap
 * @eventType emit on current gmMap scope
 *
 * @description Emitted when the map is finished loading (when the map fires
 * the 'idle' event).
 *
 * @param {string} mapId the id of the map which finished loading.
 *
 * @example
 * ```js
 * $scope.$on('gmMapIdle', function(event, mapId) {
 *     if (mapId === 'myMapId') {
 *       ...
 *     }
 * });
 * ```
 */

(function () {
'use strict';

  angular.module('AngularGM').

  directive('gmMap', ['$timeout', 'angulargmUtils', function ($timeout, angulargmUtils) {

    /** aliases **/
    var getEventHandlers = angulargmUtils.getEventHandlers;

    /** link function **/
    function link(scope, element, attrs, controller) {
      // initialize scope
      if (!angular.isDefined(scope.gmCenter)) {
        scope.center = {};
      }
      if (!angular.isDefined(scope.gmBounds)) {
        scope.bounds = {};
      }

      // Make sure gmMapId is defined
      // Note: redundant check in MapController. Can't hurt.
      if (!angular.isDefined(scope.gmMapId)) {
        throw 'angulargm must have non-empty gmMapId attribute';
      }

      // Check what's defined in attrs
      // Note: this is also redundant since angular will throw an exception if
      // these attributes are not set. I may make these optional in the future
      // (pending angular support).
      var hasCenter = false;
      var hasZoom = false;
      var hasBounds = false;
      var hasMapTypeId = false;

      if (attrs.hasOwnProperty('gmCenter')) {
        hasCenter = true;
      }
      if (attrs.hasOwnProperty('gmZoom')) {
        hasZoom = true;
      }
      if (attrs.hasOwnProperty('gmBounds')) {
        hasBounds = true;
      }
      if (attrs.hasOwnProperty('gmMapTypeId')) {
        hasMapTypeId = true;
      }

      var updateScope = function() {
        $timeout(function () {
          if (hasCenter || hasZoom || hasBounds || hasMapTypeId) {
            scope.$apply(function (s) {
              if (hasCenter) {
                scope.gmCenter = controller.center;
              }
              if (hasZoom) {
                scope.gmZoom = controller.zoom;
              }
              if (hasBounds) {
                var b = controller.bounds;
                if (b) {
                  scope.gmBounds = b;
                }
              }
              if (hasMapTypeId) {
                scope.gmMapTypeId = controller.mapTypeId;
              }
            });
          }
        });
      };

      // Add event listeners to the map
      controller.addMapListener('drag', updateScope);
      controller.addMapListener('zoom_changed', updateScope);
      controller.addMapListener('center_changed', updateScope);
      controller.addMapListener('bounds_changed', updateScope);
      controller.addMapListener('maptypeid_changed', updateScope);
      controller.addMapListener('resize', updateScope);

      // Add user supplied callbacks
      var map = controller.getMap(attrs.gmMapId);
      var handlers = getEventHandlers(attrs); // map events -> handlers
      angular.forEach(handlers, function(handler, event) {
        controller.addMapListener(event, function(ev) {
          // pass the map in
          var locals = {
            map: map
          };
          // And optionally a MouseEvent object if it exists
          if (ev !== undefined) {
            locals.event = ev;
          }

          $timeout(function() {
            handler(scope.$parent, locals);
          });
        });
      });

      if (hasCenter) {
        scope.$watch('gmCenter', function (newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && !controller.dragging) {
            var latLng = newValue;
            if (latLng)
              controller.center = latLng;
          }
        }, true);
      }

      if (hasZoom) {
        scope.$watch('gmZoom', function (newValue, oldValue) {
          var ok = (newValue != null && !isNaN(newValue));
          if (ok && newValue !== oldValue) {
            controller.zoom = newValue;
          }
        });
      }

      if (hasBounds) {
        scope.$watch('gmBounds', function(newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && !controller.dragging) {
            var bounds = newValue;
            if (bounds)
              controller.bounds = bounds;
          }
        });
      }

      if (hasMapTypeId) {
        scope.$watch('gmMapTypeId', function(newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && newValue) {
            controller.mapTypeId = newValue;
          }
        });
      }

      scope.$on('gmMapResize', function(event, gmMapId) {
        if (scope.gmMapId() === gmMapId) {
          controller.mapTrigger('resize');
        }
      });

      controller.addMapListenerOnce('idle', function() {
        scope.$emit('gmMapIdle', scope.gmMapId());
      });
      controller.mapTrigger('resize');
    }

    return {
      restrict: 'AE',
      priority: 100,
      template: '
' +
                  '

' +
                  '

' +
                '
',
      transclude: true,
      replace: true,
      scope: {
        gmCenter: '=',
        gmZoom: '=',
        gmBounds: '=',
        gmMapTypeId: '=',
        gmMapOptions: '&',
        gmMapId: '&'
      },
      controller: 'angulargmMapController',
      link: link
    };
  }]);
})();
<commit_msg>Revert "Merge pull request #55 from etiennetremel/master"<commit_after>/**
 * @ngdoc directive
 * @name angulargm.directive:gmMap
 * @element ANY
 *
 * @description
 * A directive for embedding google maps into your app.
 *
 * `gm-map-id` is required. The `gm-center`, `gm-zoom`, `gm-bounds`, and
 * `gm-map-type-id` variables do not have to exist in the current scope--they
 * will be created if necessary. All three have bi-directional association,
 * i.e.  drag or zoom the map and they will update, update them and the map
 * will change.  However, any initial state of these three variables will be
 * ignored.
 *
 * If you need to get a handle on the google.maps.Map object, see
 * {@link angulargm.service:angulargmContainer angulargmContainer}
 *
 * @param {expression} gm-map-id angular expression that evaluates to a unique
 * string id for the map, e.g. `'map_canvas'` or `myMapId` where myMapId is a
 * variable in the current scope. This allows you to have multiple
 * maps/instances of the directive.
 *
 *
 * @param {expression} gm-center center variable in the current scope.  The
 * value will be a google.maps.LatLng object.
 *
 *
 * @param {expression} gm-zoom zoom variable in the current scope.  Value will
 * be an integer.
 *
 *
 * @param {expression} gm-bounds bounds variable in the current scope.  Value
 * will be a google.maps.LatLngBounds object.
 *
 *
 * @param {expression} gm-map-type-id mapTypeId variable in the current scope.
 * Value will be a string.
 *
 *
 * @param {expression} gm-map-options object in the current scope that is a
 * google.maps.MapOptions object. If unspecified, will use the values in
 * angulargmDefaults.mapOptions. {@link angulargm.service:angulargmDefaults angulargmDefaults} is a service, so it is
 * both injectable and overrideable (using $provide.decorator).
 *
 * @param {expression} gm-on-*event* an angular expression which evaluates to
 * an event handler. This handler will be attached to each marker's \*event\*
 * event.  The variables 'map' and 'event' evaluate to the map and the
 * [google.maps.MouseEvent](https://developers.google.com/maps/documentation/javascript/reference#MouseEvent),
 * respectively. The map is always passed in, but the MouseEvent is only passed in if the event emits it.  For example:
 * ```html
 * gm-on-click="myClickFn(map, event)"
 * ```
 * will call your `myClickFn` whenever the map is clicked.  You may have
 * multiple `gm-on-*event*` handlers, but only one for each type of event.  For events that have an underscore in their
 * name, such as 'center_changed', write it as 'gm-on-center-changed'.
 *
 *
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMap#gmMapResize
 * @eventOf angulargm.directive:gmMap
 * @eventType listen on current gmMap scope
 *
 * @description Alias for google.maps.event.trigger(map, 'resize')
 *
 * @param {string} mapId Required. The id of your map.
 * @example
 * ```js
 * $scope.$broadcast('gmMapResize', 'myMapId')
 * ```
 */

/**
 * @ngdoc event
 * @name angulargm.directive:gmMap#gmMapIdle
 * @eventOf angulargm.directive:gmMap
 * @eventType emit on current gmMap scope
 *
 * @description Emitted when the map is finished loading (when the map fires
 * the 'idle' event).
 *
 * @param {string} mapId the id of the map which finished loading.
 *
 * @example
 * ```js
 * $scope.$on('gmMapIdle', function(event, mapId) {
 *     if (mapId === 'myMapId') {
 *       ...
 *     }
 * });
 * ```
 */

(function () {
'use strict';

  angular.module('AngularGM').

  directive('gmMap', ['$timeout', 'angulargmUtils', function ($timeout, angulargmUtils) {

    /** aliases **/
    var getEventHandlers = angulargmUtils.getEventHandlers;

    /** link function **/
    function link(scope, element, attrs, controller) {
      // initialize scope
      if (!angular.isDefined(scope.gmCenter)) {
        scope.center = {};
      }
      if (!angular.isDefined(scope.gmBounds)) {
        scope.bounds = {};
      }

      // Make sure gmMapId is defined
      // Note: redundant check in MapController. Can't hurt.
      if (!angular.isDefined(scope.gmMapId)) {
        throw 'angulargm must have non-empty gmMapId attribute';
      }

      // Check what's defined in attrs
      // Note: this is also redundant since angular will throw an exception if
      // these attributes are not set. I may make these optional in the future
      // (pending angular support).
      var hasCenter = false;
      var hasZoom = false;
      var hasBounds = false;
      var hasMapTypeId = false;

      if (attrs.hasOwnProperty('gmCenter')) {
        hasCenter = true;
      }
      if (attrs.hasOwnProperty('gmZoom')) {
        hasZoom = true;
      }
      if (attrs.hasOwnProperty('gmBounds')) {
        hasBounds = true;
      }
      if (attrs.hasOwnProperty('gmMapTypeId')) {
        hasMapTypeId = true;
      }

      var updateScope = function() {
        $timeout(function () {
          if (hasCenter || hasZoom || hasBounds || hasMapTypeId) {
            scope.$apply(function (s) {
              if (hasCenter) {
                scope.gmCenter = controller.center;
              }
              if (hasZoom) {
                scope.gmZoom = controller.zoom;
              }
              if (hasBounds) {
                var b = controller.bounds;
                if (b) {
                  scope.gmBounds = b;
                }
              }
              if (hasMapTypeId) {
                scope.gmMapTypeId = controller.mapTypeId;
              }
            });
          }
        });
      };

      // Add event listeners to the map
      controller.addMapListener('drag', updateScope);
      controller.addMapListener('zoom_changed', updateScope);
      controller.addMapListener('center_changed', updateScope);
      controller.addMapListener('bounds_changed', updateScope);
      controller.addMapListener('maptypeid_changed', updateScope);
      controller.addMapListener('resize', updateScope);

      // Add user supplied callbacks
      var map = controller.getMap(attrs.gmMapId);
      var handlers = getEventHandlers(attrs); // map events -> handlers
      angular.forEach(handlers, function(handler, event) {
        controller.addMapListener(event, function(ev) {
          // pass the map in
          var locals = {
            map: map
          };
          // And optionally a MouseEvent object if it exists
          if (ev !== undefined) {
            locals.event = ev;
          }

          $timeout(function() {
            handler(scope.$parent, locals);
          });
        });
      });

      if (hasCenter) {
        scope.$watch('gmCenter', function (newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && !controller.dragging) {
            var latLng = newValue;
            if (latLng)
              controller.center = latLng;
          }
        }, true);
      }

      if (hasZoom) {
        scope.$watch('gmZoom', function (newValue, oldValue) {
          var ok = (newValue != null && !isNaN(newValue));
          if (ok && newValue !== oldValue) {
            controller.zoom = newValue;
          }
        });
      }

      if (hasBounds) {
        scope.$watch('gmBounds', function(newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && !controller.dragging) {
            var bounds = newValue;
            if (bounds)
              controller.bounds = bounds;
          }
        });
      }

      if (hasMapTypeId) {
        scope.$watch('gmMapTypeId', function(newValue, oldValue) {
          var changed = (newValue !== oldValue);
          if (changed && newValue) {
            controller.mapTypeId = newValue;
          }
        });
      }

      scope.$on('gmMapResize', function(event, gmMapId) {
        if (scope.gmMapId() === gmMapId) {
          controller.mapTrigger('resize');
        }
      });

      controller.addMapListenerOnce('idle', function() {
        scope.$emit('gmMapIdle', scope.gmMapId());
      });
      controller.mapTrigger('resize');
    }

    return {
      restrict: 'AE',
      priority: 100,
      template: '
' +
                  '

' +
                  '

' +
                '
',
      transclude: true,
      replace: true,
      scope: {
        gmCenter: '=',
        gmZoom: '=',
        gmBounds: '=',
        gmMapTypeId: '=',
        gmMapOptions: '&',
        gmMapId: '&'
      },
      controller: 'angulargmMapController',
      link: link
    };
  }]);
})();
<|endoftext|>


<commit_before>"use babel";

let Server;
let Client;
let Documentation;
let Helper;
let PackageConfig;
let Config;
let Type;
let Reference;
let Rename;
let _ = require('underscore-plus');

export default class Manager {

  constructor(provider) {

    this.provider = provider;

    this.disposables = [];

    this.grammars = ['JavaScript'];

    this.clients = [];
    this.client = undefined;
    this.servers = [];
    this.server = undefined;

    this.editors = [];

    this.rename = undefined;
    this.type = undefined;
    this.reference = undefined;
    this.documentation = undefined;

    this.initialised = false;

    window.setTimeout(this.init.bind(this), 0);
  }

  init() {

    Helper = require('./atom-ternjs-helper');
    PackageConfig = require('./atom-ternjs-package-config');
    Config = require('./atom-ternjs-config');

    this.helper = new Helper(this);
    this.packageConfig = new PackageConfig(this);
    this.config = new Config(this);
    this.provider.init(this);
    this.initServers();

    this.registerHelperCommands();

    this.disposables.push(atom.project.onDidChangePaths((paths) => {

      this.destroyServer(paths);
      this.checkPaths(paths);
      this.setActiveServerAndClient();
    }));
  }

  activate() {

    this.initialised = true;
    this.registerEvents();
    this.registerCommands();
  }

  destroyObject(object) {

    if (object) {

      object.destroy();
    }
    object = undefined;
  }

  destroy() {

    for (let server of this.servers) {

      server.destroy();
      server = undefined;
    }
    this.servers = [];

    for (let client of this.clients) {

      client = undefined;
    }
    this.clients = [];

    this.server = undefined;
    this.client = undefined;
    this.unregisterEventsAndCommands();
    this.provider = undefined;

    this.destroyObject(this.config);
    this.destroyObject(this.packageConfig);
    this.destroyObject(this.reference);
    this.destroyObject(this.rename);
    this.destroyObject(this.type);
    this.destroyObject(this.helper);

    this.initialised = false;
  }

  unregisterEventsAndCommands() {

    for (let disposable of this.disposables) {

      disposable.dispose();
    }

    this.disposables = [];
  }

  initServers() {

    let dirs = atom.project.getDirectories();

    if (dirs.length === 0) {

      return;
    }

    for (let dir of dirs) {

      dir = atom.project.relativizePath(dir.path)[0];

      if (this.helper.isDirectory(dir)) {

        this.startServer(dir);
      }
    }
  }

  startServer(dir) {

    if (!Server) {

      Server = require('./atom-ternjs-server');
    }

    if (this.getServerForProject(dir)) {

      return;
    }

    let client = this.getClientForProject(dir);

    if (!client) {

      if (!Client) {

        Client = require('./atom-ternjs-client');
      }

      let clientIdx = this.clients.push(new Client(this, dir)) - 1;
      client = this.clients[clientIdx];
    }

    this.servers.push(new Server(dir, client, this));

    if (this.servers.length === this.clients.length) {

      if (!this.initialised) {

        this.activate();
      }

      this.setActiveServerAndClient(dir);
    }
  }

  setActiveServerAndClient(URI) {

    if (!URI) {

      let activePane = atom.workspace.getActivePaneItem();

      if (activePane && activePane.getURI) {

        URI = activePane.getURI();

      } else {

        this.server = undefined;
        this.client = undefined;

        return;
      }
    }

    let dir = atom.project.relativizePath(URI)[0];
    let server = this.getServerForProject(dir);
    let client = this.getClientForProject(dir);

    if (server && client) {

      this.server = server;
      this.config.gatherData();
      this.client = client;

    } else {

      this.server = undefined;
      this.config.clear();
      this.client = undefined;
    }
  }

  checkPaths(paths) {

    for (let path of paths) {

      let dir = atom.project.relativizePath(path)[0];

      if (this.helper.isDirectory(dir)) {

        this.startServer(dir);
      }
    }
  }

  destroyServer(paths) {

    if (this.servers.length === 0) {

      return;
    }

    let serverIdx;

    for (let i = 0; i < this.servers.length; i++) {

      if (paths.indexOf(this.servers[i].projectDir) === -1) {

        serverIdx = i;
        break;
      }
    }

    if (serverIdx === undefined) {

      return;
    }

    let server = this.servers[serverIdx];
    let client = this.getClientForProject(server.projectDir);
    client = undefined;

    server.destroy();
    server = undefined;

    this.servers.splice(serverIdx, 1);
  }

  getServerForProject(projectDir) {

    for (let server of this.servers) {

      if (server.projectDir === projectDir) {

        return server;
      }
    }

    return false;
  }

  getClientForProject(projectDir) {

    for (let client of this.clients) {

      if (client.projectDir === projectDir) {

        return client;
      }
    }

    return false;
  }

  getEditor(editor) {

    for (let _editor of this.editors) {

      if (_editor.id === editor.id) {

        return _editor;
      }
    }
  }

  isValidEditor(editor) {

    if (!editor || !editor.getGrammar || editor.mini) {

      return;
    }

    if (!editor.getGrammar()) {

      return;
    }

    if (this.grammars.indexOf(editor.getGrammar().name) === -1) {

      return false;
    }

    return true;
  }

  onDidChangeCursorPosition(editor, e) {

    if (this.packageConfig.options.inlineFnCompletion) {

      if (!this.type) {

        Type = require('./atom-ternjs-type');
        this.type = new Type(this);
      }

      this.type.queryType(editor, e.cursor);
    }

    if (this.rename) {

      this.rename.hide();
    }
  }

  registerEvents() {

    this.disposables.push(atom.workspace.observeTextEditors((editor) => {

      if (!this.isValidEditor(editor)) {

        return;
      }

      // Register valid editor
      this.editors.push({

        id: editor.id,
        diffs: []
      });

      if (!this.initCalled) {

        this.init();
      }

      let editorView = atom.views.getView(editor);

      this.disposables.push(editorView.addEventListener('click', (e) => {

        if (!e[this.helper.accessKey]) {

          return;
        }

        if (this.client) {

          this.client.definition();
        }
      }));

      let scrollView;

      if (!editorView.shadowRoot) {

        scrollView = editorView.querySelector('.scroll-view');

      } else {

        scrollView = editorView.shadowRoot.querySelector('.scroll-view');
      }

      this.disposables.push(scrollView.addEventListener('mousemove', (e) => {

        if (!e[this.helper.accessKey]) {

          return;
        }

        if (e.target.classList.contains('line')) {

          return;
        }

        e.target.classList.add('atom-ternjs-hover');
      }));

      this.disposables.push(scrollView.addEventListener('mouseout', (e) => {

        e.target.classList.remove('atom-ternjs-hover');
      }));

      this.disposables.push(editor.onDidChangeCursorPosition((e) => {

        if (this.type) {

          this.type.destroyOverlay();
        }

        if (this.documentation) {

          this.documentation.destroyOverlay();
        }
      }));

      this.disposables.push(editor.onDidChangeCursorPosition(_.debounce(this.onDidChangeCursorPosition.bind(this, editor), 300)));

      this.disposables.push(editor.getBuffer().onDidSave((e) => {

        if (this.client) {

          this.client.update(editor);
        }
      }));

      this.disposables.push(editor.getBuffer().onDidChange((e) => {

        this.getEditor(editor).diffs.push(e);
      }));
    }));

    this.disposables.push(atom.workspace.onDidChangeActivePaneItem((item) => {

      if (this.config) {

        this.config.clear();
      }

      if (this.type) {

        this.type.destroyOverlay();
      }

      if (this.rename) {

        this.rename.hide();
      }

      if (!this.isValidEditor(item)) {

        if (this.reference) {

          this.reference.hide();
        }

      } else {

        this.setActiveServerAndClient(item.getURI());
      }
    }));
  }

  registerHelperCommands() {

    this.disposables.push(atom.commands.add('atom-workspace', 'tern:openConfig', (e) => {

      if (!this.config) {

        this.config = new Config(this);
      }

      this.config.show();
    }));
  }

  registerCommands() {

    this.disposables.push(atom.commands.add('atom-text-editor', 'core:cancel', (e) => {

      if (this.config) {

        this.config.hide();
      }

      if (this.type) {

        this.type.destroyOverlay();
      }

      if (this.rename) {

        this.rename.hide();
      }

      if (this.reference) {

        this.reference.hide();
      }

      if (this.documentation) {

        this.documentation.destroyOverlay();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:listFiles', (e) => {

      if (this.client) {

        this.client.files().then((data) => {

          console.dir(data);
        });
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:flush', (e) => {

      if (this.server) {

        this.server.flush();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:documentation', (e) => {

      if (!this.documentation) {

        Documentation = require('./atom-ternjs-documentation');
        this.documentation = new Documentation(this);
      }

      this.documentation.request();
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:references', (e) => {

      if (!this.reference) {

        Reference = require('./atom-ternjs-reference');
        this.reference = new Reference(this);
      }

      this.reference.findReference();
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:rename', (e) => {

        if (!this.rename) {

          Rename = require('./atom-ternjs-rename');
          this.rename = new Rename(this);
        }

        this.rename.show();
      }
    ));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:markerCheckpointBack', (e) => {

      if (this.helper) {

        this.helper.markerCheckpointBack();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:startCompletion', (e) => {

      if (this.provider) {

        this.provider.forceCompletion();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:definition', (e) => {

      if (this.client) {

        this.client.definition();
      }
    }));

    this.disposables.push(atom.commands.add('atom-workspace', 'tern:restart', (e) => {

      this.restartServer();
    }));
  }

  restartServer() {

    if (!this.server) {

      return;
    }

    let dir = this.server.projectDir;

    for (let i = 0; i < this.servers.length; i++) {

      if (dir === this.servers[i].projectDir) {

        serverIdx = i;
        break;
      }
    }

    if (this.server) {

      this.server.destroy();
    }

    this.server = undefined;
    this.servers.splice(serverIdx, 1);
    this.startServer(dir);
  }
}
<commit_msg>only call request method if there is a client for the current scope. fixes #191<commit_after>"use babel";

let Server;
let Client;
let Documentation;
let Helper;
let PackageConfig;
let Config;
let Type;
let Reference;
let Rename;
let _ = require('underscore-plus');

export default class Manager {

  constructor(provider) {

    this.provider = provider;

    this.disposables = [];

    this.grammars = ['JavaScript'];

    this.clients = [];
    this.client = undefined;
    this.servers = [];
    this.server = undefined;

    this.editors = [];

    this.rename = undefined;
    this.type = undefined;
    this.reference = undefined;
    this.documentation = undefined;

    this.initialised = false;

    window.setTimeout(this.init.bind(this), 0);
  }

  init() {

    Helper = require('./atom-ternjs-helper');
    PackageConfig = require('./atom-ternjs-package-config');
    Config = require('./atom-ternjs-config');

    this.helper = new Helper(this);
    this.packageConfig = new PackageConfig(this);
    this.config = new Config(this);
    this.provider.init(this);
    this.initServers();

    this.registerHelperCommands();

    this.disposables.push(atom.project.onDidChangePaths((paths) => {

      this.destroyServer(paths);
      this.checkPaths(paths);
      this.setActiveServerAndClient();
    }));
  }

  activate() {

    this.initialised = true;
    this.registerEvents();
    this.registerCommands();
  }

  destroyObject(object) {

    if (object) {

      object.destroy();
    }
    object = undefined;
  }

  destroy() {

    for (let server of this.servers) {

      server.destroy();
      server = undefined;
    }
    this.servers = [];

    for (let client of this.clients) {

      client = undefined;
    }
    this.clients = [];

    this.server = undefined;
    this.client = undefined;
    this.unregisterEventsAndCommands();
    this.provider = undefined;

    this.destroyObject(this.config);
    this.destroyObject(this.packageConfig);
    this.destroyObject(this.reference);
    this.destroyObject(this.rename);
    this.destroyObject(this.type);
    this.destroyObject(this.helper);

    this.initialised = false;
  }

  unregisterEventsAndCommands() {

    for (let disposable of this.disposables) {

      disposable.dispose();
    }

    this.disposables = [];
  }

  initServers() {

    let dirs = atom.project.getDirectories();

    if (dirs.length === 0) {

      return;
    }

    for (let dir of dirs) {

      dir = atom.project.relativizePath(dir.path)[0];

      if (this.helper.isDirectory(dir)) {

        this.startServer(dir);
      }
    }
  }

  startServer(dir) {

    if (!Server) {

      Server = require('./atom-ternjs-server');
    }

    if (this.getServerForProject(dir)) {

      return;
    }

    let client = this.getClientForProject(dir);

    if (!client) {

      if (!Client) {

        Client = require('./atom-ternjs-client');
      }

      let clientIdx = this.clients.push(new Client(this, dir)) - 1;
      client = this.clients[clientIdx];
    }

    this.servers.push(new Server(dir, client, this));

    if (this.servers.length === this.clients.length) {

      if (!this.initialised) {

        this.activate();
      }

      this.setActiveServerAndClient(dir);
    }
  }

  setActiveServerAndClient(URI) {

    if (!URI) {

      let activePane = atom.workspace.getActivePaneItem();

      if (activePane && activePane.getURI) {

        URI = activePane.getURI();

      } else {

        this.server = undefined;
        this.client = undefined;

        return;
      }
    }

    let dir = atom.project.relativizePath(URI)[0];
    let server = this.getServerForProject(dir);
    let client = this.getClientForProject(dir);

    if (server && client) {

      this.server = server;
      this.config.gatherData();
      this.client = client;

    } else {

      this.server = undefined;
      this.config.clear();
      this.client = undefined;
    }
  }

  checkPaths(paths) {

    for (let path of paths) {

      let dir = atom.project.relativizePath(path)[0];

      if (this.helper.isDirectory(dir)) {

        this.startServer(dir);
      }
    }
  }

  destroyServer(paths) {

    if (this.servers.length === 0) {

      return;
    }

    let serverIdx;

    for (let i = 0; i < this.servers.length; i++) {

      if (paths.indexOf(this.servers[i].projectDir) === -1) {

        serverIdx = i;
        break;
      }
    }

    if (serverIdx === undefined) {

      return;
    }

    let server = this.servers[serverIdx];
    let client = this.getClientForProject(server.projectDir);
    client = undefined;

    server.destroy();
    server = undefined;

    this.servers.splice(serverIdx, 1);
  }

  getServerForProject(projectDir) {

    for (let server of this.servers) {

      if (server.projectDir === projectDir) {

        return server;
      }
    }

    return false;
  }

  getClientForProject(projectDir) {

    for (let client of this.clients) {

      if (client.projectDir === projectDir) {

        return client;
      }
    }

    return false;
  }

  getEditor(editor) {

    for (let _editor of this.editors) {

      if (_editor.id === editor.id) {

        return _editor;
      }
    }
  }

  isValidEditor(editor) {

    if (!editor || !editor.getGrammar || editor.mini) {

      return;
    }

    if (!editor.getGrammar()) {

      return;
    }

    if (this.grammars.indexOf(editor.getGrammar().name) === -1) {

      return false;
    }

    return true;
  }

  onDidChangeCursorPosition(editor, e) {

    if (this.packageConfig.options.inlineFnCompletion) {

      if (!this.type) {

        Type = require('./atom-ternjs-type');
        this.type = new Type(this);
      }

      this.type.queryType(editor, e.cursor);
    }

    if (this.rename) {

      this.rename.hide();
    }
  }

  registerEvents() {

    this.disposables.push(atom.workspace.observeTextEditors((editor) => {

      if (!this.isValidEditor(editor)) {

        return;
      }

      // Register valid editor
      this.editors.push({

        id: editor.id,
        diffs: []
      });

      if (!this.initCalled) {

        this.init();
      }

      let editorView = atom.views.getView(editor);

      this.disposables.push(editorView.addEventListener('click', (e) => {

        if (!e[this.helper.accessKey]) {

          return;
        }

        if (this.client) {

          this.client.definition();
        }
      }));

      let scrollView;

      if (!editorView.shadowRoot) {

        scrollView = editorView.querySelector('.scroll-view');

      } else {

        scrollView = editorView.shadowRoot.querySelector('.scroll-view');
      }

      this.disposables.push(scrollView.addEventListener('mousemove', (e) => {

        if (!e[this.helper.accessKey]) {

          return;
        }

        if (e.target.classList.contains('line')) {

          return;
        }

        e.target.classList.add('atom-ternjs-hover');
      }));

      this.disposables.push(scrollView.addEventListener('mouseout', (e) => {

        e.target.classList.remove('atom-ternjs-hover');
      }));

      this.disposables.push(editor.onDidChangeCursorPosition((e) => {

        if (this.type) {

          this.type.destroyOverlay();
        }

        if (this.documentation) {

          this.documentation.destroyOverlay();
        }
      }));

      this.disposables.push(editor.onDidChangeCursorPosition(_.debounce(this.onDidChangeCursorPosition.bind(this, editor), 300)));

      this.disposables.push(editor.getBuffer().onDidSave((e) => {

        if (this.client) {

          this.client.update(editor);
        }
      }));

      this.disposables.push(editor.getBuffer().onDidChange((e) => {

        this.getEditor(editor).diffs.push(e);
      }));
    }));

    this.disposables.push(atom.workspace.onDidChangeActivePaneItem((item) => {

      if (this.config) {

        this.config.clear();
      }

      if (this.type) {

        this.type.destroyOverlay();
      }

      if (this.rename) {

        this.rename.hide();
      }

      if (!this.isValidEditor(item)) {

        if (this.reference) {

          this.reference.hide();
        }

      } else {

        this.setActiveServerAndClient(item.getURI());
      }
    }));
  }

  registerHelperCommands() {

    this.disposables.push(atom.commands.add('atom-workspace', 'tern:openConfig', (e) => {

      if (!this.config) {

        this.config = new Config(this);
      }

      this.config.show();
    }));
  }

  registerCommands() {

    this.disposables.push(atom.commands.add('atom-text-editor', 'core:cancel', (e) => {

      if (this.config) {

        this.config.hide();
      }

      if (this.type) {

        this.type.destroyOverlay();
      }

      if (this.rename) {

        this.rename.hide();
      }

      if (this.reference) {

        this.reference.hide();
      }

      if (this.documentation) {

        this.documentation.destroyOverlay();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:listFiles', (e) => {

      if (this.client) {

        this.client.files().then((data) => {

          console.dir(data);
        });
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:flush', (e) => {

      if (this.server) {

        this.server.flush();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:documentation', (e) => {

      if (!this.documentation) {

        Documentation = require('./atom-ternjs-documentation');
        this.documentation = new Documentation(this);
      }

      if (this.client) {

        this.documentation.request();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:references', (e) => {

      if (!this.reference) {

        Reference = require('./atom-ternjs-reference');
        this.reference = new Reference(this);
      }

      this.reference.findReference();
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:rename', (e) => {

        if (!this.rename) {

          Rename = require('./atom-ternjs-rename');
          this.rename = new Rename(this);
        }

        this.rename.show();
      }
    ));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:markerCheckpointBack', (e) => {

      if (this.helper) {

        this.helper.markerCheckpointBack();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:startCompletion', (e) => {

      if (this.provider) {

        this.provider.forceCompletion();
      }
    }));

    this.disposables.push(atom.commands.add('atom-text-editor', 'tern:definition', (e) => {

      if (this.client) {

        this.client.definition();
      }
    }));

    this.disposables.push(atom.commands.add('atom-workspace', 'tern:restart', (e) => {

      this.restartServer();
    }));
  }

  restartServer() {

    if (!this.server) {

      return;
    }

    let dir = this.server.projectDir;

    for (let i = 0; i < this.servers.length; i++) {

      if (dir === this.servers[i].projectDir) {

        serverIdx = i;
        break;
      }
    }

    if (this.server) {

      this.server.destroy();
    }

    this.server = undefined;
    this.servers.splice(serverIdx, 1);
    this.startServer(dir);
  }
}
<|endoftext|>


<commit_before>var t = 0;
const exec = require('child_process').exec;
exec('node nodejs_app/app.js', (error, stdout, stderr)=>{
  if (error) {
    console.error('exec error: ${error}');
	t =1;
    return;
  }
  console.log('stdout: ${stdout}');
  console.log('stderr: ${stderr}');
});
exec('killall node');
return t;
<commit_msg>Update testsA.js<commit_after>var t = 0;
const exec = require('child_process').exec;
exec('node nodejs_app/app.js', function(error, stdout, stderr){
  if (error) {
    console.error('exec error: ${error}');
	t =1;
    return;
  }
  console.log('stdout: ${stdout}');
  console.log('stderr: ${stderr}');
});
exec('killall node',function(error, stdout, stderr){
  if (error) {
    console.error('exec error: ${error}');
	t =1;
    return;
  }
  console.log('stdout: ${stdout}');
  console.log('stderr: ${stderr}');
});
return t;
<|endoftext|>


<commit_before>// Copyright (c) 2009, Scott Ferguson
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the software nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SCOTT FERGUSON ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL SCOTT FERGUSON BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function HotKeyManager(quickReply) {
    /*****************
     N - Next Post
     P/M - Previous Post
     J - Next Page
     K/H - Previous Page
     O - Reanchor thread
     Q - Quick Quote current post
     E - Quick Edit current post
     R - Quick Reply current thread
    *******************/
    this.quickReply = quickReply;
    this.bindHotKeys();

    jQuery(document).data("enableSALRHotkeys", true);
    jQuery(document).bind("enableSALRHotkeys", this.enableHotKeys);
    jQuery(document).bind("disableSALRHotkeys", this.disableHotKeys);

    this.thread_post_size = jQuery('div#thread > table.post').size();
    this.current_post = findFirstUnreadPost();
    this.first_keypress = true;
}

HotKeyManager.prototype.bindHotKeys = function() {
    var that = this;

    jQuery(document).keypress(function(event) {
        var quick_reply_block = false;

        if(!jQuery(document).data("enableSALRHotkeys"))
            return;

        /*if (findCurrentPage() == 'showthread.php') {
            if (this.quickReply.isExpanded() || this.quickReply.isVisible()) {
                quick_reply_block = true;
            }
        }

        if (!quick_reply_block) {*/
            switch(event.keyCode) {
                case 110:
                    // Next post
                    that.nextPost();
                    break;
                case 112:
                case 109:
                    // Previous post
                    that.previousPost();
                    break;
                case 106:
                    // Next page
                    that.nextPage();
                    break;
                case 107:
                case 104:
                    // Previous page
                    that.previousPage();
                    break;
                case 111:
                    // Re-anchor thread
                    that.anchorThread();
                    break;
                case 49: /* 1 */
                    // Jump to first post on the page
                    that.firstPost();
                    break;
                case 48: /* 0 */
                    // Jump to last post on the page
                    that.lastPost();
                    break;
                case 115: /* s */
                    //that.toggleSignatures();
                    break;
                case 113:
                    // Quick quote current post
                    if (findCurrentPage() == 'showthread.php') {
                        that.quoteCurrentPost();
                        event.preventDefault();
                    }
                    break;
                case 101:
                    // Quick edit current post
                    if (findCurrentPage() == 'showthread.php') {
                        that.editCurrentPost();
                        event.preventDefault();
                    }
                    break;
                case 114:
                    // TODO: Conditionalize on quick reply being enabled
                    if (findCurrentPage() == 'showthread.php') {
                        that.displayQuickReply();
                        event.preventDefault();
                    }
                    break;
                case 27:
                    // TODO: Conditionalize on quick reply being enabled
                    that.hideQuickReply();
                    break;
            }
        //}
    });
};

HotKeyManager.prototype.enableHotKeys = function() {
    jQuery(document).data("enableSALRHotkeys", true);
};

HotKeyManager.prototype.disableHotKeys = function() {
    jQuery(document).data("enableSALRHotkeys", false);
};

HotKeyManager.prototype.nextPage = function() {
    this.pageCount = countPages();

    switch(findCurrentPage()) {
        case 'forumdisplay.php':
        case 'showthread.php':
            var currentPage = Number(jQuery('span.curpage').html());
            if (currentPage <= 0)
                currentPage = 1;

            if (currentPage >= this.pageCount)
                return;

            jumpToPage(nextPageUrl());
    }
};

HotKeyManager.prototype.previousPage = function() {
    this.pageCount = countPages();

    switch(findCurrentPage()) {
        case 'forumdisplay.php':
        case 'showthread.php':
            var currentPage = Number(jQuery('span.curpage').html());
            if (currentPage <= 0)
                currentPage = 1;

            if (currentPage <= 1)
                return;

            jumpToPage(prevPageUrl());
    }
};

HotKeyManager.prototype.nextPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    if (this.first_keypress) {
        this.first_keypress = false;
    } else if (this.current_post < (this.thread_post_size - 1)) {
        this.current_post++;
    }

    var post = jQuery('div#thread > table.post');
    var previous_post = post.eq(this.current_post - 1);
    var current_post = post.eq(this.current_post);
    previous_post.removeClass('selected-post');
    current_post.addClass('selected-post');

    jQuery(window).scrollTop(current_post.offset().top);
};

HotKeyManager.prototype.previousPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    this.first_keypress = false;
    if (this.current_post > 0) {
        this.current_post--;
    }

    var post = jQuery('div#thread > table.post');
    var previous_post = post.eq(this.current_post + 1);
    var current_post = post.eq(this.current_post);
    previous_post.removeClass('selected-post');
    current_post.addClass('selected-post');

    jQuery(window).scrollTop(current_post.offset().top);
};

HotKeyManager.prototype.firstPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    var post = jQuery('div#thread > table.post');
    var current_post = post.eq(this.current_post);

    if (this.current_post > 0) {
        var previous_post = post.eq(this.current_post);

        this.current_post=0;
        current_post = post.eq(this.current_post);

        previous_post.removeClass('selected-post');
        current_post.addClass('selected-post');
    }

    if (this.first_keypress) {
        current_post.addClass('selected-post');
    }
    jQuery(window).scrollTop(current_post.offset().top);
    this.first_keypress = false;
};

HotKeyManager.prototype.lastPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    var post = jQuery('div#thread > table.post');
    var current_post = post.eq(this.current_post);

    if (this.current_post < this.thread_post_size-1) {
        var previous_post = post.eq(this.current_post);

        this.current_post=this.thread_post_size-1;
        current_post = post.eq(this.current_post);

        previous_post.removeClass('selected-post');
        current_post.addClass('selected-post');
    }

    if (this.first_keypress) {
        current_post.addClass('selected-post');
    }
    jQuery(window).scrollTop(current_post.offset().top);
    this.first_keypress = false;
};

HotKeyManager.prototype.anchorThread = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    } else if (this.current_post == -1) {
        return;
    }

    this.first_keypress = false;
    var post = jQuery('div#thread > table.post').eq(this.current_post);
    post.addClass('selected-post');

    jQuery(window).scrollTop(post.offset().top);
};

HotKeyManager.prototype.toggleSignatures = function() {
    jQuery('p.signature').each(function() {
        if (jQuery(this).css('display') == 'none')
            jQuery(this).css('display','');
        else
            jQuery(this).css('display','none');
    });
};

HotKeyManager.prototype.quoteCurrentPost = function() {
    if (this.current_post == -1) {
        return;
    }
    if (!this.quickReply)
        return;

    var current_post = jQuery('div#thread > table.post').eq(this.current_post);
    var postid = current_post.attr('id').substr(4);

    this.quickReply.appendQuote(postid);
    this.quickReply.show();
};

HotKeyManager.prototype.editCurrentPost = function() {
    if (this.current_post == -1) {
        return;
    }

    var current_post = jQuery('div#thread > table.post').eq(this.current_post);
    var postid = current_post.attr('id').substr(4);

    this.quickReply.editPost(postid);
    this.quickReply.show();
};

HotKeyManager.prototype.displayQuickReply = function() {
    if (!this.quickReply)
        return;
    if (findCurrentPage() == 'showthread.php') {
        this.quickReply.show();
    }
};

HotKeyManager.prototype.hideQuickReply = function() {
    if (!this.quickReply)
        return;
    if (findCurrentPage() == 'showthread.php') {
        this.quickReply.hide();
    }
};
<commit_msg>Make sure quick reply loaded for edit hotkey<commit_after>// Copyright (c) 2009, Scott Ferguson
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the software nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SCOTT FERGUSON ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL SCOTT FERGUSON BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function HotKeyManager(quickReply) {
    /*****************
     N - Next Post
     P/M - Previous Post
     J - Next Page
     K/H - Previous Page
     O - Reanchor thread
     Q - Quick Quote current post
     E - Quick Edit current post
     R - Quick Reply current thread
    *******************/
    this.quickReply = quickReply;
    this.bindHotKeys();

    jQuery(document).data("enableSALRHotkeys", true);
    jQuery(document).bind("enableSALRHotkeys", this.enableHotKeys);
    jQuery(document).bind("disableSALRHotkeys", this.disableHotKeys);

    this.thread_post_size = jQuery('div#thread > table.post').size();
    this.current_post = findFirstUnreadPost();
    this.first_keypress = true;
}

HotKeyManager.prototype.bindHotKeys = function() {
    var that = this;

    jQuery(document).keypress(function(event) {
        var quick_reply_block = false;

        if(!jQuery(document).data("enableSALRHotkeys"))
            return;

        /*if (findCurrentPage() == 'showthread.php') {
            if (this.quickReply.isExpanded() || this.quickReply.isVisible()) {
                quick_reply_block = true;
            }
        }

        if (!quick_reply_block) {*/
            switch(event.keyCode) {
                case 110:
                    // Next post
                    that.nextPost();
                    break;
                case 112:
                case 109:
                    // Previous post
                    that.previousPost();
                    break;
                case 106:
                    // Next page
                    that.nextPage();
                    break;
                case 107:
                case 104:
                    // Previous page
                    that.previousPage();
                    break;
                case 111:
                    // Re-anchor thread
                    that.anchorThread();
                    break;
                case 49: /* 1 */
                    // Jump to first post on the page
                    that.firstPost();
                    break;
                case 48: /* 0 */
                    // Jump to last post on the page
                    that.lastPost();
                    break;
                case 115: /* s */
                    //that.toggleSignatures();
                    break;
                case 113:
                    // Quick quote current post
                    if (findCurrentPage() == 'showthread.php') {
                        that.quoteCurrentPost();
                        event.preventDefault();
                    }
                    break;
                case 101:
                    // Quick edit current post
                    if (findCurrentPage() == 'showthread.php') {
                        that.editCurrentPost();
                        event.preventDefault();
                    }
                    break;
                case 114:
                    // TODO: Conditionalize on quick reply being enabled
                    if (findCurrentPage() == 'showthread.php') {
                        that.displayQuickReply();
                        event.preventDefault();
                    }
                    break;
                case 27:
                    // TODO: Conditionalize on quick reply being enabled
                    that.hideQuickReply();
                    break;
            }
        //}
    });
};

HotKeyManager.prototype.enableHotKeys = function() {
    jQuery(document).data("enableSALRHotkeys", true);
};

HotKeyManager.prototype.disableHotKeys = function() {
    jQuery(document).data("enableSALRHotkeys", false);
};

HotKeyManager.prototype.nextPage = function() {
    this.pageCount = countPages();

    switch(findCurrentPage()) {
        case 'forumdisplay.php':
        case 'showthread.php':
            var currentPage = Number(jQuery('span.curpage').html());
            if (currentPage <= 0)
                currentPage = 1;

            if (currentPage >= this.pageCount)
                return;

            jumpToPage(nextPageUrl());
    }
};

HotKeyManager.prototype.previousPage = function() {
    this.pageCount = countPages();

    switch(findCurrentPage()) {
        case 'forumdisplay.php':
        case 'showthread.php':
            var currentPage = Number(jQuery('span.curpage').html());
            if (currentPage <= 0)
                currentPage = 1;

            if (currentPage <= 1)
                return;

            jumpToPage(prevPageUrl());
    }
};

HotKeyManager.prototype.nextPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    if (this.first_keypress) {
        this.first_keypress = false;
    } else if (this.current_post < (this.thread_post_size - 1)) {
        this.current_post++;
    }

    var post = jQuery('div#thread > table.post');
    var previous_post = post.eq(this.current_post - 1);
    var current_post = post.eq(this.current_post);
    previous_post.removeClass('selected-post');
    current_post.addClass('selected-post');

    jQuery(window).scrollTop(current_post.offset().top);
};

HotKeyManager.prototype.previousPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    this.first_keypress = false;
    if (this.current_post > 0) {
        this.current_post--;
    }

    var post = jQuery('div#thread > table.post');
    var previous_post = post.eq(this.current_post + 1);
    var current_post = post.eq(this.current_post);
    previous_post.removeClass('selected-post');
    current_post.addClass('selected-post');

    jQuery(window).scrollTop(current_post.offset().top);
};

HotKeyManager.prototype.firstPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    var post = jQuery('div#thread > table.post');
    var current_post = post.eq(this.current_post);

    if (this.current_post > 0) {
        var previous_post = post.eq(this.current_post);

        this.current_post=0;
        current_post = post.eq(this.current_post);

        previous_post.removeClass('selected-post');
        current_post.addClass('selected-post');
    }

    if (this.first_keypress) {
        current_post.addClass('selected-post');
    }
    jQuery(window).scrollTop(current_post.offset().top);
    this.first_keypress = false;
};

HotKeyManager.prototype.lastPost = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    }

    var post = jQuery('div#thread > table.post');
    var current_post = post.eq(this.current_post);

    if (this.current_post < this.thread_post_size-1) {
        var previous_post = post.eq(this.current_post);

        this.current_post=this.thread_post_size-1;
        current_post = post.eq(this.current_post);

        previous_post.removeClass('selected-post');
        current_post.addClass('selected-post');
    }

    if (this.first_keypress) {
        current_post.addClass('selected-post');
    }
    jQuery(window).scrollTop(current_post.offset().top);
    this.first_keypress = false;
};

HotKeyManager.prototype.anchorThread = function() {
    if (findCurrentPage() != 'showthread.php') {
        return;
    } else if (this.current_post == -1) {
        return;
    }

    this.first_keypress = false;
    var post = jQuery('div#thread > table.post').eq(this.current_post);
    post.addClass('selected-post');

    jQuery(window).scrollTop(post.offset().top);
};

HotKeyManager.prototype.toggleSignatures = function() {
    jQuery('p.signature').each(function() {
        if (jQuery(this).css('display') == 'none')
            jQuery(this).css('display','');
        else
            jQuery(this).css('display','none');
    });
};

HotKeyManager.prototype.quoteCurrentPost = function() {
    if (this.current_post == -1) {
        return;
    }
    if (!this.quickReply)
        return;

    var current_post = jQuery('div#thread > table.post').eq(this.current_post);
    var postid = current_post.attr('id').substr(4);

    this.quickReply.appendQuote(postid);
    this.quickReply.show();
};

HotKeyManager.prototype.editCurrentPost = function() {
    if (this.current_post == -1) {
        return;
    }
    if (!this.quickReply)
        return;

    var current_post = jQuery('div#thread > table.post').eq(this.current_post);
    var postid = current_post.attr('id').substr(4);

    this.quickReply.editPost(postid);
    this.quickReply.show();
};

HotKeyManager.prototype.displayQuickReply = function() {
    if (!this.quickReply)
        return;
    if (findCurrentPage() == 'showthread.php') {
        this.quickReply.show();
    }
};

HotKeyManager.prototype.hideQuickReply = function() {
    if (!this.quickReply)
        return;
    if (findCurrentPage() == 'showthread.php') {
        this.quickReply.hide();
    }
};
<|endoftext|>