<commit_before>var Reflux = require('reflux');
var initialData = require('../data.js');
var SurveyActions = require('../actions/SurveyActions');
var ItemTypes = require('../components/ItemTypes');
var Immutable = require('immutable');
var AlertTypes = require('../components/AlertTypes');

// a set of option texts - helps in generating suggestions
var _optionsSet = Immutable.OrderedSet();

// initialize question and option map which will help in
// faster retrieval of associated blocks and questions.
var _questionMap = Immutable.Map();     // questionId => blockId
var _optionMap = Immutable.Map();       // optionId   => questionId

// CONSTS
const ALERT_TIMEOUT = 5 * 1000; // toggles how quickly the alert hides

// mananging history
var _history = [];

var SurveyStore = Reflux.createStore({
    listenables: [SurveyActions],
    data: {
        surveyData: Immutable.List(),
        modalState: Immutable.Map({
            dropTargetID: null,
            isOpen: false
        }),
        alertState: Immutable.Map({
            msg: '',
            level: AlertTypes.INFO,
            visible: false
        })
    },
    // called when the app component is loaded
    init() {
        this.listenTo(SurveyActions.load, () => {
            var data = Immutable.fromJS(initialData);
            this.updateSurveyData(data, true);
        });
    },
    getInitialState() {
        return {
            surveyData: this.data.surveyData,
            modalState: this.data.modalState,
            alertState: this.data.alertState
        }
    },
    /**
     * Updates the survey data as the args provided. Triggers refresh.
     * Stores prev state in history, if second param is true
     * @param data surveydata
     * @param cache - boolean
     */
    updateSurveyData(data, cache = false) {
        if (cache) {
            _history.push({
                data        : this.data.surveyData,
                optionMap   : _optionMap.toJS(),
                questionMap : _questionMap.toJS()
            });
        }
        this.data.surveyData = data
        this.trigger(this.data);
    },
    // Returns the set (unique list) of options.
    getOptionsSet() {
        return _optionsSet;
    },
    getNewId(type) {
        var prefix;
        if (type === ItemTypes.QUESTION) {
            prefix = "q"
        } else if (type === ItemTypes.OPTION) {
            prefix = "o"
        } else {
            prefix = "b"
        }
        return `${prefix}_${Math.floor((Math.random() * 99999) + 1)}`
    },
    /**
     * Runs when the blockDropped action is called by the view.
     * Adds a new block to the end of the survey object.
     */
    onBlockDropped() {
        var survey = this.data.surveyData;
        var newBlock = Immutable.fromJS({
            id: this.getNewId(ItemTypes.BLOCK),
            questions: [],
            subblocks: [],
            randomizable: true,
            ordering: false
        });
        this.updateSurveyData(survey.push(newBlock), true);
        SurveyActions.showAlert("New block added.", AlertTypes.SUCCESS);
    },
    /**
     * Runs when the questionDropped action is called by the view.
     * Adds a question to the block who's id is provided as param
     * @param questionObj A POJO containing data the for the new question.
     * with the following keys - parentID, qtext, config
     */
    onQuestionDropped(questionObj) {
        var newQuestion = Immutable.fromJS({
            id: this.getNewId(ItemTypes.QUESTION),
            qtext: questionObj.qtext,
            options: [],
            ordering: questionObj.ordering,
            freetext: questionObj.freetext,
            exclusive: questionObj.exclusive
        });

        var index = this.getBlockIndex(questionObj.parentID);
        var newSurvey = this.data.surveyData.updateIn([index, 'questions'], list =>
            list.push(newQuestion)
        );

        // update and cache
        this.updateSurveyData(newSurvey, true);

        // update question map with new question
        var block = this.data.surveyData.get(index);
        _questionMap = _questionMap.set(newQuestion.get('id'), block.get('id'));

        SurveyActions.showAlert("New Question added.", AlertTypes.SUCCESS);
    },
    /**
     * Runs when the optionAdded action is called by the view.
     * Adds an option with text as otext to the question whose id is provided as an argument.
     * @param questionId (int) of the question to which the option will be added.
     * @param otext (string) the text of the option to be added
     */
    onOptionAdded(questionId, otext) {
        // template for new Option
        var newOption = Immutable.Map({
            id: this.getNewId(ItemTypes.OPTION),
            otext: otext
        });

        var survey = this.data.surveyData;
        var blockIndex = this.getBlockIndex(_questionMap.get(questionId));
        var block = survey.get(blockIndex);
        var index = this.getQuestionIndex(questionId, block);
        var newSurvey = survey.updateIn(
            [blockIndex, 'questions', index, 'options'],
            list => list.push(newOption)
        );

        // update the option map and options set
        _optionMap = _optionMap.set(newOption.get('id'), block.getIn(['questions', index, 'id']));
        _optionsSet = _optionsSet.add(otext);

        this.updateSurveyData(newSurvey, true);
    },
    /**
     * Run when the action toggleModal is called by the view
     * @param modalType - Refer to the type of object that was dropped
     * @param dropTargetID - Refers to the ID on which the object was dropped
     */
    onToggleModal(modalType, dropTargetID) {
        var modalState = this.data.modalState;

        // TODO: this handles the modal for question separately, although
        // this is not really required. deal with it later.
        if (modalType === ItemTypes.QUESTION) {
            modalState = modalState.set('isOpen', !modalState.get('isOpen'));
        }

        // sets the correct dropTarget to pass down to component
        this.data.modalState = modalState.set('dropTargetID', dropTargetID);
        this.trigger(this.data);
    },
    /**
     * Run when the action showAlert is called. Responsible for displaying
     * alert in the app
     * @param msg - the msg to be displayed
     * @param level - the level. defaults to 'info'. See Bootstrap alerts for more.
     */
    onShowAlert(msg, level=AlertTypes.INFO) {
        this.data.alertState = Immutable.Map({
            msg: msg,
            level: level,
            visible: true
        });
        this.trigger(this.data);

        // Hides the alert box
        setTimeout(function(self) {
            self.data.alertState = self.data.alertState.set('visible', false);
            self.trigger(self.data);
        }, ALERT_TIMEOUT, this);
    },
    /**
     * Called when the downloadSurvey action is called.
     * Logs the survey object to the console.
     */
    onDownloadSurvey() {
        var survey = { survey: this.data.surveyData.toJS() };
        console.log("Survey:", survey);
        SurveyActions.showAlert("Survey logged in your Dev console", AlertTypes.INFO);
    },
    /**
     * Returns the index of the block
     * @param id - id of the block
     */
    getBlockIndex(blockId) {
        return this.data.surveyData.findIndex(b => b.get('id') === blockId);
    },
    /**
     * Returns the index of a question in a block
     * @param id - id of the question
     * @param block - obj (Immutable.Map) of the container block
     */
    getQuestionIndex(questionId, block) {
        return block.get('questions').findIndex(q => q.get('id') === questionId);
    },
    /**
     * Called when the toggleParam action is called.
     * Toggles the property on the item.
     * @param itemType - type of Item the toggle button is clicked. one of ItemTypes
     * @param itemId - Id of the item for which toggle button is clicked
     * @param toggleName - string name of property that is toggled.
     */
    onToggleParam(itemType, itemId, toggleName) {
        var survey = this.data.surveyData;

        // handle the case when a param on a block is toggled
        if (itemType === ItemTypes.BLOCK) {
            let index = this.getBlockIndex(itemId);
            let newSurvey = survey.update(index, b => b.set(toggleName, !b.get(toggleName)));
            this.updateSurveyData(newSurvey);
        }

        // handle the case when a param on a question is toggled
        else if (itemType === ItemTypes.QUESTION) {
            let blockIndex = this.getBlockIndex(_questionMap.get(itemId));
            let block = survey.get(blockIndex);
            let index = this.getQuestionIndex(itemId, block);
            let newSurvey = survey.updateIn([blockIndex, 'questions', index], q =>
                q.set(toggleName, !q.get(toggleName))
            );
            this.updateSurveyData(newSurvey);
        }

        // throw exception
        else {
            throw new Error("Not a valid item type");
        }
    },
    /**
     * Called when an item has to be deleted.
     * @param itemType - refers to the type of item to be deleted. One of ItemTypes.
     * @param itemId - Id of item to be deleted.
     */
    onItemDelete(itemType, itemId) {
        var survey = this.data.surveyData;

        // handle block delete
        if (itemType === ItemTypes.BLOCK) {
            let index = this.getBlockIndex(itemId);
            let newSurvey = survey.delete(index);

            this.updateSurveyData(newSurvey, true);

            // delete the mapping of question and options
            survey.getIn([index, 'questions']).forEach(q => {
                _questionMap = _questionMap.delete(q.get('id'));
                q.get('options').forEach(o => {
                    _optionMap = _optionMap.delete(o.get('id'))
                })
            });

            SurveyActions.showAlert("Block deleted successfully.", AlertTypes.SUCCESS);
        }

        // handle question delete
        else if (itemType === ItemTypes.QUESTION) {
            let blockIndex = this.getBlockIndex(_questionMap.get(itemId));
            let block = survey.get(blockIndex);
            let index = this.getQuestionIndex(itemId, block);
            let newSurvey = survey.deleteIn([blockIndex, 'questions', index]);

            // update and cache
            this.updateSurveyData(newSurvey, true);

            // delete the mapping of the question and its options
            _questionMap = _questionMap.delete(itemId);
            block.getIn(['questions', index, 'options']).forEach(o =>
                _optionMap = _optionMap.delete(o.get('id'))
            );

            SurveyActions.showAlert("Question deleted successfully.", AlertTypes.SUCCESS);
        }

        // handle option delete
        else if (itemType === ItemTypes.OPTION) {
            let questionId = _optionMap.get(itemId);
            let blockIndex = this.getBlockIndex(_questionMap.get(questionId));
            let block = survey.get(blockIndex);
            let quesIndex = this.getQuestionIndex(questionId, block);
            let options = block.getIn(['questions', quesIndex, 'options']);
            let optionIndex = options.findIndex(op => op.get('id') === itemId);
            let newSurvey = survey.deleteIn([blockIndex, 'questions', quesIndex, 'options', optionIndex]);

            // delete the mapping
            _optionMap = _optionMap.delete(itemId);
            this.updateSurveyData(newSurvey);
        }

        // throw exception
        else {
            throw new Error("Not a valid item type");
        }
    },
    /**
     * Called when the question text is edited. Sets qtext to new value.
     * @param text - new text value
     * @param questionId - id of the question that needs to be changed
     */
    onSaveEditText(text, questionId) {
        var survey = this.data.surveyData;
        var blockIndex = this.getBlockIndex(_questionMap.get(questionId));
        var block = survey.get(blockIndex);
        var index = this.getQuestionIndex(questionId, block);
        var newSurvey = survey.updateIn([blockIndex, 'questions', index], q =>
            q.set('qtext', text)
        );
        this.updateSurveyData(newSurvey, true);
    },
    onUndoSurvey() {
        // hide the alert
        this.data.alertState = this.data.alertState.set('visible', false);

        // retrieve cached data
        var { data, optionMap, questionMap } = _history.pop();
        _questionMap = Immutable.Map(questionMap);
        _optionMap = Immutable.Map(optionMap);
        this.updateSurveyData(data);
    }
});

