<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

define(['jquery', 'underscore', 'oae.core'], function($, _, oae) {

    return function(uid) {

        // The widget container
        var $rootel = $('#' + uid);

        // Variable that will be used to keep track of the current infinite scroll instance
        var infinityScroll = false;

        /**
         * Sort entities based on whether or not they have a thumbnail. Entities with
         * thumbnails will be listed in front of those with no thumbnails, as we give
         * preference to these for UI rendering purposes.
         *
         * @see Array#sort
         */
        var sortEntityCollection = function(a, b) {
            if (a.image && !b.image) {
                return -1;
            } else if (!a.image && b.image) {
                return 1;
            }
            return 0;
        };

        /**
         * Process the incoming activities by sorting the actor entity collections on whether or not they have
         * thumbnail images, as we give preference to these for UI rendering purposes.
         *
         * @param  {Object}    data     The current user's notifications per the activitystrea.ms spec
         * @return {Object}             Object containing the notifications where the actors are sorted on whether or not they have a profile image
         */
        var processActivities = function(data) {
            $.each(data.items, function(index, activity) {
                // Sort the actor entity collections based on whether or not they have a thumbnail
                if (activity.actor['oae:collection']) {
                    activity.actor['oae:collection'].sort(sortEntityCollection);
                }
            });
            return {'results': data.items};
        };

        /**
         * Based on the number of unread notifications reported by the me feed, the first items in the notifications
         * list are flagged as unread. This will make it clearer to the end-user as to which notifications he should
         * be paying most attention to.
         */
        var flagUnread = function() {
            var unreadNotifications = oae.data.me.notificationsUnread || 0;
            // The standard `alert-info` class is used for flagging
            $('.oae-listitem:lt(' + unreadNotifications + ')', $rootel).addClass('alert-info');
        };

        /**
         * When the clickover is closed, we assume that the user has seen all of its unread notifications and mark
         * all of them as read.
         */
        var markAsRead = function() {
            // Reset the number of unread notifications on the back-end side
            $.ajax({
                'url': '/api/notifications/markRead',
                'type': 'POST'
            });
        };

        /**
         * Remove the unread notification count from the top navigation widget
         */
        var removeUnreadCount = function() {
            $('#topnavigation-notification-count').remove();
            // Reset the number of unread notifications in the me feed object
            oae.data.me.notificationsUnread = 0;
        }

        /**
         * Initialize a new infinite scroll container that fetches the current user's notifications.
         */
        var getNotifications = function() {
            // At this point, we need to make sure that the clickover is wide enough to be able to show notifications
            $('#notifications-container', $rootel).parents('.popover').removeClass('notifications-empty-container');

            // Disable the previous infinite scroll
            if (infinityScroll) {
                infinityScroll.kill();
            }

            // Set up the infinite scroll for the notifications list
            infinityScroll = $('.oae-list', $rootel).infiniteScroll('/api/notifications', {
                'limit': 10
            }, '#notifications-template', {
                'scrollContainer': $('#notifications-container', $rootel),
                'postProcessor': processActivities,
                'postRenderer': flagUnread,
                'emptyListProcessor': handleEmptyResultList
            });
        };

        /**
         * Show the empty list message when no notifications are found. This function will
         * be called by the infinite scroll plugin.
         */
        var handleEmptyResultList = function() {
            // When no notifications are available, the clickover doesn't need to be as wide as when notifications
            // are shown. Therefore, we apply a class to the popover container that makes the clickover smaller
            $('#notifications-container', $rootel).parents('.popover').addClass('notifications-empty-container');
            oae.api.util.template().render($('#notifications-noresults-template', $rootel), null, $('.oae-list', $rootel));
        };

        /**
         * Initializes the notifications clickover
         */
        var setUpNotificationsClickover = function() {
            $(document).on('click', '.oae-trigger-notifications', function() {
                // Trigger the notifications clickover
                oae.api.util.clickover($(this), $('.notifications-widget'), {
                    'onShown': function($currentRootEl) {
                        $rootel = $currentRootEl;
                        getNotifications();
                        // The notifications need to be marked as read straight away, in case the user
                        // follows a link in the notifications and doesn't actually end up closing the
                        // clickover on the current page
                        markAsRead();
                    },
                    'onHidden': removeUnreadCount
                });
            });
        };

        setUpNotificationsClickover();

    };
});
<commit_msg>Added semicolon after inline function in notifications.js<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

define(['jquery', 'underscore', 'oae.core'], function($, _, oae) {

    return function(uid) {

        // The widget container
        var $rootel = $('#' + uid);

        // Variable that will be used to keep track of the current infinite scroll instance
        var infinityScroll = false;

        /**
         * Sort entities based on whether or not they have a thumbnail. Entities with
         * thumbnails will be listed in front of those with no thumbnails, as we give
         * preference to these for UI rendering purposes.
         *
         * @see Array#sort
         */
        var sortEntityCollection = function(a, b) {
            if (a.image && !b.image) {
                return -1;
            } else if (!a.image && b.image) {
                return 1;
            }
            return 0;
        };

        /**
         * Process the incoming activities by sorting the actor entity collections on whether or not they have
         * thumbnail images, as we give preference to these for UI rendering purposes.
         *
         * @param  {Object}    data     The current user's notifications per the activitystrea.ms spec
         * @return {Object}             Object containing the notifications where the actors are sorted on whether or not they have a profile image
         */
        var processActivities = function(data) {
            $.each(data.items, function(index, activity) {
                // Sort the actor entity collections based on whether or not they have a thumbnail
                if (activity.actor['oae:collection']) {
                    activity.actor['oae:collection'].sort(sortEntityCollection);
                }
            });
            return {'results': data.items};
        };

        /**
         * Based on the number of unread notifications reported by the me feed, the first items in the notifications
         * list are flagged as unread. This will make it clearer to the end-user as to which notifications he should
         * be paying most attention to.
         */
        var flagUnread = function() {
            var unreadNotifications = oae.data.me.notificationsUnread || 0;
            // The standard `alert-info` class is used for flagging
            $('.oae-listitem:lt(' + unreadNotifications + ')', $rootel).addClass('alert-info');
        };

        /**
         * When the clickover is closed, we assume that the user has seen all of its unread notifications and mark
         * all of them as read.
         */
        var markAsRead = function() {
            // Reset the number of unread notifications on the back-end side
            $.ajax({
                'url': '/api/notifications/markRead',
                'type': 'POST'
            });
        };

        /**
         * Remove the unread notification count from the top navigation widget
         */
        var removeUnreadCount = function() {
            $('#topnavigation-notification-count').remove();
            // Reset the number of unread notifications in the me feed object
            oae.data.me.notificationsUnread = 0;
        };

        /**
         * Initialize a new infinite scroll container that fetches the current user's notifications.
         */
        var getNotifications = function() {
            // At this point, we need to make sure that the clickover is wide enough to be able to show notifications
            $('#notifications-container', $rootel).parents('.popover').removeClass('notifications-empty-container');

            // Disable the previous infinite scroll
            if (infinityScroll) {
                infinityScroll.kill();
            }

            // Set up the infinite scroll for the notifications list
            infinityScroll = $('.oae-list', $rootel).infiniteScroll('/api/notifications', {
                'limit': 10
            }, '#notifications-template', {
                'scrollContainer': $('#notifications-container', $rootel),
                'postProcessor': processActivities,
                'postRenderer': flagUnread,
                'emptyListProcessor': handleEmptyResultList
            });
        };

        /**
         * Show the empty list message when no notifications are found. This function will
         * be called by the infinite scroll plugin.
         */
        var handleEmptyResultList = function() {
            // When no notifications are available, the clickover doesn't need to be as wide as when notifications
            // are shown. Therefore, we apply a class to the popover container that makes the clickover smaller
            $('#notifications-container', $rootel).parents('.popover').addClass('notifications-empty-container');
            oae.api.util.template().render($('#notifications-noresults-template', $rootel), null, $('.oae-list', $rootel));
        };

        /**
         * Initializes the notifications clickover
         */
        var setUpNotificationsClickover = function() {
            $(document).on('click', '.oae-trigger-notifications', function() {
                // Trigger the notifications clickover
                oae.api.util.clickover($(this), $('.notifications-widget'), {
                    'onShown': function($currentRootEl) {
                        $rootel = $currentRootEl;
                        getNotifications();
                        // The notifications need to be marked as read straight away, in case the user
                        // follows a link in the notifications and doesn't actually end up closing the
                        // clickover on the current page
                        markAsRead();
                    },
                    'onHidden': removeUnreadCount
                });
            });
        };

        setUpNotificationsClickover();

    };
});
<|endoftext|>


<commit_before>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';
import Label from './Label';
import columns from '../helpers/columns';

/**
 * The Dropdown class
 */
class Dropdown extends React.Component {
    constructor(props) {
        super(props);

        // build a list of permitted values from the options values
        //
        this.permittedValues = props.options.map((opt) => opt.value);

        // generate a unique ID for this component
        //
        this.id = uniqueId('Dropdown-');

        // initialize the validation message from the custom message provided
        // (if any) or generate the default message
        //
        this.validationMessage = props.validationMessage
            || `${props.description || 'This value'} is required`;

        // validate the initial value & set the initial state
        //
        this.state = this.validate(props.value);

        // set the `hasValidated` state to its initial value
        //
        this.state.hasValidated = false;

        // bind `this` to the onEvent method
        //
        this.onEvent = this.onEvent.bind(this);
    }