module.exports = SurveyStore;
<commit_msg>missed out comments on a function<commit_after>var Reflux = require('reflux');
var initialData = require('../data.js');
var SurveyActions = require('../actions/SurveyActions');
var ItemTypes = require('../components/ItemTypes');
var Immutable = require('immutable');
var AlertTypes = require('../components/AlertTypes');

// a set of option texts - helps in generating suggestions
var _optionsSet = Immutable.OrderedSet();

// initialize question and option map which will help in
// faster retrieval of associated blocks and questions.
var _questionMap = Immutable.Map();     // questionId => blockId
var _optionMap = Immutable.Map();       // optionId   => questionId

// CONSTS
const ALERT_TIMEOUT = 5 * 1000; // toggles how quickly the alert hides

// mananging history
var _history = [];

var SurveyStore = Reflux.createStore({
    listenables: [SurveyActions],
    data: {
        surveyData: Immutable.List(),
        modalState: Immutable.Map({
            dropTargetID: null,
            isOpen: false
        }),
        alertState: Immutable.Map({
            msg: '',
            level: AlertTypes.INFO,
            visible: false
        })
    },
    // called when the app component is loaded
    init() {
        this.listenTo(SurveyActions.load, () => {
            var data = Immutable.fromJS(initialData);
            this.updateSurveyData(data, true);
        });
    },
    getInitialState() {
        return {
            surveyData: this.data.surveyData,
            modalState: this.data.modalState,
            alertState: this.data.alertState
        }
    },
    /**
     * Updates the survey data as the args provided. Triggers refresh.
     * Stores prev state in history, if second param is true
     * @param data surveydata
     * @param cache - boolean
     */
    updateSurveyData(data, cache = false) {
        if (cache) {
            _history.push({
                data        : this.data.surveyData,
                optionMap   : _optionMap.toJS(),
                questionMap : _questionMap.toJS()
            });
        }
        this.data.surveyData = data
        this.trigger(this.data);
    },
    // Returns the set (unique list) of options.
    getOptionsSet() {
        return _optionsSet;
    },
    getNewId(type) {
        var prefix;
        if (type === ItemTypes.QUESTION) {
            prefix = "q"
        } else if (type === ItemTypes.OPTION) {
            prefix = "o"
        } else {
            prefix = "b"
        }
        return `${prefix}_${Math.floor((Math.random() * 99999) + 1)}`
    },
    /**
     * Runs when the blockDropped action is called by the view.
     * Adds a new block to the end of the survey object.
     */
    onBlockDropped() {
        var survey = this.data.surveyData;
        var newBlock = Immutable.fromJS({
            id: this.getNewId(ItemTypes.BLOCK),
            questions: [],
            subblocks: [],
            randomizable: true,
            ordering: false
        });
        this.updateSurveyData(survey.push(newBlock), true);
        SurveyActions.showAlert("New block added.", AlertTypes.SUCCESS);
    },
    /**
     * Runs when the questionDropped action is called by the view.
     * Adds a question to the block who's id is provided as param
     * @param questionObj A POJO containing data the for the new question.
     * with the following keys - parentID, qtext, config
     */
    onQuestionDropped(questionObj) {
        var newQuestion = Immutable.fromJS({
            id: this.getNewId(ItemTypes.QUESTION),
            qtext: questionObj.qtext,
            options: [],
            ordering: questionObj.ordering,
            freetext: questionObj.freetext,
            exclusive: questionObj.exclusive
        });

        var index = this.getBlockIndex(questionObj.parentID);
        var newSurvey = this.data.surveyData.updateIn([index, 'questions'], list =>
            list.push(newQuestion)
        );

        // update and cache
        this.updateSurveyData(newSurvey, true);

        // update question map with new question
        var block = this.data.surveyData.get(index);
        _questionMap = _questionMap.set(newQuestion.get('id'), block.get('id'));

        SurveyActions.showAlert("New Question added.", AlertTypes.SUCCESS);
    },
    /**
     * Runs when the optionAdded action is called by the view.
     * Adds an option with text as otext to the question whose id is provided as an argument.
     * @param questionId (int) of the question to which the option will be added.
     * @param otext (string) the text of the option to be added
     */
    onOptionAdded(questionId, otext) {
        // template for new Option
        var newOption = Immutable.Map({
            id: this.getNewId(ItemTypes.OPTION),
            otext: otext
        });

        var survey = this.data.surveyData;
        var blockIndex = this.getBlockIndex(_questionMap.get(questionId));
        var block = survey.get(blockIndex);
        var index = this.getQuestionIndex(questionId, block);
        var newSurvey = survey.updateIn(
            [blockIndex, 'questions', index, 'options'],
            list => list.push(newOption)
        );

        // update the option map and options set
        _optionMap = _optionMap.set(newOption.get('id'), block.getIn(['questions', index, 'id']));
        _optionsSet = _optionsSet.add(otext);

        this.updateSurveyData(newSurvey, true);
    },
    /**
     * Run when the action toggleModal is called by the view
     * @param modalType - Refer to the type of object that was dropped
     * @param dropTargetID - Refers to the ID on which the object was dropped
     */
    onToggleModal(modalType, dropTargetID) {
        var modalState = this.data.modalState;

        // TODO: this handles the modal for question separately, although
        // this is not really required. deal with it later.
        if (modalType === ItemTypes.QUESTION) {
            modalState = modalState.set('isOpen', !modalState.get('isOpen'));
        }

        // sets the correct dropTarget to pass down to component
        this.data.modalState = modalState.set('dropTargetID', dropTargetID);
        this.trigger(this.data);
    },
    /**
     * Run when the action showAlert is called. Responsible for displaying
     * alert in the app
     * @param msg - the msg to be displayed
     * @param level - the level. defaults to 'info'. See Bootstrap alerts for more.
     */
    onShowAlert(msg, level=AlertTypes.INFO) {
        this.data.alertState = Immutable.Map({
            msg: msg,
            level: level,
            visible: true
        });
        this.trigger(this.data);

        // Hides the alert box
        setTimeout(function(self) {
            self.data.alertState = self.data.alertState.set('visible', false);
            self.trigger(self.data);
        }, ALERT_TIMEOUT, this);
    },
    /**
     * Called when the downloadSurvey action is called.
     * Logs the survey object to the console.
     */
    onDownloadSurvey() {
        var survey = { survey: this.data.surveyData.toJS() };
        console.log("Survey:", survey);
        SurveyActions.showAlert("Survey logged in your Dev console", AlertTypes.INFO);
    },
    /**
     * Returns the index of the block
     * @param id - id of the block
     */
    getBlockIndex(blockId) {
        return this.data.surveyData.findIndex(b => b.get('id') === blockId);
    },
    /**
     * Returns the index of a question in a block
     * @param id - id of the question
     * @param block - obj (Immutable.Map) of the container block
     */
    getQuestionIndex(questionId, block) {
        return block.get('questions').findIndex(q => q.get('id') === questionId);
    },
    /**
     * Called when the toggleParam action is called.
     * Toggles the property on the item.
     * @param itemType - type of Item the toggle button is clicked. one of ItemTypes
     * @param itemId - Id of the item for which toggle button is clicked
     * @param toggleName - string name of property that is toggled.
     */
    onToggleParam(itemType, itemId, toggleName) {
        var survey = this.data.surveyData;

        // handle the case when a param on a block is toggled
        if (itemType === ItemTypes.BLOCK) {
            let index = this.getBlockIndex(itemId);
            let newSurvey = survey.update(index, b => b.set(toggleName, !b.get(toggleName)));
            this.updateSurveyData(newSurvey);
        }

        // handle the case when a param on a question is toggled
        else if (itemType === ItemTypes.QUESTION) {
            let blockIndex = this.getBlockIndex(_questionMap.get(itemId));
            let block = survey.get(blockIndex);
            let index = this.getQuestionIndex(itemId, block);
            let newSurvey = survey.updateIn([blockIndex, 'questions', index], q =>
                q.set(toggleName, !q.get(toggleName))
            );
            this.updateSurveyData(newSurvey);
        }

        // throw exception
        else {
            throw new Error("Not a valid item type");
        }
    },
    /**
     * Called when an item has to be deleted.
     * @param itemType - refers to the type of item to be deleted. One of ItemTypes.
     * @param itemId - Id of item to be deleted.
     */
    onItemDelete(itemType, itemId) {
        var survey = this.data.surveyData;

        // handle block delete
        if (itemType === ItemTypes.BLOCK) {
            let index = this.getBlockIndex(itemId);
            let newSurvey = survey.delete(index);

            this.updateSurveyData(newSurvey, true);

            // delete the mapping of question and options
            survey.getIn([index, 'questions']).forEach(q => {
                _questionMap = _questionMap.delete(q.get('id'));
                q.get('options').forEach(o => {
                    _optionMap = _optionMap.delete(o.get('id'))
                })
            });

            SurveyActions.showAlert("Block deleted successfully.", AlertTypes.SUCCESS);
        }

        // handle question delete
        else if (itemType === ItemTypes.QUESTION) {
            let blockIndex = this.getBlockIndex(_questionMap.get(itemId));
            let block = survey.get(blockIndex);
            let index = this.getQuestionIndex(itemId, block);
            let newSurvey = survey.deleteIn([blockIndex, 'questions', index]);

            // update and cache
            this.updateSurveyData(newSurvey, true);

            // delete the mapping of the question and its options
            _questionMap = _questionMap.delete(itemId);
            block.getIn(['questions', index, 'options']).forEach(o =>
                _optionMap = _optionMap.delete(o.get('id'))
            );

            SurveyActions.showAlert("Question deleted successfully.", AlertTypes.SUCCESS);
        }

        // handle option delete
        else if (itemType === ItemTypes.OPTION) {
            let questionId = _optionMap.get(itemId);
            let blockIndex = this.getBlockIndex(_questionMap.get(questionId));
            let block = survey.get(blockIndex);
            let quesIndex = this.getQuestionIndex(questionId, block);
            let options = block.getIn(['questions', quesIndex, 'options']);
            let optionIndex = options.findIndex(op => op.get('id') === itemId);
            let newSurvey = survey.deleteIn([blockIndex, 'questions', quesIndex, 'options', optionIndex]);

            // delete the mapping
            _optionMap = _optionMap.delete(itemId);
            this.updateSurveyData(newSurvey);
        }

        // throw exception
        else {
            throw new Error("Not a valid item type");
        }
    },
    /**
     * Called when the question text is edited. Sets qtext to new value.
     * @param text - new text value
     * @param questionId - id of the question that needs to be changed
     */
    onSaveEditText(text, questionId) {
        var survey = this.data.surveyData;
        var blockIndex = this.getBlockIndex(_questionMap.get(questionId));
        var block = survey.get(blockIndex);
        var index = this.getQuestionIndex(questionId, block);
        var newSurvey = survey.updateIn([blockIndex, 'questions', index], q =>
            q.set('qtext', text)
        );
        this.updateSurveyData(newSurvey, true);
    },
    /**
     * Called when the undoSurvey action is triggered. Responsible for
     * setting global state to last _history item.
     */
    onUndoSurvey() {
        // hide the alert
        this.data.alertState = this.data.alertState.set('visible', false);

        // retrieve cached data
        var { data, optionMap, questionMap } = _history.pop();
        _questionMap = Immutable.Map(questionMap);
        _optionMap = Immutable.Map(optionMap);
        this.updateSurveyData(data);
    }
});

module.exports = SurveyStore;
<|endoftext|>


<commit_before>var autosize = require("autosize");
var constants = require("../util/constants");
var CurrentUserModel = require("../stores/currentUser");
var View = require("handlebones").View;
var template = require("../tmpl/comment-form");
var CommentModel = require("../models/comment");
var CommentView = require("../views/commentView");
var eb = require("../eventBus");
var Handlebones = require("handlebones");
var PolisFacebookUtils = require('../util/facebookButton');
var ProfilePicView = require('../views/profilePicView');
var serialize = require("../util/serialize");
var Utils = require("../util/utils");

var CHARACTER_LIMIT = constants.CHARACTER_LIMIT;

var CommentsByMeView = Handlebones.CollectionView.extend({
  modelView: CommentView
});

function reject() {
  return $.Deferred().reject();
}
function resolve() {
  return $.Deferred().resolve();
}

module.exports = Handlebones.ModelView.extend({
  name: "comment-form",
  template: template,

  // needed to prevent double submissions, which are annoying because they trigger a duplicate alert
  buttonActive: true,

  context: function() {
    var ctx = Handlebones.ModelView.prototype.context.apply(this, arguments);
    ctx = _.extend(ctx, this, this.model&&this.model.attributes);
    ctx.is_active = this.parent.model.get("is_active");
    ctx.shouldAutofocusOnTextarea = Utils.shouldFocusOnTextareaWhenWritePaneShown();
    ctx.hasTwitter = userObject.hasTwitter;
    ctx.hasFacebook = userObject.hasFacebook;
    return ctx;
  },
  hideMessage: function(id) {
    this.$(id).hide();
    // if there are no other warnings/tips showing, show the default one
    if (this.$(".protip:visible").length === 0) {
      this.$("#not_a_reply_message").show();
    }
  },
  showMessage: function(id) {
    // since there are now other warnings/tips showing, hide the default one
    this.$("#not_a_reply_message").hide();
    this.$(id).show();
  },
  updateOneIdeaPerCommentMessage: function(formText) {
    // TODO I18N
    // Tests to see if there is non-punctuation that follows the end of a sentence.
    if ((formText||"").match(/[\?\.\!].*[a-zA-Z0-9]+/)) {
      this.showMessage("#one_idea_per_comment_message");
    } else {
      this.hideMessage("#one_idea_per_comment_message");
    }
  },
  updateCommentNotQuestionAlert: function(formText) {
    if (formText.indexOf("?") >= 0) {
      this.showMessage("#commentNotQuestionAlert");
    } else {
      this.hideMessage("#commentNotQuestionAlert");
    }
  },
  textChange: function() {
    var formText = $(arguments[0].target).val();
    var len = formText.length;
    var remaining = CHARACTER_LIMIT - len;
    var txt;
    if (remaining < 0) {
      // txt = "- " + remaining;
      txt = remaining;
      this.$("#commentCharCount").css("color", "red");
      this.$("#comment_button").attr("disabled", "disabled");
      this.$("#comment_button").css("opacity", 0.3);
      this.showMessage("#commentTooLongAlert");
      this.buttonActive = false;
    } else if (remaining > 0) {
      txt = /*"+ " +*/ remaining;
      this.$("#commentCharCount").css("color", "gray");
      this.$("#comment_button").attr("disabled", null);
      this.$("#comment_button").css("opacity", 1);
      this.hideMessage("#commentTooLongAlert");
      this.buttonActive = true;
    } else {
      txt = remaining;
      this.$("#commentCharCount").css("color", "gray");
      this.$("#comment_button").attr("disabled", null);
      this.$("#comment_button").css("opacity", 1);
      this.hideMessage("#commentTooLongAlert");
      this.buttonActive = true;
    }
    this.$("#commentCharCount").text(txt);
    this.updateOneIdeaPerCommentMessage(formText);
    this.updateCommentNotQuestionAlert(formText);
    eb.trigger(eb.interacted);
  },
  showFormControls: function() {
    // this.$(".alert").hide();
    this.$(".comment_form_control_hideable").show();
  },
  hideFormControls: function() {
    this.$(".comment_form_control_hideable").hide();
    this.$("#commentCharCount").text("");
  },
  events: {
    "focus #comment_form_textarea": function(e) { // maybe on keyup ?
      this.showFormControls();
    },
    "blur #comment_form_textarea": function(e) {
      var txt = this.$("#comment_form_textarea").val();
      if (!txt || !txt.length) {
        this.hideFormControls();
      }
    },
    "change #comment_form_textarea": "textChange",
    "keyup #comment_form_textarea": "textChange",
    "paste #comment_form_textarea": "textChange",
    "click #facebookButtonCommentForm" : "facebookClicked",
    "click #twitterButtonCommentForm" : "twitterClicked",
    "click #comment_button": function(e){
      var that = this;
      e.preventDefault();

      function doSubmitComment() {
        if (that.buttonActive) {
          that.buttonActive = false;
          console.log("BUTTON ACTIVE FALSE");
          serialize(that, function(attrs){
            that.participantCommented(attrs).then(function() {
              that.$("#comment_form_textarea").val("");
              that.hideFormControls();
            }).always(function() {
              that.buttonActive = true;
              console.log("BUTTON ACTIVE TRUE");
            });
          });
        }
      }

      var hasSocial = window.userObject.hasFacebook || window.userObject.hasTwitter;
      if (hasSocial) {
        doSubmitComment();
      } else {
        this.showSocialAuthChoices();
      }
    }
  },
  onAuthSuccess: function() {
    $("#socialButtonsCommentForm").hide();
    $("#comment_form_controls").show();
  },
  facebookClicked: function(e) {
    e.preventDefault();
    var that = this;
    PolisFacebookUtils.connect().then(function() {
      // wait a bit for new cookies to be ready, or something, then submit comment.
      // setTimeout(doSubmitComment, 1000);
      that.onAuthSuccess();
      CurrentUserModel.update();
    }, function(err) {
      // alert("facebook error");
    });
  },
  twitterClicked: function(e) {
    var that = this;
    e.preventDefault();

    eb.on(eb.twitterConnected, function() {
      that.onAuthSuccess();
      CurrentUserModel.update();
    });
    // open a new window where the twitter auth screen will show.
    // that window will redirect back to a simple page that calls window.opener.twitterStatus("ok")
    var params = 'location=0,status=0,width=800,height=400';
    window.open(document.location.origin + "/api/v3/twitterBtn?dest=/twitterAuthReturn", 'twitterWindow', params);
  },
  showSocialAuthChoices: function() {
    $("#comment_form_controls").hide();
    $("#socialButtonsCommentForm").show();
  },
  participantCommented: function(attrs) {
    var that = this; //that = the view
    attrs.pid = this.pid;
    attrs.conversation_id = this.conversation_id;
    attrs.vote = constants.REACTIONS.AGREE; // participants' comments are automatically agreed to. Needed for now since math assumes every comment has at least one vote.

    if (/^\s*$/.exec(attrs.txt)) {
      alert("Comment is empty");
      return reject();
    }
    if (attrs.txt.length > 997) {
      alert("Comment is too long");
      return reject();
    }

    // DEMO_MODE
    if (this.pid < 0) {
      that.showMessage("#commentSentDemoAlert");
      that.trigger("commentSubmitted");
      that.updateCollection();
      return resolve();
    }

    var comment = new CommentModel(attrs);
    var promise = comment.save();
    if (!promise) {
      return reject();
    } else {
      promise.then(function() {
        that.trigger("commentSubmitted"); // view.trigger
        that.updateCollection();
        that.showMessage("#commentSentAlert");
      }, function(args) {
        if (!args || !args.length || !args[0].length) {
          alert("failed to send");
          return;
        }
        var err = args[0][0];
        if (err.status === 409) {

          // that.model.set({
          //   error: "Duplicate!",
          //   errorExtra: "That comment already exists.",
          // });
          alert("Duplicate! That comment already exists.");
        } else if (err.responseText === "polis_err_conversation_is_closed"){

          // that.model.set({
          //   error: "This conversation is closed.",
          //   errorExtra: "No further commenting is allowed.",
          // });
          alert("This conversation is closed. No further commenting is allowed.");
        } else {

          // that.model.set({
          //   error: "Error sending comment.",
          //   errorExtra: "Please try again later.",
          // });
          alert("Error sending comment, please try again later.");
        }
      });
      return promise;
    }
  },
  updateCollection: function() {
    this.collection.fetch({
      data: $.param({
        conversation_id: this.conversation_id,
        pid: this.pid
      })
    });
  },
  initialize: function(options) {
    Handlebones.ModelView.prototype.initialize.apply(this, arguments);
    this.pid = options.pid;
    this.model = options.model;
    this.conversation_id = options.conversation_id;
    this.collection = options.collection;
    this.commentsByMeView = this.addChild(new CommentsByMeView({
      collection: options.collection
    }));

    this.profilePicView = this.addChild(new ProfilePicView({
      model: CurrentUserModel,
    }));

    this.listenTo(this, "render", function(){
      setTimeout(function() {
        autosize($("#comment_form_textarea"));
      },100);
    });

  },
});<commit_msg>delay fb twitter ready callback in case cookies not ready<commit_after>var autosize = require("autosize");
var constants = require("../util/constants");
var CurrentUserModel = require("../stores/currentUser");
var View = require("handlebones").View;
var template = require("../tmpl/comment-form");
var CommentModel = require("../models/comment");
var CommentView = require("../views/commentView");
var eb = require("../eventBus");
var Handlebones = require("handlebones");
var PolisFacebookUtils = require('../util/facebookButton');
var ProfilePicView = require('../views/profilePicView');
var serialize = require("../util/serialize");
var Utils = require("../util/utils");

var CHARACTER_LIMIT = constants.CHARACTER_LIMIT;

var CommentsByMeView = Handlebones.CollectionView.extend({
  modelView: CommentView
});

function reject() {
  return $.Deferred().reject();
}
function resolve() {
  return $.Deferred().resolve();
}