    /**
     * Handle the initial mounting of the component
     *
     * This method ensures that `onValidation` is called once when the component
     * is first initialized. This allows the component to inform its parent of
     * the initial validation state.
     */
    componentWillMount() {
        // do we have an `onValidation` handler?
        //
        if (this.props.onValidation) {
            // call the `onValidation` handler
            //
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    /**
     * Handle changes to the component properties
     * @param  {Object} newProps The new properties for the object
     */
    componentWillReceiveProps(newProps) {
        // save the current value for later use
        //
        const currentValue = this.state.value;

        // build the new state by validating the new value property
        //
        const newState = this.validate(newProps.value);

        // ensure the `hasValidated` state is preserved
        //
        newState.hasValidated = this.state.hasValidated;

        // set the new state
        //
        this.setState(newState);

        // do we have an onValidation handler? has the value changed? if so,
        // call the handler with the new validation state
        //
        if (this.props.onValidation && currentValue !== newState.value) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        // do we have an onChange handler? has the value changed? if so, call
        // the handler with the new value
        //
        if (this.props.onChange && currentValue !== newState.value) {
            this.props.onChange(newState.value);
        }
    }

    /**
     * Handle events from the `select` element
     * @param  {Object} event The event object
     */
    onEvent(event) {
        // get the current value of the component
        //
        const currentValue = this.state.value;

        // get the new state of the component based on the new value from the
        // select element
        //
        const newState = this.validate(event.target.value);

        // set the new state
        //
        this.setState(() => newState);

        // do we have an `onChange` handler? do we have a new value? if so,
        // call the `onChange` handler with the new value
        //
        if (this.props.onChange && newState.value !== currentValue) {
            this.props.onChange(newState.value);
        }

        // do we have an `onValidation` handler? if so, call it with the new
        // validation state
        //
        if (this.props.onValidation) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }
    }

    /**
     * Validate a value for the component
     * @param  {String} option The value to validate
     * @return {Object}        The new state of the component, including the
     *                         value
     */
    validate(option) {
        // make sure the value is cast to a string
        //
        let value = `${option || ''}`;

        // determine if the value is in the list of permitted values
        //
        const inValues = this.permittedValues.find((val) => val === value);

        // if the value is not in the list of values, set it to the `null` value
        //
        if (!inValues) {
            value = '';
        }

        // determine if the value is valid, based on the `required` flag
        //
        const isValid = !this.props.required || !!value;

        // return the new state
        //
        return {
            value,
            hasValidated:      true,
            isValid,
            validationMessage: isValid ? null : this.validationMessage,
        };
    }

    /**
     * Render the component
     * @return {React.Element} The React Element describing this component
     */
    render() {
        // generate the classes for the outer div
        //
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        // generate the classes for the label component
        //
        const labelClasses = columns(this.props.labelColumns);

        const selectClasses = columns(this.props.dropdownColumns);

        const select = (
            <select
                id={this.id}
                className="form-control"
                onChange={this.onEvent}
                onBlur={this.onEvent}
            >
                {this.props.includeNull
                    ? <option key={uniqueId('Dropdown-')} value="">
                        {this.props.nullName || 'Please select one'}
                    </option>
                    : ''
                }
                {this.props.options.map((opt) =>
                    <option key={uniqueId('Dropdown-')} value={opt.value}>{opt.name}</option>)
                }
            </select>
        );

        return (

                {this.props.label
                    ?
                    : ''
                }
                {selectClasses
                    ?
{select}

                    : select
                }

                {this.state.hasValidated && !this.state.isValid
                    ? {this.state.validationMessage}
                    : ''
                }

        );
    }
}

// set the property types for the Dropdown component
//
Dropdown.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    includeNull:       React.PropTypes.bool,
    nullName:          React.PropTypes.string,
    required:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    value:             React.PropTypes.string,
    options:           React.PropTypes.array.isRequired,
    onValidation:      React.PropTypes.func,
    onChange:          React.PropTypes.func,
};

// export the component
//
export default Dropdown;
<commit_msg>added labelColumns, dropdownColumns to proptypes<commit_after>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import classnames from 'classnames';
import Label from './Label';
import columns from '../helpers/columns';

/**
 * The Dropdown class
 */
class Dropdown extends React.Component {
    constructor(props) {
        super(props);

        // build a list of permitted values from the options values
        //
        this.permittedValues = props.options.map((opt) => opt.value);

        // generate a unique ID for this component
        //
        this.id = uniqueId('Dropdown-');

        // initialize the validation message from the custom message provided
        // (if any) or generate the default message
        //
        this.validationMessage = props.validationMessage
            || `${props.description || 'This value'} is required`;

        // validate the initial value & set the initial state
        //
        this.state = this.validate(props.value);

        // set the `hasValidated` state to its initial value
        //
        this.state.hasValidated = false;

        // bind `this` to the onEvent method
        //
        this.onEvent = this.onEvent.bind(this);
    }

    /**
     * Handle the initial mounting of the component
     *
     * This method ensures that `onValidation` is called once when the component
     * is first initialized. This allows the component to inform its parent of
     * the initial validation state.
     */
    componentWillMount() {
        // do we have an `onValidation` handler?
        //
        if (this.props.onValidation) {
            // call the `onValidation` handler
            //
            this.props.onValidation(
                this.state.hasValidated,
                this.state.isValid,
                this.state.validationMessage
            );
        }
    }

    /**
     * Handle changes to the component properties
     * @param  {Object} newProps The new properties for the object
     */
    componentWillReceiveProps(newProps) {
        // save the current value for later use
        //
        const currentValue = this.state.value;

        // build the new state by validating the new value property
        //
        const newState = this.validate(newProps.value);

        // ensure the `hasValidated` state is preserved
        //
        newState.hasValidated = this.state.hasValidated;

        // set the new state
        //
        this.setState(newState);

        // do we have an onValidation handler? has the value changed? if so,
        // call the handler with the new validation state
        //
        if (this.props.onValidation && currentValue !== newState.value) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }

        // do we have an onChange handler? has the value changed? if so, call
        // the handler with the new value
        //
        if (this.props.onChange && currentValue !== newState.value) {
            this.props.onChange(newState.value);
        }
    }

    /**
     * Handle events from the `select` element
     * @param  {Object} event The event object
     */
    onEvent(event) {
        // get the current value of the component
        //
        const currentValue = this.state.value;

        // get the new state of the component based on the new value from the
        // select element
        //
        const newState = this.validate(event.target.value);

        // set the new state
        //
        this.setState(() => newState);

        // do we have an `onChange` handler? do we have a new value? if so,
        // call the `onChange` handler with the new value
        //
        if (this.props.onChange && newState.value !== currentValue) {
            this.props.onChange(newState.value);
        }

        // do we have an `onValidation` handler? if so, call it with the new
        // validation state
        //
        if (this.props.onValidation) {
            this.props.onValidation(
                newState.hasValidated,
                newState.isValid,
                newState.validationMessage
            );
        }
    }

    /**
     * Validate a value for the component
     * @param  {String} option The value to validate
     * @return {Object}        The new state of the component, including the
     *                         value
     */
    validate(option) {
        // make sure the value is cast to a string
        //
        let value = `${option || ''}`;

        // determine if the value is in the list of permitted values
        //
        const inValues = this.permittedValues.find((val) => val === value);

        // if the value is not in the list of values, set it to the `null` value
        //
        if (!inValues) {
            value = '';
        }

        // determine if the value is valid, based on the `required` flag
        //
        const isValid = !this.props.required || !!value;

        // return the new state
        //
        return {
            value,
            hasValidated:      true,
            isValid,
            validationMessage: isValid ? null : this.validationMessage,
        };
    }

    /**
     * Render the component
     * @return {React.Element} The React Element describing this component
     */
    render() {
        // generate the classes for the outer div
        //
        const divClasses = classnames('form-group', {
            'has-error': this.state.hasValidated && !this.state.isValid,
        });

        // generate the classes for the label component
        //
        const labelClasses = columns(this.props.labelColumns);

        const selectClasses = columns(this.props.dropdownColumns);

        const select = (
            <select
                id={this.id}
                className="form-control"
                onChange={this.onEvent}
                onBlur={this.onEvent}
            >
                {this.props.includeNull
                    ? <option key={uniqueId('Dropdown-')} value="">
                        {this.props.nullName || 'Please select one'}
                    </option>
                    : ''
                }
                {this.props.options.map((opt) =>
                    <option key={uniqueId('Dropdown-')} value={opt.value}>{opt.name}</option>)
                }
            </select>
        );

        return (

                {this.props.label
                    ?
                    : ''
                }
                {selectClasses
                    ?
{select}

                    : select
                }

                {this.state.hasValidated && !this.state.isValid
                    ? {this.state.validationMessage}
                    : ''
                }

        );
    }
}

// set the property types for the Dropdown component
//
Dropdown.propTypes = {
    label:             React.PropTypes.string,
    description:       React.PropTypes.string,
    includeNull:       React.PropTypes.bool,
    nullName:          React.PropTypes.string,
    required:          React.PropTypes.bool,
    validationMessage: React.PropTypes.string,
    value:             React.PropTypes.string,
    options:           React.PropTypes.array.isRequired,
    labelColumns:      React.PropTypes.object,
    dropdownColumns:   React.PropTypes.object,
    onValidation:      React.PropTypes.func,
    onChange:          React.PropTypes.func,
};

// export the component
//
export default Dropdown;
<|endoftext|>


<commit_before>'use strict';

define(['dust/core', 'lib/utils'], function(dust, utils) {

    dust.filters.amp = function(string) {
        if (utils.isServer) {
            var imageSize = require('image-size'),
            request = require('request');

            string = string.replace(/<img([^>]*)>/g, function(all, attr) {

                attr = attr.replace(/src\w*=\w*("|')([^\"\']+)("|')/g, function(at, qt, src) {
                    var chunks = [], size = {height: -1, width: -1}, found = false;
                    try {
                        if (src.substr(0, 4).toLowerCase() !== 'http') {
                            src = 'http:' + src;
                        }
                        var r = request.get({uri: src})
                            .on('data', function (chunk) {
                                chunks.push(chunk);
                                if (chunks.length > 2) {
                                    r.abort();
                                }
                            }).on('end', function() {
                                var buffer = Buffer.concat(chunks);
                                size = imageSize(buffer);
                                found = true;
                            });
                        setTimeout(function() { found = true; }, 150);
                        while (!found) {
                            require('deasync').runLoopOnce();
                        }
                    } catch (err) {
                        if (GLOBAL && GLOBAL.liveblogLogger) {
                            liveblogLogger.info('ERROR amp request: %s', err);
                        }
                    }
                    return 'src="' + src + '" height="' + size.height + '" width="' + size.width + '"';
                });
                return '<amp-img layout="responsive"' + attr + '/>';
            });

            return string;
        }
        return string;
    };
    return dust;
});
<commit_msg>fix: bypass `.ico`files.<commit_after>'use strict';

define(['dust/core', 'lib/utils'], function(dust, utils) {

    var bypassExtensions = ['.ico'];
    dust.filters.amp = function(string) {
        if (utils.isServer) {

            string = string.replace(/<img([^>]*)>/g, function(all, attr) {
                var bypass = false;
                attr = attr.replace(/src\w*=\w*("|')([^\"\']+)("|')/g, function(at, qt, src) {
                    var chunks = [],
                        size = {height: -1, width: -1},
                        found = false;

                    for(var i=0, count = bypassExtensions.length; i < count; i++) {
                        if(src.substr(src.length-4) === bypassExtensions[i]) {
                            bypass = true;
                        }
                    }
                    if(bypass) {
                        return at;
                    }
                    try {
                        if (src.substr(0, 4).toLowerCase() !== 'http') {
                            src = 'http:' + src;
                        }

                        var imageSize = require('image-size'),
                            request = require('request');

                        var r = request.get({uri: src})
                            .on('data', function (chunk) {
                                chunks.push(chunk);
                                if (chunks.length > 2) {
                                    r.abort();
                                }
                            }).on('end', function() {
                                var buffer = Buffer.concat(chunks);
                                size = imageSize(buffer);
                                found = true;
                            });
                        setTimeout(function() { found = true; }, 150);
                        while (!found) {
                            require('deasync').runLoopOnce();
                        }
                    } catch (err) {
                        if (GLOBAL && GLOBAL.liveblogLogger) {
                            liveblogLogger.info('ERROR amp request: %s', err);
                        }
                    }
                    return 'src="' + src + '" height="' + size.height + '" width="' + size.width + '"';
                });
                if(bypass) {
                    return all;
                }
                return '<amp-img layout="responsive"' + attr + '/>';
            });

            return string;
        }
        return string;
    };
    return dust;
});
<|endoftext|>