module.exports = Handlebones.ModelView.extend({
  name: "comment-form",
  template: template,

  // needed to prevent double submissions, which are annoying because they trigger a duplicate alert
  buttonActive: true,

  context: function() {
    var ctx = Handlebones.ModelView.prototype.context.apply(this, arguments);
    ctx = _.extend(ctx, this, this.model&&this.model.attributes);
    ctx.is_active = this.parent.model.get("is_active");
    ctx.shouldAutofocusOnTextarea = Utils.shouldFocusOnTextareaWhenWritePaneShown();
    ctx.hasTwitter = userObject.hasTwitter;
    ctx.hasFacebook = userObject.hasFacebook;
    return ctx;
  },
  hideMessage: function(id) {
    this.$(id).hide();
    // if there are no other warnings/tips showing, show the default one
    if (this.$(".protip:visible").length === 0) {
      this.$("#not_a_reply_message").show();
    }
  },
  showMessage: function(id) {
    // since there are now other warnings/tips showing, hide the default one
    this.$("#not_a_reply_message").hide();
    this.$(id).show();
  },
  updateOneIdeaPerCommentMessage: function(formText) {
    // TODO I18N
    // Tests to see if there is non-punctuation that follows the end of a sentence.
    if ((formText||"").match(/[\?\.\!].*[a-zA-Z0-9]+/)) {
      this.showMessage("#one_idea_per_comment_message");
    } else {
      this.hideMessage("#one_idea_per_comment_message");
    }
  },
  updateCommentNotQuestionAlert: function(formText) {
    if (formText.indexOf("?") >= 0) {
      this.showMessage("#commentNotQuestionAlert");
    } else {
      this.hideMessage("#commentNotQuestionAlert");
    }
  },
  textChange: function() {
    var formText = $(arguments[0].target).val();
    var len = formText.length;
    var remaining = CHARACTER_LIMIT - len;
    var txt;
    if (remaining < 0) {
      // txt = "- " + remaining;
      txt = remaining;
      this.$("#commentCharCount").css("color", "red");
      this.$("#comment_button").attr("disabled", "disabled");
      this.$("#comment_button").css("opacity", 0.3);
      this.showMessage("#commentTooLongAlert");
      this.buttonActive = false;
    } else if (remaining > 0) {
      txt = /*"+ " +*/ remaining;
      this.$("#commentCharCount").css("color", "gray");
      this.$("#comment_button").attr("disabled", null);
      this.$("#comment_button").css("opacity", 1);
      this.hideMessage("#commentTooLongAlert");
      this.buttonActive = true;
    } else {
      txt = remaining;
      this.$("#commentCharCount").css("color", "gray");
      this.$("#comment_button").attr("disabled", null);
      this.$("#comment_button").css("opacity", 1);
      this.hideMessage("#commentTooLongAlert");
      this.buttonActive = true;
    }
    this.$("#commentCharCount").text(txt);
    this.updateOneIdeaPerCommentMessage(formText);
    this.updateCommentNotQuestionAlert(formText);
    eb.trigger(eb.interacted);
  },
  showFormControls: function() {
    // this.$(".alert").hide();
    this.$(".comment_form_control_hideable").show();
  },
  hideFormControls: function() {
    this.$(".comment_form_control_hideable").hide();
    this.$("#commentCharCount").text("");
  },
  events: {
    "focus #comment_form_textarea": function(e) { // maybe on keyup ?
      this.showFormControls();
    },
    "blur #comment_form_textarea": function(e) {
      var txt = this.$("#comment_form_textarea").val();
      if (!txt || !txt.length) {
        this.hideFormControls();
      }
    },
    "change #comment_form_textarea": "textChange",
    "keyup #comment_form_textarea": "textChange",
    "paste #comment_form_textarea": "textChange",
    "click #facebookButtonCommentForm" : "facebookClicked",
    "click #twitterButtonCommentForm" : "twitterClicked",
    "click #comment_button": function(e){
      var that = this;
      e.preventDefault();

      function doSubmitComment() {
        if (that.buttonActive) {
          that.buttonActive = false;
          console.log("BUTTON ACTIVE FALSE");
          serialize(that, function(attrs){
            that.participantCommented(attrs).then(function() {
              that.$("#comment_form_textarea").val("");
              that.hideFormControls();
            }).always(function() {
              that.buttonActive = true;
              console.log("BUTTON ACTIVE TRUE");
            });
          });
        }
      }

      var hasSocial = window.userObject.hasFacebook || window.userObject.hasTwitter;
      if (hasSocial) {
        doSubmitComment();
      } else {
        this.showSocialAuthChoices();
      }
    }
  },
  onAuthSuccess: function() {
    $("#socialButtonsCommentForm").hide();
    $("#comment_form_controls").show();
  },
  facebookClicked: function(e) {
    e.preventDefault();
    var that = this;
    PolisFacebookUtils.connect().then(function() {
      // wait a bit for new cookies to be ready, or something, then submit comment.
      setTimeout(function() {
        that.onAuthSuccess();
        CurrentUserModel.update();
      }, 100);
    }, function(err) {
      // alert("facebook error");
    });
  },
  twitterClicked: function(e) {
    var that = this;
    e.preventDefault();

    eb.on(eb.twitterConnected, function() {
      // wait a bit for new cookies to be ready, or something, then submit comment.
      setTimeout(function() {
        that.onAuthSuccess();
        CurrentUserModel.update();
      }, 100);
    });
    // open a new window where the twitter auth screen will show.
    // that window will redirect back to a simple page that calls window.opener.twitterStatus("ok")
    var params = 'location=0,status=0,width=800,height=400';
    window.open(document.location.origin + "/api/v3/twitterBtn?dest=/twitterAuthReturn", 'twitterWindow', params);
  },
  showSocialAuthChoices: function() {
    $("#comment_form_controls").hide();
    $("#socialButtonsCommentForm").show();
  },
  participantCommented: function(attrs) {
    var that = this; //that = the view
    attrs.pid = this.pid;
    attrs.conversation_id = this.conversation_id;
    attrs.vote = constants.REACTIONS.AGREE; // participants' comments are automatically agreed to. Needed for now since math assumes every comment has at least one vote.

    if (/^\s*$/.exec(attrs.txt)) {
      alert("Comment is empty");
      return reject();
    }
    if (attrs.txt.length > 997) {
      alert("Comment is too long");
      return reject();
    }

    // DEMO_MODE
    if (this.pid < 0) {
      that.showMessage("#commentSentDemoAlert");
      that.trigger("commentSubmitted");
      that.updateCollection();
      return resolve();
    }

    var comment = new CommentModel(attrs);
    var promise = comment.save();
    if (!promise) {
      return reject();
    } else {
      promise.then(function() {
        that.trigger("commentSubmitted"); // view.trigger
        that.updateCollection();
        that.showMessage("#commentSentAlert");
      }, function(args) {
        if (!args || !args.length || !args[0].length) {
          alert("failed to send");
          return;
        }
        var err = args[0][0];
        if (err.status === 409) {

          // that.model.set({
          //   error: "Duplicate!",
          //   errorExtra: "That comment already exists.",
          // });
          alert("Duplicate! That comment already exists.");
        } else if (err.responseText === "polis_err_conversation_is_closed"){

          // that.model.set({
          //   error: "This conversation is closed.",
          //   errorExtra: "No further commenting is allowed.",
          // });
          alert("This conversation is closed. No further commenting is allowed.");
        } else {

          // that.model.set({
          //   error: "Error sending comment.",
          //   errorExtra: "Please try again later.",
          // });
          alert("Error sending comment, please try again later.");
        }
      });
      return promise;
    }
  },
  updateCollection: function() {
    this.collection.fetch({
      data: $.param({
        conversation_id: this.conversation_id,
        pid: this.pid
      })
    });
  },
  initialize: function(options) {
    Handlebones.ModelView.prototype.initialize.apply(this, arguments);
    this.pid = options.pid;
    this.model = options.model;
    this.conversation_id = options.conversation_id;
    this.collection = options.collection;
    this.commentsByMeView = this.addChild(new CommentsByMeView({
      collection: options.collection
    }));

    this.profilePicView = this.addChild(new ProfilePicView({
      model: CurrentUserModel,
    }));

    this.listenTo(this, "render", function(){
      setTimeout(function() {
        autosize($("#comment_form_textarea"));
      },100);
    });

  },
});<|endoftext|>


<commit_before>var app = app || {};

app.views = app.views || {};
app.views.utility = app.views.utility || {};