<commit_before>$(document).ready(function() {
	//add
	$('#customAdd1').click(function(event) {
		$('#customPopup1').show();
		$('#customPopup1 input[type!="checkbox"], #customPopup1 select').val('');
		$('#customPopup1 .invalid').qtip('destroy', true);
		$('#customPopup1 .invalid').removeClass('invalid');

		$('#customBtn1').click(function() {
			var ajaxData = $('#customPopup1 input[type!="checkbox"], #customPopup1 select').serializeArray();
			ajaxData.push(
				{'name': 'action', 'value': 'customAjax'},
				{'name': 'type', 'value': type},
				{'name': 'id', 'value': id},
				{'name': 'subAction', 'value': 'add'},
				{'name': 'subType', 'value': 'payment'}
			);
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: ajaxData
			}).done(function(data) {
				$('#customPopup1 .invalid').qtip('destroy', true);
				$('#customPopup1 .invalid').removeClass('invalid');
				if (data.status == 'success') {
					location.reload();
				}
				else {
					$.each(data, function(key, value) {
						if (key != 'status') {
							$('#customPopup1 [name=' + key + ']').addClass('invalid');
							$('#customPopup1 [name=' + key + ']').qtip({
								'content': value,
								'style': {'classes': 'qtip-tipsy-custom'},
								'position': {
									'my': 'bottom center',
									'at': 'top center'
								},
								show: {'event': 'focus'},
								hide: {'event': 'blur'}
							});
						}
					});
				}
			});
		});
		event.preventDefault();
	});

	$('#customAdd2').click(function(event) {
		$('#customPopup2').show();
		$('#customPopup2 [name=itemType]').val('');
		$('#customPopup2 ul').eq(0).children(':not(:first)').remove();
		$('#customPopup2 .invalid').qtip('destroy', true);
		$('#customPopup2 .invalid').removeClass('invalid');
		resetRecurringOptions('#customPopup2');

		$('#customPopup2 [name=itemType]').change(function() {
			var $select = $(this);
			var $ul = $select.closest('ul');
			var itemType = $select.val();
			if (itemType == '') {
				$ul.children(':not(:first)').remove();
				$('#customPopup2 ul').eq(1).hide();
			}
			else if (itemType == 'product') {
				$.ajax({
					url: 'ajax.php',
					type: 'POST',
					data: {
						'action': 'customAjax',
						'type': type,
						'id': id,
						'subAction': 'list',
						'itemType': itemType
					}
				}).done(function(data) {
					var html = '
Product<select name="productID">' + data.products + '</select>
';
					html += '
Location<select name="locationID">' + data.locations + '</select>
';
					html += '
Unit Price<input type="text" name="unitPrice" autocomplete="off">
';
					html += '
Quantity<input type="text" name="quantity" autocomplete="off" value="1">
';
					html += '
Recurring<select name="recurring"><option value="no">No</option><option value="yes">Yes</option></select>
';
					if ($ul.children().length > 1) {
						$ul.children(':not(:first)').remove();
						resetRecurringOptions('#customPopup2');
					}
					//TODO: find out why these inputs don't line up with the first one
					$ul.append(html);
				});
			}
			else {
				var html = '
Name<input type="text" name="name" autocomplete="off">
';
				html += '
Unit Price<input type="text" name="unitPrice" autocomplete="off">
';
				html += '
Quantity<input type="text" name="quantity" autocomplete="off" value="1">
';
				html += '
Recurring<select name="recurring"><option value="no">No</option><option value="yes">Yes</option></select>
';
				if ($ul.children().length > 1) {
					$ul.children(':not(:first)').remove();
					resetRecurringOptions('#customPopup2');
				}
				//TODO: find out why these inputs don't line up with the first one
				$ul.append(html);
			}
		});

		$('#customPopup2').on('change', '[name=recurring]', function() {
			if ($(this).val() == 'yes') {
				$('#customPopup2 ul').eq(1).show();
			}
			else {
				resetRecurringOptions('#customPopup2');
			}
		});

		$('#customBtn2').click(function() {
			var ajaxData = $('#customPopup2 input[type!="checkbox"], #customPopup2 select[name!=itemType]').serializeArray();
			var itemType = $('#customPopup2 [name=itemType]').val();
			itemType = (itemType == 'preDiscount') ? 'discount' : itemType;
			ajaxData.push(
				{'name': 'action', 'value': 'customAjax'},
				{'name': 'type', 'value': type},
				{'name': 'id', 'value': id},
				{'name': 'subAction', 'value': 'add'},
				{'name': 'subType', 'value': itemType}
			);
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: ajaxData
			}).done(function(data) {
				$('#customPopup2 .invalid').qtip('destroy', true);
				$('#customPopup2 .invalid').removeClass('invalid');
				if (data.status == 'success') {
					location.reload();
				}
				else {
					$.each(data, function(key, value) {
						if (key != 'status') {
							$('#customPopup2 [name=' + key + ']').addClass('invalid');
							$('#customPopup2 [name=' + key + ']').qtip({
								'content': value,
								'style': {'classes': 'qtip-tipsy-custom'},
								'position': {
									'my': 'bottom center',
									'at': 'top center'
								},
								show: {'event': 'focus'},
								hide: {'event': 'blur'}
							});
						}
					});
				}
			});
		});
		event.preventDefault();
	});

	//edit
	$('.customTable .controlEdit').click(function(event) {
		var $button = $(this);
		if ($button.hasClass('editEnabled')) {
			$('#customPopup3').show();
			$('#customPopup3 [name=unitPrice]').val($button.data('unitprice'));
			$('#customPopup3 [name=quantity]').val($button.data('quantity'));

			$('#customBtn3').click(function() {
				$.ajax({
					url: 'ajax.php',
					type: 'POST',
					data: {
						'action': 'customAjax',
						'type': type,
						'id': id,
						'subAction': 'edit',
						'subType': $button.data('type'),
						'subID':  $button.data('id'),
						'unitPrice': $('#customPopup3 [name=unitPrice]').val(),
						'quantity': $('#customPopup3 [name=quantity]').val()
					}
				}).done(function(data) {
					location.reload();
				});
			});
		}
		event.preventDefault();
	});

	//delete
	$('.customTable .controlDelete').click(function(event) {
		var $button = $(this);
		if ($button.hasClass('deleteEnabled')) {
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: {
					'action': 'customAjax',
					'type': type,
					'id': id,
					'subAction': 'delete',
					'subType': $button.data('type'),
					'subID':  $button.data('id')
				}
			}).done(function(data) {
				location.reload();
			});
		}
		event.preventDefault();
	});
});<commit_msg>This line isn't needed in Expense.<commit_after>$(document).ready(function() {
	//add
	$('#customAdd1').click(function(event) {
		$('#customPopup1').show();
		$('#customPopup1 input[type!="checkbox"], #customPopup1 select').val('');
		$('#customPopup1 .invalid').qtip('destroy', true);
		$('#customPopup1 .invalid').removeClass('invalid');

		$('#customBtn1').click(function() {
			var ajaxData = $('#customPopup1 input[type!="checkbox"], #customPopup1 select').serializeArray();
			ajaxData.push(
				{'name': 'action', 'value': 'customAjax'},
				{'name': 'type', 'value': type},
				{'name': 'id', 'value': id},
				{'name': 'subAction', 'value': 'add'},
				{'name': 'subType', 'value': 'payment'}
			);
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: ajaxData
			}).done(function(data) {
				$('#customPopup1 .invalid').qtip('destroy', true);
				$('#customPopup1 .invalid').removeClass('invalid');
				if (data.status == 'success') {
					location.reload();
				}
				else {
					$.each(data, function(key, value) {
						if (key != 'status') {
							$('#customPopup1 [name=' + key + ']').addClass('invalid');
							$('#customPopup1 [name=' + key + ']').qtip({
								'content': value,
								'style': {'classes': 'qtip-tipsy-custom'},
								'position': {
									'my': 'bottom center',
									'at': 'top center'
								},
								show: {'event': 'focus'},
								hide: {'event': 'blur'}
							});
						}
					});
				}
			});
		});
		event.preventDefault();
	});

	$('#customAdd2').click(function(event) {
		$('#customPopup2').show();
		$('#customPopup2 [name=itemType]').val('');
		$('#customPopup2 ul').eq(0).children(':not(:first)').remove();
		$('#customPopup2 .invalid').qtip('destroy', true);
		$('#customPopup2 .invalid').removeClass('invalid');
		resetRecurringOptions('#customPopup2');

		$('#customPopup2 [name=itemType]').change(function() {
			var $select = $(this);
			var $ul = $select.closest('ul');
			var itemType = $select.val();
			if (itemType == '') {
				$ul.children(':not(:first)').remove();
				$('#customPopup2 ul').eq(1).hide();
			}
			else if (itemType == 'product') {
				$.ajax({
					url: 'ajax.php',
					type: 'POST',
					data: {
						'action': 'customAjax',
						'type': type,
						'id': id,
						'subAction': 'list',
						'itemType': itemType
					}
				}).done(function(data) {
					var html = '
Product<select name="productID">' + data.products + '</select>
';
					html += '
Location<select name="locationID">' + data.locations + '</select>
';
					html += '
Unit Price<input type="text" name="unitPrice" autocomplete="off">
';
					html += '
Quantity<input type="text" name="quantity" autocomplete="off" value="1">
';
					html += '
Recurring<select name="recurring"><option value="no">No</option><option value="yes">Yes</option></select>
';
					if ($ul.children().length > 1) {
						$ul.children(':not(:first)').remove();
						resetRecurringOptions('#customPopup2');
					}
					//TODO: find out why these inputs don't line up with the first one
					$ul.append(html);
				});
			}
			else {
				var html = '
Name<input type="text" name="name" autocomplete="off">
';
				html += '
Unit Price<input type="text" name="unitPrice" autocomplete="off">
';
				html += '
Quantity<input type="text" name="quantity" autocomplete="off" value="1">
';
				html += '
Recurring<select name="recurring"><option value="no">No</option><option value="yes">Yes</option></select>
';
				if ($ul.children().length > 1) {
					$ul.children(':not(:first)').remove();
					resetRecurringOptions('#customPopup2');
				}
				//TODO: find out why these inputs don't line up with the first one
				$ul.append(html);
			}
		});

		$('#customPopup2').on('change', '[name=recurring]', function() {
			if ($(this).val() == 'yes') {
				$('#customPopup2 ul').eq(1).show();
			}
			else {
				resetRecurringOptions('#customPopup2');
			}
		});

		$('#customBtn2').click(function() {
			var ajaxData = $('#customPopup2 input[type!="checkbox"], #customPopup2 select[name!=itemType]').serializeArray();
			var itemType = $('#customPopup2 [name=itemType]').val();
			ajaxData.push(
				{'name': 'action', 'value': 'customAjax'},
				{'name': 'type', 'value': type},
				{'name': 'id', 'value': id},
				{'name': 'subAction', 'value': 'add'},
				{'name': 'subType', 'value': itemType}
			);
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: ajaxData
			}).done(function(data) {
				$('#customPopup2 .invalid').qtip('destroy', true);
				$('#customPopup2 .invalid').removeClass('invalid');
				if (data.status == 'success') {
					location.reload();
				}
				else {
					$.each(data, function(key, value) {
						if (key != 'status') {
							$('#customPopup2 [name=' + key + ']').addClass('invalid');
							$('#customPopup2 [name=' + key + ']').qtip({
								'content': value,
								'style': {'classes': 'qtip-tipsy-custom'},
								'position': {
									'my': 'bottom center',
									'at': 'top center'
								},
								show: {'event': 'focus'},
								hide: {'event': 'blur'}
							});
						}
					});
				}
			});
		});
		event.preventDefault();
	});

	//edit
	$('.customTable .controlEdit').click(function(event) {
		var $button = $(this);
		if ($button.hasClass('editEnabled')) {
			$('#customPopup3').show();
			$('#customPopup3 [name=unitPrice]').val($button.data('unitprice'));
			$('#customPopup3 [name=quantity]').val($button.data('quantity'));

			$('#customBtn3').click(function() {
				$.ajax({
					url: 'ajax.php',
					type: 'POST',
					data: {
						'action': 'customAjax',
						'type': type,
						'id': id,
						'subAction': 'edit',
						'subType': $button.data('type'),
						'subID':  $button.data('id'),
						'unitPrice': $('#customPopup3 [name=unitPrice]').val(),
						'quantity': $('#customPopup3 [name=quantity]').val()
					}
				}).done(function(data) {
					location.reload();
				});
			});
		}
		event.preventDefault();
	});

	//delete
	$('.customTable .controlDelete').click(function(event) {
		var $button = $(this);
		if ($button.hasClass('deleteEnabled')) {
			$.ajax({
				url: 'ajax.php',
				type: 'POST',
				data: {
					'action': 'customAjax',
					'type': type,
					'id': id,
					'subAction': 'delete',
					'subType': $button.data('type'),
					'subID':  $button.data('id')
				}
			}).done(function(data) {
				location.reload();
			});
		}
		event.preventDefault();
	});
});<|endoftext|>


<commit_before>// todo Add "Last Exectuted", "When to execute next", Pause button, and maybe "create by | or owned by team"

var module = angular.module('rollupApp', ['mgcrea.ngStrap',
	'mgcrea.ngStrap.tooltip', 'ui.bootstrap.modal', 'template/modal/backdrop.html',
	'template/modal/window.html']);

module.controller('rollupController', function ($scope, $http, $uibModal) {

	$scope.lastSaved = null;
	$scope.tasks = [];
	$scope.taskCopies = [];

	$http.get("/api/v1/rollups/rollup") //todo don't hard code
		.success(function (response) {

			if (response) {
				// todo sort by task name
				$scope.tasks = response;
				$scope.taskCopies = angular.copy($scope.tasks);
			}
			else
				$scope.tasks = [];
		})
		.error(function (data, status, headers, config) {
			$scope.alert("Could not read list of roll-ups from server.", status, data);
		});

	$scope.isUnchanged = function (task) {
		for (var i = 0; i < $scope.taskCopies.length; i++) {
			var original = $scope.taskCopies[i];
			if (task.id == original.id) {
				return angular.equals(task, original);
			}
		}
		return false; // New task
	};

	$scope.updateCopy = function (task) {
		for (var i = 0; i < $scope.taskCopies.length; i++) {
			var original = $scope.taskCopies[i];
			if (task.id == original.id) {
				$scope.taskCopies[i] = angular.copy(task);
				break;
			}
		}
	};

	$scope.onBlur = function (task) {
		if ($scope.isUnchanged(task))
			return;

		$scope.saveRollupTask(task);

		currentDate = new Date();
		$scope.lastSaved = (currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours()) + ":" +
			(currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes()) + ":" +
			(currentDate.getSeconds() < 10 ? "0" + currentDate.getSeconds() : currentDate.getSeconds());

		// Flash Last Saved message
		$('#lastSaved').fadeOut('slow').fadeIn('slow').animate({opacity: 1.0}, 1000);
	};

	$scope.toHumanReadableCron = function (schedule) {
		return prettyCron.toString(schedule);
	};

	$scope.toHumanReadableTimeUnit = function (timeUnit) {
		if (timeUnit.value == 1)
			return timeUnit.value + " " + timeUnit.unit.substring(0, timeUnit.unit.length - 1);
		else
			return timeUnit.value + " " + timeUnit.unit;
	};

	$scope.toTql = function (query) {
		tql = "SELECT " + query.query.metrics[0].name;
		tql += " FROM " + $scope.toHumanReadableTimeUnit(query.query.start_relative) + " to now";  // todo fix for non-relative times

		// Tags
		var first = true;
		if (query.query.metrics[0].tags && Object.keys(query.query.metrics[0].tags).length > 0) {
			tql += " WHERE ";

			var tags = query.query.metrics[0].tags;
			for (var tag in tags) {
				if (first)
					first = false;
				else {
					tql += ", ";
				}

				//noinspection JSUnfilteredForInLoop
				tql += tag + ' = [' + tags[tag] + ']';
			}
		}

		// Aggregators
		if (query.query.metrics[0].aggregators && query.query.metrics[0].aggregators.length > 0) {
			tql += " AGGREGATE ";

			first = true;
			query.query.metrics[0].aggregators.forEach(function (aggregator) {
				if (first)
					first = false;
				else
					tql += " | ";

				tql += $scope.toHumanReadableAggregator(aggregator);
			});
		}

		// Group By
		var groupBy = query.query.metrics[0].group_by;
		if (groupBy && groupBy.length > 0) {
			var groupExists = false;
			tql += " GROUP BY";
			_.each(groupBy, function (group) {
				if (groupExists) {
					tql += " AND";
				}

				if (group.name == "tag") {
					groupExists = true;
					tql += " tags(";
					_.each(group.tags, function (tag) {
						tql += '"' + tag + '", ';
					});
					tql = tql.substring(0, tql.length - 2); // Remove trailing comma and space
					tql += ")";
				}
				if (group.name == "time") {
					groupExists = true;
					tql += " time(" + $scope.toHumanReadableTimeUnit(group.range_size) + ", ";
					tql += group.group_count;
					tql += ")";
				}
				if (group.name == "value") {
					groupExists = true;
					tql += " value(" + group.range_size + ")";
				}
			});
		}

		// Save As
		tql += " SAVE AS " + query.save_as;

		return tql;
	};

	$scope.toHumanReadableAggregator = function (aggregator) {
		var result = aggregator.name + '(';
		if (aggregator.sampling) {
			result += $scope.toHumanReadableTimeUnit(aggregator.sampling) + ", ";
			result += aggregator.sampling.align_start_time ? aggregator.sampling.align_start_time : false;
		}
		else {
			_.each(_.values(_.omit(aggregator, 'name')), function (value) {
				result += value + ",";
			});
			result = result.substring(0, result.length - 1); // Remove trailing comma

		}
		result += ')';
		return result;
	};

	$scope.executeTimes = [
		'* * * * * ?',    // every minute
		'5 * * * * ?',    // every 5 minutes
		'10 * * * * ?',   // every 10 minutes
		'15 * * * * ?',   // every 15 minutes
		'30 * * * * ?',   // every 30 minutes
		'* 1 * * * ?',    // every 1 hour
		'* 6 * * * ?',    // every 6 hours
		'0 0 * * * ?',    // every day
		'0 0 * * sat ?'   // once a week
	];

	$scope.aggregators = ["sum", "avg", "min", "max"];

	$scope.scheduleModified = function (task, item) {
		task.schedule = item;
		$scope.onBlur(task);
	};

	$scope.startTimeModified = function (rollup, item) {
		rollup.tasks[0].query.start_relative = item;
	};

	$scope.aggregatorModified = function (rollup, item) {
		rollup.tasks[0].query.metrics[0].aggregators[0].name = item;
	};

	$scope.aggregatorSamplingModified = function (rollup, item) {
		rollup.tasks[0].query.metrics[0].aggregators[0].sampling = item;
	};

	$scope.addRollupTask = function () {
		var task = {schedule: '15 * * * * ?', rollups: []};
		task.edit = true;
		$scope.tasks.push(task);

	};

	$scope.editRollupTask = function (task) {
		task.edit = true;
	};

	$scope.saveRollupTask = function (task) {
		$scope.postNewRollupTask(task);
		task.edit = false;
	};

	$scope.deleteRollupTask = function (task) {
		bootbox.confirm({
			size: 'medium',
			message: "Are you sure you want to delete the rollup?",
			callback: function (result) {
				if (result) {
					var res = $http.delete('/api/v1/rollups/delete/' + task.id); // todo don't hardcode?
					res.success(function (data, status, headers, config) {
						var i = $scope.tasks.indexOf(task);
						if (i != -1) {
							$scope.tasks.splice(i, 1);
						}
					});
					res.error(function (data, status, headers, config) {
						$scope.alert("Failed to delete roll-up.", status, data);
					});
				}
			}
		});
	};

	$scope.addRollup = function (task, rollup, edit) {
		var modalInstance = $uibModal.open({
			templateUrl: 'rollup-create.html?cacheBust=' + Math.random().toString(36).slice(2), //keep dialog from caching
			controller: 'KairosDBTargetCtrl',
			size: 'lg',
			backdrop: 'static', // disable closing of dialog with click away
			keyboard: false, // disable closing dialog with ESC
			resolve: {
				rollup: function () {
					return rollup;
				}
			}
		});

		modalInstance.result.then(
			function (newRollup) {
				if (edit) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
				}
				task.rollups.push(newRollup);
				$scope.saveTask(task);
			});
	};

	$scope.pasteQuery = function (task, rollup, edit) {
		var modalInstance = $uibModal.open({
			templateUrl: 'paste-query.html?cacheBust=' + Math.random().toString(36).slice(2), //keep dialog from caching
			controller: 'PasteQueryCtrl',
			size: 'lg',
			backdrop: 'static', // disable closing of dialog with click away
			keyboard: false, // disable closing dialog with ESC
			resolve: {
				rollup: function () {
					return rollup;
				}
			}
		});

		modalInstance.result.then(
			function (newRollup) {
				if (edit) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
				}
				task.rollups.push(newRollup);
				$scope.saveTask(task);
			});
	};

	$scope.editRollup = function (task, rollup) {
		$scope.addRollup(task, rollup, true);
	};

	$scope.saveRollup = function (task, rollup) {
		rollup.edit = false;
	};

	$scope.deleteRollup = function (task, rollup) {
		bootbox.confirm({
			size: 'medium',
			message: "Are you sure you want to delete the query?",
			callback: function (result) {
				if (result) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
					$scope.saveRollupTask(task);
				}
			}
		});
	};

	$scope.postNewRollupTask = function (task) {
		// todo need to remove "edit" property

		var res = $http.post('/api/v1/rollups/task', task); // todo don't hardcode?
		res.success(function (data, status, headers, config) {
			$scope.updateCopy(task);
			//$scope.message = data;
			//console.log(status);
		});
		res.error(function (data, status, headers, config) {
			$scope.alert("Could not save roll-ups", status, data);
		});
	};

	$scope.saveTask = function (task) {
		// todo need to remove "edit" property

		var res = $http.post('/api/v1/rollups/rollup', task); // todo don't hardcode?
		res.success(function (data, status, headers, config) {
			//console.log(status);
		});
		res.error(function (data, status, headers, config) {
			$scope.alert("Could not save query.", status, data);
		});
	};

	// TODO how to not duplicate in the controller?
	$scope.alert = function (message, status, data) {
		//alert(message);
		var error = "";
		if (data && data.errors)
			error = data.errors;

		bootbox.alert({
			title: message,
			message: status + ":" + (error ? error : "" )
		});
	};

});<commit_msg>List of rollups is now sorted by name.<commit_after>// todo Add "Last Exectuted", "When to execute next", Pause button, and maybe "create by | or owned by team"