app.views.utility.Form = (function() {

	'use strict';

	return app.abstracts.BaseView.extend({

		// A template is required to display anything.
		template: null,

		events: {
			'change :input': 'process',
			'submit form': 'process',
			'click .form-field-action': 'onFormFieldAction',
		},

		inputs: [],

		initialize: function() {

			_.bindAll(this, 'process');
			this.process = _.throttle(this.process, 500, { leading: false });
			this.prepareInputs();
		},

		prepareInputs: function() {

			var inputs = _.result(this, 'inputs') || [];
			this.inputs = _.map(inputs, this.prepareInput, this);
		},

		prepareInput: function(input) {

			input = _.clone(input);
			input.id = input.id || input.name;
			var value = this.getInputValueOverride(input.name);
			if (!_.isUndefined(value)) {
				input.value = value;
			} else if (input.value) {
				input.value = _.result(input, 'value');
			} else {
				input.value = input.default;
			}
			switch (input.type) {
				case 'select':
					input.options = _.result(input, 'options') || [];
					input.options = _.map(input.options, function(option) {
						return {
							key: option.key,
							label: _.result(option, 'label'),
							selected: input.value === option.key,
						};
					});
					break;
			}
			input.visible = input.visible !== false;
			return input;
		},

		serializeData: function() {

			var data = app.abstracts.BaseView.prototype.serializeData.apply(this, arguments);
			data.inputs = this.inputs;
			return data;
		},

		onFormFieldAction: function(evt) {

			var $target = $(evt.target);
			var name = $target.attr('data-input');
			if (!name) return;
			var input = this.getInputByName(name);
			if (!input) return;
			var action = _.findWhere(input.actions || [], { name: $target.attr('data-action') });
			if (!action || !action.fn) return;
			var fn = _.bind(action.fn, this);
			var formData = this.getFormData();
			var value = formData[name];
			var $input = this.$(':input[name="' + name + '"]');

			fn(value, function(error, newValue) {

				if (error) {
					return app.mainView.showMessage(error);
				}

				if (newValue) {
					$input.val(newValue).trigger('change');
				}
			});
		},

		showErrors: function(validationErrors) {

			if (!_.isEmpty(validationErrors)) {
				_.each(validationErrors, function(validationError) {
					var $field = this.$(':input[name="' + validationError.field + '"]');
					var $row = $field.parents('.form-row').first();
					var $error = $row.find('.form-error');
					$field.addClass('error');
					$error.append($('<div/>', {
						class: 'form-error-message',
						text: validationError.error,
					}));
				}, this);
			}
		},

		clearErrors: function() {

			this.$('.form-error-message').remove();
			this.$('.form-row.error').removeClass('error');
			this.$(':input.error').removeClass('error');
		},

		getFormData: function() {

			return this.$('form').serializeJSON();
		},

		allRequiredFieldsFilledIn: function() {

			var formData = this.getFormData();
			return _.every(this.inputs, function(input) {
				return input.required !== true || !!formData[input.name];
			});
		},

		process: function(evt) {

			if (evt && evt.preventDefault) {
				evt.preventDefault();
			}

			this.clearErrors();

			var data = this.getFormData();

			// Set defaults.
			_.each(this.inputs, function(input) {
				if (_.isUndefined(data[input.name]) && !_.isUndefined(input.default)) {
					data[input.name] = input.default;
				}
			});

			this.validate(data, _.bind(function(error, validationErrors) {

				if (error) {
					return app.mainView.showMessage(error);
				}

				if (!_.isEmpty(validationErrors)) {
					this.showErrors(validationErrors);
					this.onValidationErrors(validationErrors);
				} else {
					// No validation errors.
					_.each(this.inputs, function(input) {
						switch (input.type) {
							case 'checkbox':
								// Force checkbox fields to have boolean values.
								data[input.name] = !!data[input.name];
								break;
						}
					});
					try {
						// Try saving.
						this.save(data);
					} catch (error) {
						app.log(error);
						return app.mainView.showMessage(error);
					}
				}

			}, this));
		},

		// `data` is an object containing the form data.
		// Execute the callback with an array of errors to indicate failure.
		// Execute the callback with no arguments to indicate success.
		validate: function(data, done) {

			async.map(this.inputs || [], _.bind(function(input, next) {

				var value = data[input.name];
				var errors = [];

				if (input.required && _.isEmpty(value)) {
					errors.push({
						field: input.name,
						error: app.i18n.t('form.field-required', {
							label: _.result(input, 'label')
						}),
					});
					return next(null, errors);
				}

				if (input.validate) {
					var validateFn = _.bind(input.validate, this);
					try {
						validateFn(value, data);
					} catch (error) {
						errors.push({
							field: input.name,
							error: error,
						});
					}
				}

				if (!input.validateAsync) {
					return next(null, errors);
				}

				try {
					var validateAsyncFn = _.bind(input.validateAsync, this);
					validateAsyncFn(value, data, function(error) {
						if (error) {
							errors.push({
								field: input.name,
								error: error,
							});
						}
						next(null, errors);
					});
				} catch (error) {
					next(error);
				}

			}, this), function(error, results) {

				if (error) {
					return done(error);
				}

				// Flatten the errors array.
				var errors = Array.prototype.concat.apply([], results);

				done(null, errors);
			});
		},

		getInputValueOverride: function(name) {

			return app.settings.get(name);
		},

		getInputDefaultValue: function(name) {

			var input = this.getInputByName(name);
			return input && _.result(input, 'default') || null;
		},

		getInputByName: function(name) {

			return _.findWhere(this.inputs, { name: name });
		},

		save: function(data) {

			app.settings.set(data);
		},

		onValidationErrors: function(validationErrors) {
			// Left empty intentionally.
			// Override as needed.
		},

	});

})();
<commit_msg>Provide missing form data during processing.<commit_after>var app = app || {};

app.views = app.views || {};
app.views.utility = app.views.utility || {};

app.views.utility.Form = (function() {

	'use strict';

	return app.abstracts.BaseView.extend({

		// A template is required to display anything.
		template: null,

		events: {
			'change :input': 'process',
			'submit form': 'process',
			'click .form-field-action': 'onFormFieldAction',
		},

		inputs: [],

		initialize: function() {

			_.bindAll(this, 'process');
			this.process = _.throttle(this.process, 500, { leading: false });
			this.prepareInputs();
		},

		prepareInputs: function() {

			var inputs = _.result(this, 'inputs') || [];
			this.inputs = _.map(inputs, this.prepareInput, this);
		},

		prepareInput: function(input) {

			input = _.clone(input);
			input.id = input.id || input.name;
			var value = this.getInputValueOverride(input.name);
			if (!_.isUndefined(value)) {
				input.value = value;
			} else if (input.value) {
				input.value = _.result(input, 'value');
			} else {
				input.value = input.default;
			}
			switch (input.type) {
				case 'select':
					input.options = _.result(input, 'options') || [];
					input.options = _.map(input.options, function(option) {
						return {
							key: option.key,
							label: _.result(option, 'label'),
							selected: input.value === option.key,
						};
					});
					break;
			}
			input.visible = input.visible !== false;
			return input;
		},

		serializeData: function() {

			var data = app.abstracts.BaseView.prototype.serializeData.apply(this, arguments);
			data.inputs = this.inputs;
			return data;
		},

		onFormFieldAction: function(evt) {

			var $target = $(evt.target);
			var name = $target.attr('data-input');
			if (!name) return;
			var input = this.getInputByName(name);
			if (!input) return;
			var action = _.findWhere(input.actions || [], { name: $target.attr('data-action') });
			if (!action || !action.fn) return;
			var fn = _.bind(action.fn, this);
			var formData = this.getFormData();
			var value = formData[name];
			var $input = this.$(':input[name="' + name + '"]');

			fn(value, function(error, newValue) {

				if (error) {
					return app.mainView.showMessage(error);
				}

				if (newValue) {
					$input.val(newValue).trigger('change');
				}
			});
		},

		showErrors: function(validationErrors) {

			if (!_.isEmpty(validationErrors)) {
				_.each(validationErrors, function(validationError) {
					var $field = this.$(':input[name="' + validationError.field + '"]');
					var $row = $field.parents('.form-row').first();
					var $error = $row.find('.form-error');
					$field.addClass('error');
					$error.append($('<div/>', {
						class: 'form-error-message',
						text: validationError.error,
					}));
				}, this);
			}
		},

		clearErrors: function() {

			this.$('.form-error-message').remove();
			this.$('.form-row.error').removeClass('error');
			this.$(':input.error').removeClass('error');
		},

		getFormData: function() {

			return this.$('form').serializeJSON();
		},

		allRequiredFieldsFilledIn: function() {

			var formData = this.getFormData();
			return _.every(this.inputs, function(input) {
				return input.required !== true || !!formData[input.name];
			});
		},

		process: function(evt) {

			if (evt && evt.preventDefault) {
				evt.preventDefault();
			}

			this.clearErrors();

			var data = this.getFormData();

			// Add missing data from inputs.
			_.each(this.inputs, function(input) {
				if (_.isUndefined(data[input.name]) && !_.isUndefined(input.value)) {
					data[input.name] = input.value;
				}
			});

			this.validate(data, _.bind(function(error, validationErrors) {

				if (error) {
					return app.mainView.showMessage(error);
				}

				if (!_.isEmpty(validationErrors)) {
					this.showErrors(validationErrors);
					this.onValidationErrors(validationErrors);
				} else {
					// No validation errors.
					_.each(this.inputs, function(input) {
						switch (input.type) {
							case 'checkbox':
								// Force checkbox fields to have boolean values.
								data[input.name] = !!data[input.name];
								break;
						}
					});
					try {
						// Try saving.
						this.save(data);
					} catch (error) {
						app.log(error);
						return app.mainView.showMessage(error);
					}
				}

			}, this));
		},

		// `data` is an object containing the form data.
		// Execute the callback with an array of errors to indicate failure.
		// Execute the callback with no arguments to indicate success.
		validate: function(data, done) {

			async.map(this.inputs || [], _.bind(function(input, next) {

				var value = data[input.name];
				var errors = [];

				if (input.required && _.isEmpty(value)) {
					errors.push({
						field: input.name,
						error: app.i18n.t('form.field-required', {
							label: _.result(input, 'label')
						}),
					});
					return next(null, errors);
				}

				if (input.validate) {
					var validateFn = _.bind(input.validate, this);
					try {
						validateFn(value, data);
					} catch (error) {
						errors.push({
							field: input.name,
							error: error,
						});
					}
				}

				if (!input.validateAsync) {
					return next(null, errors);
				}

				try {
					var validateAsyncFn = _.bind(input.validateAsync, this);
					validateAsyncFn(value, data, function(error) {
						if (error) {
							errors.push({
								field: input.name,
								error: error,
							});
						}
						next(null, errors);
					});
				} catch (error) {
					next(error);
				}

			}, this), function(error, results) {

				if (error) {
					return done(error);
				}

				// Flatten the errors array.
				var errors = Array.prototype.concat.apply([], results);

				done(null, errors);
			});
		},

		getInputValueOverride: function(name) {

			return app.settings.get(name);
		},

		getInputDefaultValue: function(name) {

			var input = this.getInputByName(name);
			return input && _.result(input, 'default') || null;
		},

		getInputByName: function(name) {

			return _.findWhere(this.inputs, { name: name });
		},

		save: function(data) {

			app.settings.set(data);
		},

		onValidationErrors: function(validationErrors) {
			// Left empty intentionally.
			// Override as needed.
		},

	});

})();
<|endoftext|>


<commit_before>'use strict';

angular.module('arachne.widgets.directives')

/**
 * Shows a heatmap constructed from the current searches
 * agg_geogrid facet. If the resources of the viewport fall below
 * a certain threshold, the map will show markers instead.
 *
 * @author Daniel de Oliveira
 * @author David Neugebauer
 */
.directive('con10tMap', ['$filter', 'searchService', 'mapService', 'heatmapPainter', 'placesService','placesPainter',
    function($filter, searchService, mapService, heatmapPainter, placesService, placesPainter) {

        function enrichQuery(currentQuery,scope) {

            if (!currentQuery.q) currentQuery.q = '*';

            if (!currentQuery.lat)
                if (scope.lat)  currentQuery.lat=scope.lat;
            if (!currentQuery.lng)
                if (scope.lng)  currentQuery.lng=scope.lng;
            if (!currentQuery.zoom)
                if (scope.zoom) currentQuery.zoom=scope.zoom;
            // Add a limit to the search if defined in the attribute
            if (scope.limit) {
                currentQuery.limit = scope.limit;
            }

            // Add restrictions for facets to the search if defined
            if (scope.facetsSelect) {

                var facet, len = scope.facetsSelect.length;

                for (var i = 0; i < len; i++) {

                    facet = scope.facetsSelect[i];

                    if (!currentQuery.hasFacet(facet.key)) {
                        currentQuery.facets.push(facet);
                    }
                }
            }

            // Add a further restriction for the catalog id
            if (scope.catalogId) {
                scope.catalogId = parseFloat(scope.catalogId);
                if (!currentQuery.hasFacet('catalogIds')) {
                    currentQuery.facets.push({key: 'catalogIds',value: scope.catalogId});
                }
            }
        }

        /**
         * Set the map's view:
         * fit bounds to entities only when zoom or coordinates are not explicitely
         * required by the url, else use the url settings
         */
        function fitViewToMarkers(zoom,lat,lng,places) {

            if ((places && places.length > 0) && !(zoom || lat || lng)) {

                var latLngs = places.map(function (place) {
                    if (place.location) {
                        return [place.location.lat, place.location.lon];
                    }
                });

                // Sets zoom, such that it is not too detailed when few entities are shown.
                // Zooms out a little bit to prevent all markers being hidden behind menus on the side.
                if (mapService.getMap().getZoom() > 9) {
                    mapService.getMap().fitBounds(latLngs).setZoom(9);
                } else if (mapService.getMap().getZoom() > 4) {
                    mapService.getMap().fitBounds(latLngs).setZoom(mapService.getMap().getZoom() - 1);
                } else
                    mapService.getMap().fitBounds(latLngs);
            }
        }

        return {
            restrict: 'A',
            scope: {
                catalogId: '@',
                overlays: '=?',
                baselayers: '=',
                defaultBaselayer: '=',	// "key"
                limit: '@?',		    // "500"
                facetsSelect: '=',		// {facetName: facetValue, ...}
                lat: '@',
                lng: '@',
                zoom: '@',
                clustered: '=',  		// true|false
                disableZoomControl: '@?', // true|false - disables the standard leaflet zoom control
            },
            // menu elements may appear in the transcluded html
            transclude: true,
            template: '<ng-transclude></ng-transclude>',
            link : function(scope,element) {

                if (scope.limit==undefined||scope.limit>500) scope.limit=500;

                var fitViewToMarkersAllowed=true;

                var cq = searchService.currentQuery();
                enrichQuery(cq,scope);

                function mapOnMove(entities) {

                    if (mapService.underLimit()) {

                        placesPainter.clear(); // TODO implement map.removeLayers

                        heatmapPainter.clear();
                        var places = placesService.makePlaces(entities,cq.bbox.split(","));

                        placesPainter.drawPlaces(
                            places, scope);

                        {
                            if (fitViewToMarkersAllowed) {
                                fitViewToMarkers(
                                    cq.zoom, cq.lat, cq.lng,
                                    places
                                );
                            }
                        }
                    }
                    else {
                        placesPainter.clear();
                        heatmapPainter.clear();

                        var bucketsToDraw = null;
                        var agg_geogrid = searchService.getFacet("agg_geogrid");
                        if (agg_geogrid) bucketsToDraw = agg_geogrid.values;
                        heatmapPainter.drawBuckets(cq.bbox, bucketsToDraw);
                    }

                    fitViewToMarkersAllowed=false;
                }

                mapService.setLimit(scope.limit);
                mapService.registerOnMoveListener(mapOnMove);

                mapService.initializeMap(
                    element.attr('id'),
                    {
                        zoomControl: !scope.disableZoomControl,
                        minZoom: 3
                    } // 3 is to prevent wrong bbox searches
                    // when the window is bigger than the world,
                );

                heatmapPainter.setMap(mapService.getMap());
                placesPainter.setMap(mapService.getMap());

                // Add baselayers and activate one, given by url
                // parameter "baselayer" or a default value
                mapService.setBaselayers(scope.baselayers);
                mapService.activateBaselayer(cq.baselayer || "osm");

                mapService.setOverlays(scope.overlays);
                mapService.activateOverlays(cq.getArrayParam('overlays')); // TODO this is undefined

                mapService.initializeView(cq.lat,cq.lng,cq.zoom);
            }
        }
    }
]);<commit_msg>Fix typo.<commit_after>'use strict';

angular.module('arachne.widgets.directives')

/**
 * Shows a heatmap constructed from the current searches
 * agg_geogrid facet. If the resources of the viewport fall below
 * a certain threshold, the map will show markers instead.
 *
 * @author Daniel de Oliveira
 * @author David Neugebauer
 */
.directive('con10tMap', ['$filter', 'searchService', 'mapService', 'heatmapPainter', 'placesService','placesPainter',
    function($filter, searchService, mapService, heatmapPainter, placesService, placesPainter) {

        function enrichQuery(currentQuery,scope) {

            if (!currentQuery.q) currentQuery.q = '*';

            if (!currentQuery.lat)
                if (scope.lat)  currentQuery.lat=scope.lat;
            if (!currentQuery.lng)
                if (scope.lng)  currentQuery.lng=scope.lng;
            if (!currentQuery.zoom)
                if (scope.zoom) currentQuery.zoom=scope.zoom;
            // Add a limit to the search if defined in the attribute
            if (scope.limit) {
                currentQuery.limit = scope.limit;
            }

            // Add restrictions for facets to the search if defined
            if (scope.facetsSelect) {

                var facet, len = scope.facetsSelect.length;

                for (var i = 0; i < len; i++) {

                    facet = scope.facetsSelect[i];

                    if (!currentQuery.hasFacet(facet.key)) {
                        currentQuery.facets.push(facet);
                    }
                }
            }

            // Add a further restriction for the catalog id
            if (scope.catalogId) {
                scope.catalogId = parseFloat(scope.catalogId);
                if (!currentQuery.hasFacet('catalogIds')) {
                    currentQuery.facets.push({key: 'catalogIds',value: scope.catalogId});
                }
            }
        }

        /**
         * Set the map's view:
         * fit bounds to entities only when zoom or coordinates are not explicitely
         * required by the url, else use the url settings
         */
        function fitViewToMarkers(zoom,lat,lng,places) {

            if ((places && places.length > 0) && !(zoom || lat || lng)) {

                var latLngs = places.map(function (place) {
                    if (place.location) {
                        return [place.location.lat, place.location.lon];
                    }
                });

                // Sets zoom, such that it is not too detailed when few entities are shown.
                // Zooms out a little bit to prevent all markers being hidden behind menus on the side.
                if (mapService.getMap().getZoom() > 9) {
                    mapService.getMap().fitBounds(latLngs).setZoom(9);
                } else if (mapService.getMap().getZoom() > 4) {
                    mapService.getMap().fitBounds(latLngs).setZoom(mapService.getMap().getZoom() - 1);
                } else
                    mapService.getMap().fitBounds(latLngs);
            }
        }

        return {
            restrict: 'A',
            scope: {
                catalogId: '@',
                overlays: '=?',
                baselayers: '=',
                defaultBaselayer: '=',	// "key"
                limit: '@?',		    // "500"
                facetsSelect: '=',		// {facetName: facetValue, ...}
                lat: '@',
                lng: '@',
                zoom: '@',
                clustered: '=',  		// true|false
                disableZoomControl: '@?' // true|false - disables the standard leaflet zoom control
            },
            // menu elements may appear in the transcluded html
            transclude: true,
            template: '<ng-transclude></ng-transclude>',
            link : function(scope,element) {

                if (scope.limit==undefined||scope.limit>500) scope.limit=500;

                var fitViewToMarkersAllowed=true;

                var cq = searchService.currentQuery();
                enrichQuery(cq,scope);

                function mapOnMove(entities) {

                    if (mapService.underLimit()) {

                        placesPainter.clear(); // TODO implement map.removeLayers

                        heatmapPainter.clear();
                        var places = placesService.makePlaces(entities,cq.bbox.split(","));

                        placesPainter.drawPlaces(
                            places, scope);

                        {
                            if (fitViewToMarkersAllowed) {
                                fitViewToMarkers(
                                    cq.zoom, cq.lat, cq.lng,
                                    places
                                );
                            }
                        }
                    }
                    else {
                        placesPainter.clear();
                        heatmapPainter.clear();

                        var bucketsToDraw = null;
                        var agg_geogrid = searchService.getFacet("agg_geogrid");
                        if (agg_geogrid) bucketsToDraw = agg_geogrid.values;
                        heatmapPainter.drawBuckets(cq.bbox, bucketsToDraw);
                    }

                    fitViewToMarkersAllowed=false;
                }

                mapService.setLimit(scope.limit);
                mapService.registerOnMoveListener(mapOnMove);

                mapService.initializeMap(
                    element.attr('id'),
                    {
                        zoomControl: !scope.disableZoomControl,
                        minZoom: 3
                    } // 3 is to prevent wrong bbox searches
                    // when the window is bigger than the world,
                );

                heatmapPainter.setMap(mapService.getMap());
                placesPainter.setMap(mapService.getMap());

                // Add baselayers and activate one, given by url
                // parameter "baselayer" or a default value
                mapService.setBaselayers(scope.baselayers);
                mapService.activateBaselayer(cq.baselayer || "osm");

                mapService.setOverlays(scope.overlays);
                mapService.activateOverlays(cq.getArrayParam('overlays')); // TODO this is undefined

                mapService.initializeView(cq.lat,cq.lng,cq.zoom);
            }
        }
    }
]);<|endoftext|>


<commit_before>'use strict';