var module = angular.module('rollupApp', ['mgcrea.ngStrap',
	'mgcrea.ngStrap.tooltip', 'ui.bootstrap.modal', 'template/modal/backdrop.html',
	'template/modal/window.html']);

module.controller('rollupController', function ($scope, $http, $uibModal, orderByFilter) {

	$scope.lastSaved = null;
	$scope.tasks = [];
	$scope.taskCopies = [];

	$http.get("/api/v1/rollups/rollup") //todo don't hard code
		.success(function (response) {

			if (response) {
				// todo sort by task name
				$scope.tasks = response;
				$scope.taskCopies = angular.copy($scope.tasks);
				$scope.tasks = orderByFilter($scope.tasks, "name");
			}
			else
				$scope.tasks = [];
		})
		.error(function (data, status, headers, config) {
			$scope.alert("Could not read list of roll-ups from server.", status, data);
		});

	$scope.isUnchanged = function (task) {
		for (var i = 0; i < $scope.taskCopies.length; i++) {
			var original = $scope.taskCopies[i];
			if (task.id == original.id) {
				return angular.equals(task, original);
			}
		}
		return false; // New task
	};

	$scope.updateCopy = function (task) {
		for (var i = 0; i < $scope.taskCopies.length; i++) {
			var original = $scope.taskCopies[i];
			if (task.id == original.id) {
				$scope.taskCopies[i] = angular.copy(task);
				break;
			}
		}
	};

	$scope.onBlur = function (task) {
		$scope.tasks = orderByFilter($scope.tasks, "name");

		if ($scope.isUnchanged(task))
			return;

		$scope.saveRollupTask(task);

		currentDate = new Date();
		$scope.lastSaved = (currentDate.getHours() < 10 ? "0" + currentDate.getHours() : currentDate.getHours()) + ":" +
			(currentDate.getMinutes() < 10 ? "0" + currentDate.getMinutes() : currentDate.getMinutes()) + ":" +
			(currentDate.getSeconds() < 10 ? "0" + currentDate.getSeconds() : currentDate.getSeconds());

		// Flash Last Saved message
		$('#lastSaved').fadeOut('slow').fadeIn('slow').animate({opacity: 1.0}, 1000);
	};

	$scope.toHumanReadableCron = function (schedule) {
		return prettyCron.toString(schedule);
	};

	$scope.toHumanReadableTimeUnit = function (timeUnit) {
		if (timeUnit.value == 1)
			return timeUnit.value + " " + timeUnit.unit.substring(0, timeUnit.unit.length - 1);
		else
			return timeUnit.value + " " + timeUnit.unit;
	};

	$scope.toTql = function (query) {
		tql = "SELECT " + query.query.metrics[0].name;
		tql += " FROM " + $scope.toHumanReadableTimeUnit(query.query.start_relative) + " to now";  // todo fix for non-relative times

		// Tags
		var first = true;
		if (query.query.metrics[0].tags && Object.keys(query.query.metrics[0].tags).length > 0) {
			tql += " WHERE ";

			var tags = query.query.metrics[0].tags;
			for (var tag in tags) {
				if (first)
					first = false;
				else {
					tql += ", ";
				}

				//noinspection JSUnfilteredForInLoop
				tql += tag + ' = [' + tags[tag] + ']';
			}
		}

		// Aggregators
		if (query.query.metrics[0].aggregators && query.query.metrics[0].aggregators.length > 0) {
			tql += " AGGREGATE ";

			first = true;
			query.query.metrics[0].aggregators.forEach(function (aggregator) {
				if (first)
					first = false;
				else
					tql += " | ";

				tql += $scope.toHumanReadableAggregator(aggregator);
			});
		}

		// Group By
		var groupBy = query.query.metrics[0].group_by;
		if (groupBy && groupBy.length > 0) {
			var groupExists = false;
			tql += " GROUP BY";
			_.each(groupBy, function (group) {
				if (groupExists) {
					tql += " AND";
				}

				if (group.name == "tag") {
					groupExists = true;
					tql += " tags(";
					_.each(group.tags, function (tag) {
						tql += '"' + tag + '", ';
					});
					tql = tql.substring(0, tql.length - 2); // Remove trailing comma and space
					tql += ")";
				}
				if (group.name == "time") {
					groupExists = true;
					tql += " time(" + $scope.toHumanReadableTimeUnit(group.range_size) + ", ";
					tql += group.group_count;
					tql += ")";
				}
				if (group.name == "value") {
					groupExists = true;
					tql += " value(" + group.range_size + ")";
				}
			});
		}

		// Save As
		tql += " SAVE AS " + query.save_as;

		return tql;
	};

	$scope.toHumanReadableAggregator = function (aggregator) {
		var result = aggregator.name + '(';
		if (aggregator.sampling) {
			result += $scope.toHumanReadableTimeUnit(aggregator.sampling) + ", ";
			result += aggregator.sampling.align_start_time ? aggregator.sampling.align_start_time : false;
		}
		else {
			_.each(_.values(_.omit(aggregator, 'name')), function (value) {
				result += value + ",";
			});
			result = result.substring(0, result.length - 1); // Remove trailing comma

		}
		result += ')';
		return result;
	};

	$scope.executeTimes = [
		'* * * * * ?',    // every minute
		'5 * * * * ?',    // every 5 minutes
		'10 * * * * ?',   // every 10 minutes
		'15 * * * * ?',   // every 15 minutes
		'30 * * * * ?',   // every 30 minutes
		'* 1 * * * ?',    // every 1 hour
		'* 6 * * * ?',    // every 6 hours
		'0 0 * * * ?',    // every day
		'0 0 * * sat ?'   // once a week
	];

	$scope.aggregators = ["sum", "avg", "min", "max"];

	$scope.scheduleModified = function (task, item) {
		task.schedule = item;
		$scope.onBlur(task);
	};

	$scope.startTimeModified = function (rollup, item) {
		rollup.tasks[0].query.start_relative = item;
	};

	$scope.aggregatorModified = function (rollup, item) {
		rollup.tasks[0].query.metrics[0].aggregators[0].name = item;
	};

	$scope.aggregatorSamplingModified = function (rollup, item) {
		rollup.tasks[0].query.metrics[0].aggregators[0].sampling = item;
	};

	$scope.addRollupTask = function () {
		var task = {schedule: '15 * * * * ?', rollups: []};
		task.edit = true;
		$scope.tasks.push(task);

	};

	$scope.editRollupTask = function (task) {
		task.edit = true;
	};

	$scope.saveRollupTask = function (task) {
		$scope.postNewRollupTask(task);
		task.edit = false;
	};

	$scope.deleteRollupTask = function (task) {
		bootbox.confirm({
			size: 'medium',
			message: "Are you sure you want to delete the rollup?",
			callback: function (result) {
				if (result) {
					var res = $http.delete('/api/v1/rollups/delete/' + task.id); // todo don't hardcode?
					res.success(function (data, status, headers, config) {
						var i = $scope.tasks.indexOf(task);
						if (i != -1) {
							$scope.tasks.splice(i, 1);
						}
					});
					res.error(function (data, status, headers, config) {
						$scope.alert("Failed to delete roll-up.", status, data);
					});
				}
			}
		});
	};

	$scope.addRollup = function (task, rollup, edit) {
		var modalInstance = $uibModal.open({
			templateUrl: 'rollup-create.html?cacheBust=' + Math.random().toString(36).slice(2), //keep dialog from caching
			controller: 'KairosDBTargetCtrl',
			size: 'lg',
			backdrop: 'static', // disable closing of dialog with click away
			keyboard: false, // disable closing dialog with ESC
			resolve: {
				rollup: function () {
					return rollup;
				}
			}
		});

		modalInstance.result.then(
			function (newRollup) {
				if (edit) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
				}
				task.rollups.push(newRollup);
				$scope.saveTask(task);
			});
	};

	$scope.pasteQuery = function (task, rollup, edit) {
		var modalInstance = $uibModal.open({
			templateUrl: 'paste-query.html?cacheBust=' + Math.random().toString(36).slice(2), //keep dialog from caching
			controller: 'PasteQueryCtrl',
			size: 'lg',
			backdrop: 'static', // disable closing of dialog with click away
			keyboard: false, // disable closing dialog with ESC
			resolve: {
				rollup: function () {
					return rollup;
				}
			}
		});

		modalInstance.result.then(
			function (newRollup) {
				if (edit) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
				}
				task.rollups.push(newRollup);
				$scope.saveTask(task);
			});
	};

	$scope.editRollup = function (task, rollup) {
		$scope.addRollup(task, rollup, true);
	};

	$scope.saveRollup = function (task, rollup) {
		rollup.edit = false;
	};

	$scope.deleteRollup = function (task, rollup) {
		bootbox.confirm({
			size: 'medium',
			message: "Are you sure you want to delete the query?",
			callback: function (result) {
				if (result) {
					task.rollups.splice(task.rollups.indexOf(rollup), 1);
					$scope.saveRollupTask(task);
				}
			}
		});
	};

	$scope.postNewRollupTask = function (task) {
		// todo need to remove "edit" property

		var res = $http.post('/api/v1/rollups/task', task); // todo don't hardcode?
		res.success(function (data, status, headers, config) {
			$scope.updateCopy(task);
			//$scope.message = data;
			//console.log(status);
		});
		res.error(function (data, status, headers, config) {
			$scope.alert("Could not save roll-ups", status, data);
		});
	};

	$scope.saveTask = function (task) {
		// todo need to remove "edit" property

		var res = $http.post('/api/v1/rollups/rollup', task); // todo don't hardcode?
		res.success(function (data, status, headers, config) {
			//console.log(status);
		});
		res.error(function (data, status, headers, config) {
			$scope.alert("Could not save query.", status, data);
		});
	};

	// TODO how to not duplicate in the controller?
	$scope.alert = function (message, status, data) {
		//alert(message);
		var error = "";
		if (data && data.errors)
			error = data.errors;

		bootbox.alert({
			title: message,
			message: status + ":" + (error ? error : "" )
		});
	};

});<|endoftext|>