/* Widget directives */
angular.module('arachne.widgets.directives', [])
	.directive('con10tItem', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {
				attrs.$observe('con10tItem', function(value) {
					element.attr("href", "http://arachne.dainst.org/entity/" + value);
				});
			}
		}
	})

	.directive('con10tSearchQuery', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {

				attrs.$observe('con10tSearchQuery', function(value) {
					scope.q = value;
					updateHref();
				});

				attrs.$observe('con10tSearchFacet', function(value) {
					scope.fq = value;
					updateHref();
				});

				function updateHref() {
					var href = "http://arachne.dainst.org/search?q=" + scope.q;
					if (scope.fq) href += "&fq=" + scope.fq;
					element.attr("href", href);
				}

			}
		}
	})

	.directive('con10tPage', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {
				attrs.$observe('con10tPage', function(value) {
					element.attr("href", "http://arachne.dainst.org/projects/" + value);
				});
			}
		}
	})

	.directive('con10tToc', ['$location', '$anchorScroll', function ($location, $anchorScroll) {
		return {
			restrict: 'E',
			scope: {
				tocHeading: '@'
			},
			templateUrl: 'partials/widgets/con10t-toc.html',
			link: function(scope, element, attrs) {
				var headings = document.querySelectorAll(".con10t-toc-entry");

				scope.toc = [];

				for(var i = 0; i < headings.length; i++) {
					var headingID = headings[i].textContent.replace(/ /g, "_");
					var heading = {
						target: headingID,
						text: headings[i].textContent,
						depth: "con10t-toc-item level-" + headings[i].tagName.charAt(1)
					};
					headings[i].id = headingID;
					scope.toc.push(heading);
				}

				// Angular seems to do anchorScroll() directly on load. But without the scope initialized, the targets are not yet
				// existing. Therefore: Try a delayed anchorScroll() after the scope is initialized, if there is an existing hash.
				if($location.hash() != ""){
                    $anchorScroll();
				}

				scope.scrollTo = function(id) {
                    $location.hash(id);
					$anchorScroll()
				}
			}
		};
	}])

	.directive('con10tCatalogTree', ['Catalog', '$filter', function(Catalog, $filter) {
		return {
			restrict: 'E',
			scope: {
				catalogId: '@',
				heading: '='
			},
			templateUrl: 'partials/widgets/con10t-catalog-tree.html',
			link: function(scope) {

				scope.catalog = Catalog.get({id:scope.catalogId});
				scope.isShown = {};

				scope.escapePath = function(path){
					return $filter('escapeSlashes')(path)
				};

				scope.toggleCollapse = function(label){
					scope.isShown[label] = !scope.isShown[label];
				};

				scope.checkIfShown = function(label){
					return scope.isShown[label]; // at first load -> undefined, so it gets hidden but: ugly?
				};

			}
		};
	}])

	.directive('con10tCatalogMap', ['searchService', function(searchService) {
		return {
			restrict: 'E',
			scope: {
				overlays: '=',
				mapConfig: '='
			},
			templateUrl: 'partials/widgets/con10t-catalog-map.html',
			link: function(scope) {
				// Add restrictions for facets to the search if defined in mapConfig
				var facets = scope.mapConfig.facetsSelect;

				if (facets) {
					var query = searchService.currentQuery();

					for (var i = 0; i < facets.length; i++) {
						var facet = facets[i];

						if (!query.hasFacet(facet.key)) {
							query.facets.push(facet);
						}
					}
				}
			}
		};
	}])

    .directive('con10tSearch', ['$location', '$filter', function($location, $filter) {

        return {
            restrict: 'E',
            templateUrl: 'partials/widgets/con10t-search.html',
			scope: {
				catalogId: '@',
				fq: '@'
			},
			link: function(scope, element, attrs) {
            scope.placeholder = attrs.searchPlaceholder;
            scope.slashRegex  = /\//g;

            if(!scope.placeholder)
               scope.placeholder = $filter('i18n')('ui_projectsearchplaceholder');

				scope.search = function() {
              	var url = "search/?q=";
					if(scope.catalogId != undefined && scope.catalogId != "")
						url += "catalogPaths:"+$filter('escapeSlashes')(scope.catalogId)+"+";

               if(scope.q != null && scope.q != "")
                  url += scope.q;
					else
						url += "*";

					if(scope.fq != undefined && scope.fq != "")
               {
                  var split = scope.fq.split(':');
                  url += '&fq='+split[0]+':"'+split[1]+'"';
               }
					$location.url(url);
				};
			}
        };
    }])
	.directive('con10tTree', ['Query', 'Entity', '$location', function(Query, Entity, $location){
        return {
           restrict: 'E',
           templateUrl: 'partials/widgets/con10t-tree.html',
           scope: {
           },
           link: function (scope, element, attrs) {
              scope.staticFacets = [];
              if(!attrs.fq)
                 return;

              var fq_facets = attrs.fq.split(',');
              for(var i = 0; i < fq_facets.length; i++)
              {
                 scope.staticFacets.push(fq_facets[i].split(':'));
              }
              scope.wildcardFacet = attrs.wildcardFacet;
              scope.hierarchyFacets  = []

              if(attrs.hierarchyFacets)
                 scope.hierarchyFacets = attrs.hierarchyFacets.split(',');

               // in the UI, the tree starts with the last provided static facet's name
              scope.treeRoot = [{name: scope.staticFacets[scope.staticFacets.length - 1][1], depth: 0, children:[], facet: null, parent: null}];
              scope.isShown = {};

              scope.getNodeChildren = function(node){
                 if(node.children != 0){
                    return;
                 }

                 var treeQuery = new Query();

                 for(var i = 0; i < scope.staticFacets.length; i++){
                    treeQuery = treeQuery.addFacet(scope.staticFacets[i][0], scope.staticFacets[i][1]);
                 }

                 var collectedFacets = this.collectAllFacets(node);

                 for(var i = 0; i < collectedFacets.length; i++){
                    treeQuery = treeQuery.addFacet(collectedFacets[i][0], collectedFacets[i][1]);
                 }

                 treeQuery.q = "*";

                 Entity.query(treeQuery.toFlatObject(), function(response) {
                    for(var  i = 0; i < response.facets.length; i++){
                       var currentResultFacet = response.facets[i];
                       // try to find custom hierarchy-facet or wildcard
                       if((scope.hierarchyFacets.length > 0 && currentResultFacet.name == (scope.hierarchyFacets[node.depth]))
                          || (scope.wildcardFacet && currentResultFacet.name.indexOf(scope.wildcardFacet) > -1)){
                          for(var j = 0; j < currentResultFacet.values.length; j++)
                          {
                             node.children.push({name: currentResultFacet.values[j].value, depth: node.depth + 1, children:[],
                                facet: [currentResultFacet.name, currentResultFacet.values[j].value], parent: node});
                          }
                       }
                    }
                    if(node.children == 0){
                       node.isLeaf = true;
                    }
                    scope.isShown[ node.name] = true;
                 });
              };

              scope.collectAllFacets = function(node){
                 var result = [];
                 if(node.parent != null){
                    result.push(node.facet);
                    result = result.concat(this.collectAllFacets(node.parent));
                    return result;
                 }
                 return result;
              };

              scope.toggleCollapse = function(node){
                 scope.isShown[node.name] = !scope.isShown[node.name];
                 if(scope.isShown[node.name]){
                    this.getNodeChildren(node)
                 }
              };
              scope.checkIfShown = function(label){
                 return scope.isShown[label]; // at first load -> undefined, so it gets hidden but: ugly?
              };

               scope.closeAll = function(){
                   for(var key in scope.isShown)
                       scope.isShown[key] = false;
               };

              scope.startFacetedSearch = function(node){
                 var facets = this.collectAllFacets(node);

                 facets = facets.concat(scope.staticFacets);

                 var url = "search/?q=*";

                 for(var i = 0; i < facets.length; i++){
                    url += "&fq="+facets[i][0]+':"'+facets[i][1]+'"';
                 }

                 $location.url(url);
              };
           }
        }
    }])
;
<commit_msg>fixed link in con10t-page widget<commit_after>'use strict';

/* Widget directives */
angular.module('arachne.widgets.directives', [])
	.directive('con10tItem', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {
				attrs.$observe('con10tItem', function(value) {
					element.attr("href", "http://arachne.dainst.org/entity/" + value);
				});
			}
		}
	})

	.directive('con10tSearchQuery', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {

				attrs.$observe('con10tSearchQuery', function(value) {
					scope.q = value;
					updateHref();
				});

				attrs.$observe('con10tSearchFacet', function(value) {
					scope.fq = value;
					updateHref();
				});

				function updateHref() {
					var href = "http://arachne.dainst.org/search?q=" + scope.q;
					if (scope.fq) href += "&fq=" + scope.fq;
					element.attr("href", href);
				}

			}
		}
	})

	.directive('con10tPage', function() {
		return {
			restrict: 'A',
			link: function(scope, element, attrs) {
				attrs.$observe('con10tPage', function(value) {
					element.attr("href", "http://arachne.dainst.org/project/" + value);
				});
			}
		}
	})

	.directive('con10tToc', ['$location', '$anchorScroll', function ($location, $anchorScroll) {
		return {
			restrict: 'E',
			scope: {
				tocHeading: '@'
			},
			templateUrl: 'partials/widgets/con10t-toc.html',
			link: function(scope, element, attrs) {
				var headings = document.querySelectorAll(".con10t-toc-entry");

				scope.toc = [];

				for(var i = 0; i < headings.length; i++) {
					var headingID = headings[i].textContent.replace(/ /g, "_");
					var heading = {
						target: headingID,
						text: headings[i].textContent,
						depth: "con10t-toc-item level-" + headings[i].tagName.charAt(1)
					};
					headings[i].id = headingID;
					scope.toc.push(heading);
				}

				// Angular seems to do anchorScroll() directly on load. But without the scope initialized, the targets are not yet
				// existing. Therefore: Try a delayed anchorScroll() after the scope is initialized, if there is an existing hash.
				if($location.hash() != ""){
                    $anchorScroll();
				}

				scope.scrollTo = function(id) {
                    $location.hash(id);
					$anchorScroll()
				}
			}
		};
	}])

	.directive('con10tCatalogTree', ['Catalog', '$filter', function(Catalog, $filter) {
		return {
			restrict: 'E',
			scope: {
				catalogId: '@',
				heading: '='
			},
			templateUrl: 'partials/widgets/con10t-catalog-tree.html',
			link: function(scope) {

				scope.catalog = Catalog.get({id:scope.catalogId});
				scope.isShown = {};

				scope.escapePath = function(path){
					return $filter('escapeSlashes')(path)
				};

				scope.toggleCollapse = function(label){
					scope.isShown[label] = !scope.isShown[label];
				};

				scope.checkIfShown = function(label){
					return scope.isShown[label]; // at first load -> undefined, so it gets hidden but: ugly?
				};

			}
		};
	}])

	.directive('con10tCatalogMap', ['searchService', function(searchService) {
		return {
			restrict: 'E',
			scope: {
				overlays: '=',
				mapConfig: '='
			},
			templateUrl: 'partials/widgets/con10t-catalog-map.html',
			link: function(scope) {
				// Add restrictions for facets to the search if defined in mapConfig
				var facets = scope.mapConfig.facetsSelect;

				if (facets) {
					var query = searchService.currentQuery();

					for (var i = 0; i < facets.length; i++) {
						var facet = facets[i];

						if (!query.hasFacet(facet.key)) {
							query.facets.push(facet);
						}
					}
				}
			}
		};
	}])

    .directive('con10tSearch', ['$location', '$filter', function($location, $filter) {

        return {
            restrict: 'E',
            templateUrl: 'partials/widgets/con10t-search.html',
			scope: {
				catalogId: '@',
				fq: '@'
			},
			link: function(scope, element, attrs) {
            scope.placeholder = attrs.searchPlaceholder;
            scope.slashRegex  = /\//g;

            if(!scope.placeholder)
               scope.placeholder = $filter('i18n')('ui_projectsearchplaceholder');

				scope.search = function() {
              	var url = "search/?q=";
					if(scope.catalogId != undefined && scope.catalogId != "")
						url += "catalogPaths:"+$filter('escapeSlashes')(scope.catalogId)+"+";

               if(scope.q != null && scope.q != "")
                  url += scope.q;
					else
						url += "*";

					if(scope.fq != undefined && scope.fq != "")
               {
                  var split = scope.fq.split(':');
                  url += '&fq='+split[0]+':"'+split[1]+'"';
               }
					$location.url(url);
				};
			}
        };
    }])
	.directive('con10tTree', ['Query', 'Entity', '$location', function(Query, Entity, $location){
        return {
           restrict: 'E',
           templateUrl: 'partials/widgets/con10t-tree.html',
           scope: {
           },
           link: function (scope, element, attrs) {
              scope.staticFacets = [];
              if(!attrs.fq)
                 return;

              var fq_facets = attrs.fq.split(',');
              for(var i = 0; i < fq_facets.length; i++)
              {
                 scope.staticFacets.push(fq_facets[i].split(':'));
              }
              scope.wildcardFacet = attrs.wildcardFacet;
              scope.hierarchyFacets  = []

              if(attrs.hierarchyFacets)
                 scope.hierarchyFacets = attrs.hierarchyFacets.split(',');

               // in the UI, the tree starts with the last provided static facet's name
              scope.treeRoot = [{name: scope.staticFacets[scope.staticFacets.length - 1][1], depth: 0, children:[], facet: null, parent: null}];
              scope.isShown = {};

              scope.getNodeChildren = function(node){
                 if(node.children != 0){
                    return;
                 }

                 var treeQuery = new Query();

                 for(var i = 0; i < scope.staticFacets.length; i++){
                    treeQuery = treeQuery.addFacet(scope.staticFacets[i][0], scope.staticFacets[i][1]);
                 }

                 var collectedFacets = this.collectAllFacets(node);

                 for(var i = 0; i < collectedFacets.length; i++){
                    treeQuery = treeQuery.addFacet(collectedFacets[i][0], collectedFacets[i][1]);
                 }

                 treeQuery.q = "*";

                 Entity.query(treeQuery.toFlatObject(), function(response) {
                    for(var  i = 0; i < response.facets.length; i++){
                       var currentResultFacet = response.facets[i];
                       // try to find custom hierarchy-facet or wildcard
                       if((scope.hierarchyFacets.length > 0 && currentResultFacet.name == (scope.hierarchyFacets[node.depth]))
                          || (scope.wildcardFacet && currentResultFacet.name.indexOf(scope.wildcardFacet) > -1)){
                          for(var j = 0; j < currentResultFacet.values.length; j++)
                          {
                             node.children.push({name: currentResultFacet.values[j].value, depth: node.depth + 1, children:[],
                                facet: [currentResultFacet.name, currentResultFacet.values[j].value], parent: node});
                          }
                       }
                    }
                    if(node.children == 0){
                       node.isLeaf = true;
                    }
                    scope.isShown[ node.name] = true;
                 });
              };

              scope.collectAllFacets = function(node){
                 var result = [];
                 if(node.parent != null){
                    result.push(node.facet);
                    result = result.concat(this.collectAllFacets(node.parent));
                    return result;
                 }
                 return result;
              };

              scope.toggleCollapse = function(node){
                 scope.isShown[node.name] = !scope.isShown[node.name];
                 if(scope.isShown[node.name]){
                    this.getNodeChildren(node)
                 }
              };
              scope.checkIfShown = function(label){
                 return scope.isShown[label]; // at first load -> undefined, so it gets hidden but: ugly?
              };

               scope.closeAll = function(){
                   for(var key in scope.isShown)
                       scope.isShown[key] = false;
               };

              scope.startFacetedSearch = function(node){
                 var facets = this.collectAllFacets(node);

                 facets = facets.concat(scope.staticFacets);

                 var url = "search/?q=*";

                 for(var i = 0; i < facets.length; i++){
                    url += "&fq="+facets[i][0]+':"'+facets[i][1]+'"';
                 }

                 $location.url(url);
              };
           }
        }
    }])