<commit_before>import React from 'react';
import * as time from '../Utils/time.js';
import * as DB from '../../constants/database.js'
import '../../styles/activities.css';
import { getField } from './activityfns.js';
import { Accordion, Button, Card, Col, Form, Row } from 'react-bootstrap';

/**
 * Returns a dropdown of all the timezones.
 */
function timezonePicker() {
  // TODO: Make this dropdown. (#51)
  return

}

/**
 * A single activity.
 *
 * @param {Object} props This component expects the following props:
 * - `activity` {Object} The activity to display.
 */
class Activity extends React.Component {
  /** {@inheritdoc} */
  constructor(props) {
    super(props);

    this.state = { editing: false };

    // Bind state users/modifiers to `this`.
    this.setEditActivity = this.setEditActivity.bind(this);
    this.finishEditActivity = this.finishEditActivity.bind(this);
    this.displayCard = this.displayCard.bind(this);
  }

  /**
   * Set the activity into editing mode.
   */
  setEditActivity = () => ( this.setState({editing: true}) );

  /**
   * Set the activity into viewing mode.
   */
  finishEditActivity = () => (  this.setState({editing: false}) );

  /**
   * Display the current activity, either in view or display mode.
   */
  displayCard() {
    let activity = this.props.activity;
    if (!this.state.editing) { // View mode.
      return (
        <Card.Body onClick={this.setEditActivity}>

Start time: {time.timestampToFormatted(activity[DB.ACTIVITIES_START_TIME])}

End time: {time.timestampToFormatted(activity[DB.ACTIVITIES_END_TIME])}

        </Card.Body>
      );
    } else { // Edit mode.
      return (
        // TODO: Save form. (#48)
        <Form className="activity-editor" onSubmit={this.finishEditActivity}>
          <Form.Group as={Row} controlId="formActivityTitle">
            <Col sm={2}><Form.Label>Title:</Form.Label></Col>
            <Col><Form.Control type="text" placeholder={activity[DB.ACTIVITIES_TITLE]}/></Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityStartTime">
            <Col sm={2}><Form.Label>From:</Form.Label></Col>
            <Col sm={4}><Form.Control type="date" label="date"/></Col>
            <Col sm={2}><Form.Control type="time" label="time"/></Col>
            <Col sm={1}>{timezonePicker()}</Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityStartTime">
            <Col sm={2}><Form.Label>To:</Form.Label></Col>
            <Col sm={4}><Form.Control type="date" label="date"/></Col>
            <Col sm={2}><Form.Control type="time" label="time"/></Col>
            <Col sm={1}>{timezonePicker()}</Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityTitle">
            <Col sm={2}><Form.Label>Description:</Form.Label></Col>
            <Col><Form.Control type="text"
              placeholder={getField(activity, DB.ACTIVITIES_DESCRIPTION, "Add some details!") }/>
            </Col>
          </Form.Group>
          <Button type="submit" className="float-right">Done!</Button>
        </Form>
      )
    }
  }

  /** @inheritdoc */
  render() {
    const activity = this.props.activity;
    return (
      <Accordion defaultActiveKey='1'>
        <Card>
          <Accordion.Toggle as={Card.Header} eventKey='0' align='center' >
            {activity[DB.ACTIVITIES_TITLE]}
          </Accordion.Toggle>
          <Accordion.Collapse eventKey='0' className={'view-activity' + (this.state.editing? ' edit': '')}>
            { this.displayCard() }
          </Accordion.Collapse>
        </Card>
      </Accordion>
    );
  }
};

export default Activity;
<commit_msg>Wire up the form<commit_after>import React from 'react';
import * as time from '../Utils/time.js';
import * as DB from '../../constants/database.js'
import '../../styles/activities.css';
import { getField } from './activityfns.js';
import { Accordion, Button, Card, Col, Form, Row } from 'react-bootstrap';

/**
 * Returns a dropdown of all the timezones.
 */
function timezonePicker() {
  // TODO: Make this dropdown. (#51)
  return

}

/**
 * A single activity.
 *
 * @param {Object} props This component expects the following props:
 * - `activity` {Object} The activity to display.
 */
class Activity extends React.Component {
  /** {@inheritdoc} */
  constructor(props) {
    super(props);

    this.state = { editing: false, title: "abcd" };

    // Bind state users/modifiers to `this`.
    this.setEditActivity = this.setEditActivity.bind(this);
    this.finishEditActivity = this.finishEditActivity.bind(this);
    this.displayCard = this.displayCard.bind(this);

    // References.
    this.editTitleRef = React.createRef();
    this.editStartDateRef = React.createRef();
    this.editEndDateRef = React.createRef();
    this.editStartTimeRef = React.createRef();
    this.editEndTimeRef = React.createRef();
    this.editDescriptionRef = React.createRef();
  }

  /**
   * Set the activity into editing mode.
   */
  setEditActivity = () => ( this.setState({editing: true}) );

  /**
   * Set the activity into viewing mode.
   */
  finishEditActivity = (event) => {
    this.setState({editing: false});
    event.preventDefault();
  };

  /**
   * Display the current activity, either in view or display mode.
   */
  displayCard() {
    let activity = this.props.activity;
    if (!this.state.editing) { // View mode.
      return (
        <Card.Body onClick={this.setEditActivity}>

Start time: {time.timestampToFormatted(activity[DB.ACTIVITIES_START_TIME])}

End time: {time.timestampToFormatted(activity[DB.ACTIVITIES_END_TIME])}

        </Card.Body>
      );
    } else { // Edit mode.
      return (
        // TODO: Save form. (#48)
        <Form className="activity-editor" onSubmit={this.finishEditActivity}>
          <Form.Group as={Row} controlId="formActivityTitle">
            <Col sm={2}><Form.Label>Title:</Form.Label></Col>
            <Col><Form.Control type="text" placeholder={activity[DB.ACTIVITIES_TITLE]} ref={this.editTitleReference}/></Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityStartTime">
            <Col sm={2}><Form.Label>From:</Form.Label></Col>
            <Col sm={4}><Form.Control type="date" label="date" ref={this.editStartDateRef}/></Col>
            <Col sm={2}><Form.Control type="time" label="time" ref={this.editStartTimeRef}/></Col>
            <Col sm={1}>{timezonePicker()}</Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityStartTime">
            <Col sm={2}><Form.Label>To:</Form.Label></Col>
            <Col sm={4}><Form.Control type="date" label="date" ref={this.editEndDateRef}/></Col>
            <Col sm={2}><Form.Control type="time" label="time" ref={this.editEndTimeRef}/></Col>
            <Col sm={1}>{timezonePicker()}</Col>
          </Form.Group>
          <Form.Group as={Row} controlId="formActivityTitle">
            <Col sm={2}><Form.Label>Description:</Form.Label></Col>
            <Col><Form.Control type="text"
              placeholder={getField(activity, DB.ACTIVITIES_DESCRIPTION, "Add some details!")}
              ref={this.editDescriptionRef} />
            </Col>
          </Form.Group>
          <Button type="submit" className="float-right">Done!</Button>
        </Form>
      )
    }
  }

  /** @inheritdoc */
  render() {
    const activity = this.props.activity;
    return (
      <Accordion defaultActiveKey='1'>
        <Card>
          <Accordion.Toggle as={Card.Header} eventKey='0' align='center' >
            {activity[DB.ACTIVITIES_TITLE]}
          </Accordion.Toggle>
          <Accordion.Collapse eventKey='0' className={'view-activity' + (this.state.editing? ' edit': '')}>
            { this.displayCard() }
          </Accordion.Collapse>
        </Card>
      </Accordion>
    );
  }
};

export default Activity;
<|endoftext|>


<commit_before>/*
 * chroot-test.js: Tests for the `chroot` plugin responsible for enforcing chroot on spawned apps.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    exec = require('child_process').exec,
    fs = require('fs'),
    path = require('path'),
    eyes = require('eyes'),
    vows = require('vows'),
    helpers = require('../helpers'),
    haibu = require('../../lib/haibu'),
    config = haibu.config;

var repo, npmApp, app = {
  "name": "test",
  "user": "marak",
  "repository": {
    "type": "git",
    "url": "https://github.com/Marak/hellonode.git",
    "branch": "master"
  },
  "scripts": {
    "start": "server.js"
  },
  "directories": {
    "home": "hellonode"
  }
};

vows.describe('haibu/plugins/chroot').addBatch(helpers.requireInit()).addBatch({
  "This test requires the chroot plugin": {
    "should respond without an error": function () {
      haibu.use(haibu.chroot);
      assert.isTrue(true);
    }
  }
}).addBatch({
  "This test requires the chroot directory": {
    topic: function () {
      var that = this,
          root = haibu.config.get('chroot:root'),
          directories;

      directories = [
        root,
        path.join(root, 'usr', 'local', 'lib', 'node'),
        path.join(root, 'usr', 'local', 'bin'),
        path.join(root, 'tmp')
      ];

      exec('mkdir -p ' + directories.join(' '), function () {
        that.callback();
      });
    },
    "should create the chroot directory": function () {
      assert.isTrue(true);
    }
  }
}).addBatch({
  "An instance of haibu.Spawner": {
    topic: function (spawner) {
      return new haibu.Spawner({ chroot: true, maxRestart: 1 });
    },
    "when passed a valid app json": {
      "the trySpawn() method": {
        topic: function (spawner) {
          spawner.trySpawn(app, this.callback);
        },
        "should return a valid drone result object": function (err, result) {
          assert.isNull(err);
          assert.isNotNull(result.drone);
          result.process.kill();
        }
      }
    },
    "when passed a valid app json with npm dependencies": {
      "the trySpawn() method": {
        topic: function (spawner) {

          var sourceDir = path.join(__dirname, '..', 'fixtures', 'repositories', 'npm-deps'),
              pkgJson = fs.readFileSync(path.join(sourceDir, 'package.json'));

          npmApp = JSON.parse(pkgJson);
          npmApp.user = 'charlie';
          npmApp.repository.directory = sourceDir;
          repo = haibu.repository.create(npmApp);
          spawner.trySpawn(repo, this.callback);
        },
        "should return a valid drone result object": function (err, result) {
          assert.isNull(err);
          assert.isNotNull(result.drone);
          result.process.kill();
        }
      }
    }
  }
}).addBatch({
  "An instance of the Npm repository": {
    "the allDependencies() method": {
      topic: function () {
        var that = this;
        new (haibu.drone.Drone)().clean(npmApp, function (err) {
          if (err) {
            that.callback(err);
          }

          path.exists(repo.homeDir, that.callback.bind(null, null));
        });
      },
      "should remove the dependencies from the node_modules directory": function (err, exists) {
        assert.isTrue(!err);
        assert.isFalse(exists);
      }
    }
  }
}).export(module);<commit_msg>[test] Only run `test/plugins/chroot-test.js` when --chroot is set<commit_after>/*
 * chroot-test.js: Tests for the `chroot` plugin responsible for enforcing chroot on spawned apps.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var assert = require('assert'),
    exec = require('child_process').exec,
    fs = require('fs'),
    path = require('path'),
    argv = require('optimist').argv,
    eyes = require('eyes'),
    vows = require('vows'),
    helpers = require('../helpers'),
    haibu = require('../../lib/haibu'),
    config = haibu.config;

var repo, npmApp, app = {
  "name": "test",
  "user": "marak",
  "repository": {
    "type": "git",
    "url": "https://github.com/Marak/hellonode.git",
    "branch": "master"
  },
  "scripts": {
    "start": "server.js"
  },
  "directories": {
    "home": "hellonode"
  }
};

if (argv.chroot) {
  vows.describe('haibu/plugins/chroot').addBatch(helpers.requireInit()).addBatch({
    "This test requires the chroot plugin": {
      "should respond without an error": function () {
        haibu.use(haibu.chroot);
        assert.isTrue(true);
      }
    }
  }).addBatch({
    "This test requires the chroot directory": {
      topic: function () {
        var that = this,
            root = haibu.config.get('chroot:root'),
            directories;

        directories = [
          root,
          path.join(root, 'usr', 'local', 'lib', 'node'),
          path.join(root, 'usr', 'local', 'bin'),
          path.join(root, 'tmp')
        ];

        exec('mkdir -p ' + directories.join(' '), function () {
          that.callback();
        });
      },
      "should create the chroot directory": function () {
        assert.isTrue(true);
      }
    }
  }).addBatch({
    "An instance of haibu.Spawner": {
      topic: function (spawner) {
        return new haibu.Spawner({ chroot: true, maxRestart: 1 });
      },
      "when passed a valid app json": {
        "the trySpawn() method": {
          topic: function (spawner) {
            spawner.trySpawn(app, this.callback);
          },
          "should return a valid drone result object": function (err, result) {
            assert.isNull(err);
            assert.isNotNull(result.drone);
            result.process.kill();
          }
        }
      },
      "when passed a valid app json with npm dependencies": {
        "the trySpawn() method": {
          topic: function (spawner) {

            var sourceDir = path.join(__dirname, '..', 'fixtures', 'repositories', 'npm-deps'),
                pkgJson = fs.readFileSync(path.join(sourceDir, 'package.json'));

            npmApp = JSON.parse(pkgJson);
            npmApp.user = 'charlie';
            npmApp.repository.directory = sourceDir;
            repo = haibu.repository.create(npmApp);
            spawner.trySpawn(repo, this.callback);
          },
          "should return a valid drone result object": function (err, result) {
            assert.isNull(err);
            assert.isNotNull(result.drone);
            result.process.kill();
          }
        }
      }
    }
  }).addBatch({
    "An instance of the Npm repository": {
      "the allDependencies() method": {
        topic: function () {
          var that = this;
          new (haibu.drone.Drone)().clean(npmApp, function (err) {
            if (err) {
              that.callback(err);
            }

            path.exists(repo.homeDir, that.callback.bind(null, null));
          });
        },
        "should remove the dependencies from the node_modules directory": function (err, exists) {
          assert.isTrue(!err);
          assert.isFalse(exists);
        }
      }
    }
  }).export(module);
}<|endoftext|>


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

PT.renderCartoDBMap = function(containerId){
  var url, mapOptions;
  url = "http://cfcm.cartodb.com/api/v2/viz/21e86fd4-5887-11e4-b28c-0e018d66dc29/viz.json";
  mapOptions = {
    zoom: 15,
    center_lat: -19.9540425000,
    center_lon: -43.9385429000,
    scrollwheel: false,
    zoomControl: false,
    shareable: false,
    searchControl: false
  };

  $.getScript("http://libs.cartocdn.com/cartodb.js/v3/cartodb.js", function(){
    cartodb.createVis('map-viz', url, mapOptions)
    .done(function(){
      $(".cartodb-searchbox").hide();
    });
  });
};

PT.populateImages = function(responses, containerId){
  var $container = $(containerId), images, image;
  var images = [];
  responses.forEach(function(response){
    response.answers.forEach(function(answer){

      if(answer.input_type == "image" && answer.value){
        images.push(answer.value);
      }
    })
  });

  $container.empty();
  images.forEach(function(url){
    image = '';
    $container.append(image);
  })
};

PT.renderGoogleMap = function(serverResponse){
  var markers = [];
  var surveyDefinition = {};
  var map = null,
      infoWindow = null;

  var attachMarkerClickEvent = function(marker, response){
    var image = null;
    var displayableAnswers = {};
    // 1. browse through answers, put all images to $image and displayable answers to $displayableAnswers.
    for(var i=0,len=response.answers.length;i<len;i++){
      var answer = response.answers[i];
      switch(answer.input_type){
        case 'location':
          break;
        case 'image':
          if(typeof answer.value!=='undefined' && answer.value){
            image = answer.value;
          }
          break;
        default:
          if(typeof answer.value!=='undefined' && answer.value){
            displayableAnswers[surveyDefinition[answer.id]] = answer.value;
          }
      }
    }
    // 2. construct infoWindow string
    var infoWindowImageHtml='';
    if(image !== null){
      infoWindowImageHtml =
      '
'+
        ''+
      '
';
    }
    var infoWindowTableHtml = '';
    if(Object.keys(displayableAnswers).length > 0){
      infoWindowTableHtml = '

';
      for(var i in displayableAnswers){
        infoWindowTableHtml += '
'+i+'

'+displayableAnswers[i]+'
';
      }
      infoWindowTableHtml += '

';
    }
    var infoWindowContentHtml =
      '
'+
        infoWindowImageHtml+
        infoWindowTableHtml+
      '
';

    google.maps.event.addListener(marker, 'click', function(){
      infoWindow.setContent(infoWindowContentHtml);
      infoWindow.open(map, marker);
    });
  };  // func attachMarkerClickEvent

  window.mapInit = function(){
    // start initialization
    // create map
    var mapOptions = {
      zoom: 8,
      center: new google.maps.LatLng(-34.397, 150.644),
      scrollwheel: false
    };
    map = new google.maps.Map(document.getElementById('map-viz'), mapOptions);
    infoWindow = new google.maps.InfoWindow({
        content: ''
    });

    // store survey definition
    var surveyInputs = serverResponse.survey.inputs;
    for(var i=0,len=surveyInputs.length;i<len;i++){
      var input = surveyInputs[i];
      surveyDefinition[input.id] = input.label;
    }

    // create map markers
    var surveyResponses = serverResponse.responses;
    for(var i=0,len=surveyResponses.length;i<len;i++){
      var response = surveyResponses[i];

      // 1 find geo location
      var lat = null,
          lon = null;
      for(var j=0,len2=response.answers.length;j<len2;j++){
        var answer = response.answers[j];
        if(typeof answer.input_type!=='undefined' && answer.input_type=='location' && typeof answer.value!=='undefined' && typeof answer.value.lon!=='undefined'){
          lat = answer.value.lat;
          lon = answer.value.lon;
          break;
        }
      }
      if(lat==null || lon==null){
        if(response.locationstamp && response.locationstamp.lat !== 'undefined' && response.locationstamp.long !== 'undefined'){
          lat = response.locationstamp.lat;
          lon = response.locationstamp.lon;
        } else {
          continue; // find if next response is a geolocation
        }
      }

      // 2. create that marker;
      if(lat && lon){
        var marker = new google.maps.Marker({
          map: map,
          position: new google.maps.LatLng(lat, lon)
        });
        markers.push(marker);

        // 3. attach onclick event
        attachMarkerClickEvent(marker, response);
      }
    } // for surveyResponses - created map markers

    // scale map viewport to include all the markers.
    var points = $.map(markers, function(a){
        return a.getPosition();
    });
    var bounds = new google.maps.LatLngBounds();
    for(var i=0;i<points.length;i++){
        bounds.extend(points[i]);
    }

    map.fitBounds(bounds);

  };  // func mapInit

  // load map script

  if(typeof google === 'undefined'){
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&' +
        'callback=mapInit';
    document.body.appendChild(script);
  } else {
    window.mapInit();
  }
};

$(function(){
  dispatcher.subscribe('sharedataloaded', function(data){
    PT.populateImages(PT.responses, "#image-viz");
    PT.renderGoogleMap(data);
  })
});
<commit_msg>Add response id to map info window<commit_after>var PT = PT || {};

PT.renderCartoDBMap = function(containerId){
  var url, mapOptions;
  url = "http://cfcm.cartodb.com/api/v2/viz/21e86fd4-5887-11e4-b28c-0e018d66dc29/viz.json";
  mapOptions = {
    zoom: 15,
    center_lat: -19.9540425000,
    center_lon: -43.9385429000,
    scrollwheel: false,
    zoomControl: false,
    shareable: false,
    searchControl: false
  };

  $.getScript("http://libs.cartocdn.com/cartodb.js/v3/cartodb.js", function(){
    cartodb.createVis('map-viz', url, mapOptions)
    .done(function(){
      $(".cartodb-searchbox").hide();
    });
  });
};

PT.populateImages = function(responses, containerId){
  var $container = $(containerId), images, image;
  var images = [];
  responses.forEach(function(response){
    response.answers.forEach(function(answer){

      if(answer.input_type == "image" && answer.value){
        images.push(answer.value);
      }
    })
  });

  $container.empty();
  images.forEach(function(url){
    image = '';
    $container.append(image);
  })
};

PT.renderGoogleMap = function(serverResponse){
  var markers = [];
  var surveyDefinition = {};
  var map = null,
      infoWindow = null;

  var attachMarkerClickEvent = function(marker, response){
    var image = null;
    var displayableAnswers = {};
    // 1. browse through answers, put all images to $image and displayable answers to $displayableAnswers.
    for(var i=0,len=response.answers.length;i<len;i++){
      var answer = response.answers[i];
      switch(answer.input_type){
        case 'location':
          break;
        case 'image':
          if(typeof answer.value!=='undefined' && answer.value){
            image = answer.value;
          }
          break;
        default:
          if(typeof answer.value!=='undefined' && answer.value){
            displayableAnswers[surveyDefinition[answer.id]] = answer.value;
          }
      }
    }
    // 2. construct infoWindow string
    var infoWindowImageHtml='';
    if(image !== null){
      infoWindowImageHtml =
      '
'+
        ''+
      '
';
    }
    var infoWindowTableHtml = '';
    if(Object.keys(displayableAnswers).length > 0){
      infoWindowTableHtml = '

';
      for(var i in displayableAnswers){
        infoWindowTableHtml += '
'+i+'

'+displayableAnswers[i]+'
';
      }
      infoWindowTableHtml += '

';
    }
    var infoWindowContentHtml =
      '
'+
        infoWindowImageHtml+
        infoWindowTableHtml+
      '
';

    google.maps.event.addListener(marker, 'click', function(){
      infoWindow.setContent(infoWindowContentHtml);
      infoWindow.open(map, marker);
    });
  };  // func attachMarkerClickEvent

  window.mapInit = function(){
    // start initialization
    // create map
    var mapOptions = {
      zoom: 8,
      center: new google.maps.LatLng(-34.397, 150.644),
      scrollwheel: false
    };
    map = new google.maps.Map(document.getElementById('map-viz'), mapOptions);
    infoWindow = new google.maps.InfoWindow({
        content: ''
    });

    // store survey definition
    var surveyInputs = serverResponse.survey.inputs;
    for(var i=0,len=surveyInputs.length;i<len;i++){
      var input = surveyInputs[i];
      surveyDefinition[input.id] = input.label;
    }

    // create map markers
    var surveyResponses = serverResponse.responses;
    for(var i=0,len=surveyResponses.length;i<len;i++){
      var response = surveyResponses[i];

      // 1 find geo location
      var lat = null,
          lon = null;
      for(var j=0,len2=response.answers.length;j<len2;j++){
        var answer = response.answers[j];
        if(typeof answer.input_type!=='undefined' && answer.input_type=='location' && typeof answer.value!=='undefined' && typeof answer.value.lon!=='undefined'){
          lat = answer.value.lat;
          lon = answer.value.lon;
          break;
        }
      }
      if(lat==null || lon==null){
        if(response.locationstamp && response.locationstamp.lat !== 'undefined' && response.locationstamp.long !== 'undefined'){
          lat = response.locationstamp.lat;
          lon = response.locationstamp.lon;
        } else {
          continue; // find if next response is a geolocation
        }
      }

      // 2. create that marker;
      if(lat && lon){
        var marker = new google.maps.Marker({
          map: map,
          position: new google.maps.LatLng(lat, lon)
        });
        markers.push(marker);

        // 3. attach onclick event
        attachMarkerClickEvent(marker, response);
      }
    } // for surveyResponses - created map markers

    // scale map viewport to include all the markers.
    var points = $.map(markers, function(a){
        return a.getPosition();
    });
    var bounds = new google.maps.LatLngBounds();
    for(var i=0;i<points.length;i++){
        bounds.extend(points[i]);
    }

    map.fitBounds(bounds);

  };  // func mapInit

  // load map script

  if(typeof google === 'undefined'){
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&' +
        'callback=mapInit';
    document.body.appendChild(script);
  } else {
    window.mapInit();
  }
};

$(function(){
  dispatcher.subscribe('sharedataloaded', function(data){
    PT.populateImages(PT.responses, "#image-viz");
    PT.renderGoogleMap(data);
  })
});
<|endoftext|>


<commit_before>function isPunctuation(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION);
}
function isHiragana(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.HIRAGANA);
}

function isKatakana(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.KATAKANA);
}
function isKanji(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) ||
           (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
}

function getStartIndexOfKanjiAt(str, atIndex) {
    if  (!isKanji(str[atIndex])) {
        return -1;
    }
    while (atIndex > 0) {
        if (!isKanji(str, atIndex)) {
            return atIndex + 1;
        }
        atIndex--;
    }
    return 0;
}
function getEndIndexOfKanjiAt(str, atIndex) {
    if  (!isKanji(str[atIndex])) {
        return -1;
    }
    while (atIndex < str.length) {
        if (!isKanji(str, atIndex)) {
            return atIndex - 1;
        }
        atIndex++;
    }
    return str.length - 1;
}

function getIndexOfNextOpeningBrace(str, atIndex) {
    return str.indexOf("[", atIndex);
}
function getIndexOfNextClosingBrace(str, atIndex) {
    return str.indexOf("]", atIndex);
}

function getIndexOfNextWord(str, atIndex, wordDelimiter) {
    str.indexOf(wordDelimiter, atIndex);
}

function parseFuriUsingSquareBrackets(str, wordDelimiter) {
    var elements = $('

');
    var searchText = "";
    var index = 0;
    var endIndex = str.length;
    while (index < endIndex) {
        var bracesStart = getIndexOfNextOpeningBrace(str, index);
        var bracesEnd = getIndexOfNextClosingBrace(str, bracesStart);
        var kanjiStart = getStartIndexOfKanjiAt(str, bracesStart);
        var kanjiEnd = bracesStart - 1;
        searchText += str.substring(index, kanjiEnd);
        index = bracesEnd + 1;
        var kanjiElement = $("" + str.substring(kanjiStart, kanjiEnd) + "");
        var furiElement = $("" + str.substring(kanjiStart, kanjiEnd) + "");
        var element = $("

");
        element.append(kanjiElement).append(furiElement);
        elements.append(element);
    }
    elements.attr("search", searchText);
    return elements;
}

$(".parse-furi").each(
    function(e) {
        e = $(e);
        e.html(parseFuriUsingSquareBrackets(e.text()));
    });

$(".rtk-link").each(
    function(e) {
        e = $(e);
        var rtkLink = "http://kanji.koohi.com/study/kanji/" + e.attr("kanji");
        e.wrap("");
    });

$(".jisho-link").each(
		function(e) {
		    e = $(e);
		    var jishoLink = "http://jisho.org/search/" + e.attr("search");
		    e.wrap("");
		});

function cycleFont()
{
		var f = $(this);
		var prevfont = Number(f.attr("font_index")) % fontlist.length;
		var curfont = (Number(f.attr("font_index")) + 1) % fontlist.length;
		f.attr("font_index", curfont);
		f.removeClass(fontlist[prevfont]);
		f.addClass(fontlist[curfont]);
}

$(".cycle-font").each(
		function(e)
		{
				e = $(e);
				var fontlist = e.attr("font_classes");
				if (fontlist != undefined) {
						fontlist = fontlist.split(",");
				} else {
						return;
				}
				e.attr("curfont", 0);
        cycleFont(e);
				e.onclick(cycleFont);
		} );
<commit_msg>Cleanup of variable names and parameter usage<commit_after>function isPunctuation(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION);
}
function isHiragana(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.HIRAGANA);
}

function isKatakana(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.KATAKANA);
}
function isKanji(str, atIndex) {
    var c = str[atIndex];
    return (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) ||
           (Character.UnicodeBlock.of(c)==Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
}

function getStartIndexOfKanjiAt(str, atIndex) {
    if  (!isKanji(str[atIndex])) {
        return -1;
    }
    while (atIndex > 0) {
        if (!isKanji(str, atIndex)) {
            return atIndex + 1;
        }
        atIndex--;
    }
    return 0;
}
function getEndIndexOfKanjiAt(str, atIndex) {
    if  (!isKanji(str[atIndex])) {
        return -1;
    }
    while (atIndex < str.length) {
        if (!isKanji(str, atIndex)) {
            return atIndex - 1;
        }
        atIndex++;
    }
    return str.length - 1;
}

function getIndexOfNextOpeningBrace(str, atIndex) {
    return str.indexOf("[", atIndex);
}
function getIndexOfNextClosingBrace(str, atIndex) {
    return str.indexOf("]", atIndex);
}

function getIndexOfNextWord(str, atIndex, wordDelimiter) {
    str.indexOf(wordDelimiter, atIndex);
}

function parseFuriUsingSquareBrackets(str, wordDelimiter) {
    var elements = $('

');
    var searchText = "";
    var index = 0;
    var endIndex = str.length;
    while (index < endIndex) {
        var bracesStart = getIndexOfNextOpeningBrace(str, index);
        var bracesEnd = getIndexOfNextClosingBrace(str, bracesStart);
        var kanjiStart = getStartIndexOfKanjiAt(str, bracesStart);
        var kanjiEnd = bracesStart - 1;
        searchText += str.substring(index, kanjiEnd);
        index = bracesEnd + 1;
        var kanjiElement = $("" + str.substring(kanjiStart, kanjiEnd) + "");
        var furiElement = $("" + str.substring(kanjiStart, kanjiEnd) + "");
        var element = $("

");
        element.append(kanjiElement).append(furiElement);
        elements.append(element);
    }
    elements.attr("search", searchText);
    return elements;
}

$(".parse-furi").each(
    function() {
        $(this).html(parseFuriUsingSquareBrackets(e.text()));
    });

$(".rtk-link").each(
    function() {
        var e = $(this);
        var rtkLink = "http://kanji.koohi.com/study/kanji/" + e.attr("kanji");
        e.wrap("");
    });

$(".jisho-link").each(
		function() {
		    var e = $(this);
		    var jishoLink = "http://jisho.org/search/" + e.attr("search");
		    e.wrap("");
		});

function cycleFont()
{
		var e = $(this);
		var prevfont = Number(e.attr("font_index")) % fontlist.length;
		var curfont = (Number(e.attr("font_index")) + 1) % fontlist.length;
		e.attr("font_index", curfont);
		e.removeClass(fontlist[prevfont]);
		e.addClass(fontlist[curfont]);
}

$(".cycle-font").each(
		function()
		{
				var e = $(this);
				var fontlist = e.attr("font_classes");
				if (fontlist != undefined) {
						fontlist = fontlist.split(",");
				} else {
						return;
				}
				e.attr("curfont", 0);
        cycleFont(e);
				e.onclick(cycleFont);
		} );
<|endoftext|>