;
<|endoftext|>


<commit_before>/*global describe, it, beforeEach, expect, xit, jasmine */
/*jshint laxbreak:true */

//var jquery_preload = require('../../src/jquery_preload.js');
//jquery_preload.with_jquery(function() {
//});

describe("Background Img parsing", function () {
    "use strict";

    var Img = require('../../src/core/img.js'),
        Point = require('../../src/math/point.js'),
        xmlString,
        $xml,
        image,
        srcString = "http://www.example.com/example_img.png",
        frameString = "plot",
        anchorString = "1,1",
        baseString = "0,0",
        positionString = "-1,1";

    var $, jqw = require('../node_jquery_helper.js').createJQuery();
    beforeEach(function() { $ = jqw.$; });

    var JQueryXMLParser;
    beforeEach(function () {
        JQueryXMLParser = require('../../src/parser/jquery_xml_parser.js')($);
    });

    beforeEach(function() {
        JQueryXMLParser = require('../../src/parser/jquery_xml_parser.js')($),
        xmlString = ''
            + '<?xml version="1.0" encoding="UTF-8"?>';
        var $xml = JQueryXMLParser.stringToJQueryXMLObj(xmlString);
        image = Img.parseXML($xml);
    });

    it("should be able to parse a background from XML", function () {
        expect(image).not.toBeUndefined();
        expect(image instanceof Img).toBe(true);
    });

    it("should be able to parse a img from XML and read its 'src' attribute", function () {
        expect(image.src()).toBe(srcString);
    });

    it("should be able to parse a img from XML and read its 'anchor' attribute", function () {
        expect(image.anchor().x()).toEqual((Point.parse(anchorString)).x());
        expect(image.anchor().y()).toEqual((Point.parse(anchorString)).y());
    });

    it("should be able to parse a img from XML and read its 'base' attribute", function () {
        expect(image.base().x()).toEqual((Point.parse(baseString)).x());
        expect(image.base().y()).toEqual((Point.parse(baseString)).y());
    });

    it("should be able to parse a img from XML and read its 'position' attribute", function () {
        expect(image.position().x()).toEqual((Point.parse(positionString)).x());
        expect(image.position().y()).toEqual((Point.parse(positionString)).y());
    });

    it("should be able to parse a img from XML and read its 'frame' attribute", function () {
        expect(image.frame()).toBe(frameString.toLowerCase());
    });

});
<commit_msg>remove extraneous comments<commit_after>/*global describe, it, beforeEach, expect, xit, jasmine */
/*jshint laxbreak:true */

describe("Background Img parsing", function () {
    "use strict";

    var Img = require('../../src/core/img.js'),
        Point = require('../../src/math/point.js'),
        xmlString,
        $xml,
        image,
        srcString = "http://www.example.com/example_img.png",
        frameString = "plot",
        anchorString = "1,1",
        baseString = "0,0",
        positionString = "-1,1";

    var $, jqw = require('../node_jquery_helper.js').createJQuery();
    beforeEach(function() { $ = jqw.$; });

    var JQueryXMLParser;
    beforeEach(function () {
        JQueryXMLParser = require('../../src/parser/jquery_xml_parser.js')($);
    });

    beforeEach(function() {
        JQueryXMLParser = require('../../src/parser/jquery_xml_parser.js')($),
        xmlString = ''
            + '<?xml version="1.0" encoding="UTF-8"?>';
        var $xml = JQueryXMLParser.stringToJQueryXMLObj(xmlString);
        image = Img.parseXML($xml);
    });

    it("should be able to parse a background from XML", function () {
        expect(image).not.toBeUndefined();
        expect(image instanceof Img).toBe(true);
    });

    it("should be able to parse a img from XML and read its 'src' attribute", function () {
        expect(image.src()).toBe(srcString);
    });

    it("should be able to parse a img from XML and read its 'anchor' attribute", function () {
        expect(image.anchor().x()).toEqual((Point.parse(anchorString)).x());
        expect(image.anchor().y()).toEqual((Point.parse(anchorString)).y());
    });

    it("should be able to parse a img from XML and read its 'base' attribute", function () {
        expect(image.base().x()).toEqual((Point.parse(baseString)).x());
        expect(image.base().y()).toEqual((Point.parse(baseString)).y());
    });

    it("should be able to parse a img from XML and read its 'position' attribute", function () {
        expect(image.position().x()).toEqual((Point.parse(positionString)).x());
        expect(image.position().y()).toEqual((Point.parse(positionString)).y());
    });

    it("should be able to parse a img from XML and read its 'frame' attribute", function () {
        expect(image.frame()).toBe(frameString.toLowerCase());
    });

});
<|endoftext|>


<commit_before>var timeout_id, plugin_uuid
var ip = "192.168.1.1"
var rpc = Serverboards.rpc

function update(){
  console.log("ping %o", ip)
  rpc.call(plugin_uuid+".ping", {ip: ip}).then(function(result){
    var table=$('tbody#pinger')
    if (table.length==0){ // There is no table, do not try again
      console.log("Stop pinger table")
      return;
    }
    var cd=new Date()
    var time = ((cd.getHours() < 10)?"0":"") + cd.getHours() +":"+ ((cd.getMinutes() < 10)?"0":"") + cd.getMinutes() +":"+ ((cd.getSeconds() < 10)?"0":"") + cd.getSeconds();

    var tr=$(`<tr>`)
    var td
    td=$('<td>').text(time)
    tr.append(td)
    td=$('<td>').text(result.ms.toPrecision(4)+' ms')
    tr.append(td)

    table.prepend(tr)

    while (table.find("tr").length>15)
      table.find("tr").last().remove()

    timeout_id = setTimeout( update, 1000 )
  })
}

function main(){
  console.log("Called JS! %o", Serverboards)
  var nip = $('input#ip').val()
  if (nip)
    ip=nip
  else if (ip)
    $('input#ip').val(ip)

  rpc.call("plugin.start",["serverboards.core.actions/cmd"]).then(function(uuid){
    plugin_uuid=uuid

    update()
  })

  $('button#reload').on('click',function(){
    $('tbody#pinger').html('')
    if (timeout_id)
      clearTimeout(timeout_id)
    ip = $('input#ip').val()
    update()
  })
}

main()
<commit_msg>Pinger can receive data from the services to ping a specific IP.<commit_after>var timeout_id, plugin_uuid
var ip = "192.168.1.1"
var rpc = Serverboards.rpc

function update(){
  console.log("ping %o", ip)
  rpc.call(plugin_uuid+".ping", {ip: ip}).then(function(result){
    var table=$('tbody#pinger')
    if (table.length==0){ // There is no table, do not try again
      console.log("Stop pinger table")
      return;
    }
    var cd=new Date()
    var time = ((cd.getHours() < 10)?"0":"") + cd.getHours() +":"+ ((cd.getMinutes() < 10)?"0":"") + cd.getMinutes() +":"+ ((cd.getSeconds() < 10)?"0":"") + cd.getSeconds();

    var tr=$(`<tr>`)
    var td
    td=$('<td>').text(time)
    tr.append(td)
    td=$('<td>').text(result.ms.toPrecision(4)+' ms')
    tr.append(td)

    table.prepend(tr)

    while (table.find("tr").length>15)
      table.find("tr").last().remove()

    timeout_id = setTimeout( update, 1000 )
  })
}

function main(config){
  console.log("Called JS! %o", config)
  if (config && config.service){
    ip=config.service.config.ip
    $('input#ip').val(ip)
  }
  else {
    var nip = $('input#ip').val()
    if (nip)
      ip=nip
    else if (ip)
      $('input#ip').val(ip)
  }

  rpc.call("plugin.start",["serverboards.core.actions/cmd"]).then(function(uuid){
    plugin_uuid=uuid

    update()
  })

  $('button#reload').on('click',function(){
    $('tbody#pinger').html('')
    if (timeout_id)
      clearTimeout(timeout_id)
    ip = $('input#ip').val()
    update()
  })
}

Serverboards.add_screen("serverboards.core.pinger/pinger", main)
<|endoftext|>