<commit_before>(function() {
    'use strict';
    const DATETIME_FORMAT_OPTIONS = {
        month: 'short',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
    };

    const $$ = function(id) { return document.getElementById(id) };

    const SIGN_IN_CHANNEL = Symbol.for('sign-in');
    const START_CHANNEL = Symbol.for('start');
    const NEW_RECORD_CHANNEL = Symbol.for('new-record');
    const SHOW_WIDGET_CHANNEL = Symbol.for('show-widget');
    const HIDE_WIDGET_CHANNEL = Symbol.for('hide-widget');

    class Bus {
        constructor() {
            this.callbacks = {};
        }

        subscribe(channelName, callback) {
            const callbacks = this.callbacks[channelName] || [];
            callbacks.push(callback);
            this.callbacks[channelName] = callbacks;
        }

        notify(channelName, payload={}) {
            console.log('notify: ' + channelName.toString());
            this.callbacks[channelName].forEach(function(callback) {
                callback(payload);
            });
        }
    }

    class Widget {
        constructor() {
            this.dom = {};
        }

        toggle(visible) {
            if (visible) {
                this.container.removeAttribute('hidden');
            } else {
                this.container.setAttribute('hidden', true);
            }
        }
    }

    class SignInForm {
        constructor() {
            this.callbacks = [];
            this.dom = this.cacheDom();
            this.bind();
        }

        cacheDom() {
            const dom = {};

            dom.$modal = $('#signInModal');

            dom.username = $$('username');
            dom.password = $$('password');

            dom.errorMessages = {};
            dom.errorMessages.username = $$('usernameError');
            dom.errorMessages.password = $$('passwordError');
            dom.errorMessages.non_field_errors = $$('nonFieldErrors');

            return dom;
        }

        toggle(visible) {
            if (visible) {
                // hide errors
                $('.sign-in-error').text('');
                $$('nonFieldErrors').setAttribute('hidden', true);

                // show popup
                this.dom.$modal.modal('show');
                this.dom.username.focus();
            } else {
                this.dom.$modal.modal('hide');
            }
        }

        showErrors(response) {
            const errorMessages = this.dom.errorMessages;

            Object.keys(errorMessages).forEach(function(fieldName) {
                const element = errorMessages[fieldName];
                const errors = response[fieldName];

                if (errors) {
                    element.textContent = errors.join(';');
                    element.removeAttribute('hidden');
                } else {
                    element.textContent = '';
                    element.setAttribute('hidden', true);
                }
            })
        }

        bind() {
            $('#butSubmitSignIn').on('click', async function() {
                APP.showSpinner();
                const tokenResponse = await fetch('/auth/jwt/create/', {
                    method: 'POST',
                    body: JSON.stringify({
                        username: $$('username').value,
                        password: $$('password').value,
                    }),
                    headers: {
                        'User-Agent': 'Home Budget PWA',
                        'Content-Type': 'application/json',
                    },
                });

                if (!tokenResponse.ok) {
                    const responseBody = await tokenResponse.json();
                    this.showErrors(responseBody);
                    return;
                }

                const tokenData = await tokenResponse.json();
                Auth.token = tokenData.token;

                const success = await Auth.fetchProfile();
                if (!success) {
                    this.showErrors({ non_field_errors: ['Failed to load profile.'] });
                    return;
                }

                this.toggle(false);
                APP.hideSpinner();
                BUS.notify(SIGN_IN_CHANNEL);
            }.bind(this));

            $('#butCancelSignIn').on('click', function() {
                this.toggle(false);
            }.bind(this));
        }
    }

    class Auth {
        constructor(form) {
            this.form = form;
            this.bind();
            Auth.instance = this;
        }

        bind() {
            BUS.subscribe(START_CHANNEL, function() {
                if (!this.isSignedIn) {
                    this.form.toggle(true);
                }
            }.bind(this));
        }

        get isSignedIn() {
            return Auth.token && Auth.profile && Auth.profile.tags
        }

        static async fetchProfile() {
            const tokenPayload = this.parsedToken;

            const profileResponse = await this.fetch(`/api/user/${tokenPayload.user_id}/`);

            if (!profileResponse.ok) {
                return false;
            }

            this.profile = await profileResponse.json();
            return true;
        }

        static get parsedToken() {
            const base64Url = this.token.split('.')[1];
            const base64 = base64Url.replace('-', '+').replace('_', '/');
            return JSON.parse(window.atob(base64));
        }

        static set token(token) {
            localStorage.setItem(this.TOKEN_KEY, token);
        }

        static get token() {
            return localStorage.getItem(this.TOKEN_KEY);
        }

        static set profile(profile) {
            localStorage.setItem(this.PROFILE_KEY, JSON.stringify(profile));
        }

        static get profile() {
            const json = localStorage.getItem(this.PROFILE_KEY);
            return JSON.parse(json);
        }

        static get TOKEN_KEY() {
            return 'AUTH_TOKEN'
        }

        static get PROFILE_KEY() {
            return 'PROFILE'
        }

        static async refresh() {
            const response = await fetch('/auth/jwt/refresh/', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ token: this.token }),
            });

            if (!response.ok && 400 === response.status) {
                this.token = null;
                this.profile = null;
                this.instance.form.toggle(true);
            } else {
                debugger
            }
        }

        static async fetch(url, options={}) {
            APP.showSpinner();

            const token = localStorage.getItem(Auth.TOKEN_KEY);

            if (!options.headers) { options.headers = {}; }
            options.headers.Authorization = `JWT ${token}`;
            options.headers['User-Agent'] = 'Home Budget PWA';
            options.headers['Content-Type'] = 'application/json';

            const result = await fetch(url, options);

            if (!result.ok && result.status === 401) {
                await this.refresh();
                if (this.instance.isSignedIn) {
                    return this.fetch(url, options);
                } else {
                    this.instance.form.toggle(true);
                }
            }

            APP.hideSpinner();

            return result
        }
    }

    class IndexPage extends Widget {
        constructor() {
            super();
            this.currentPageNumberLabel = $$('currentPageNumberLabel');
            this.initCurrentPage();
            this.template = document.querySelector('.record-item.cardTemplate');
            this.container = document.querySelector('.records-list');
            this.cards = $$('cards');
            this.bind();
        }

        initCurrentPage() {
            if (Number.isNaN(this.currentPage)) {
                this.currentPage = 1;
            }
            this.updateCurrentPageLabel();
        }

        get currentPage() {
            return parseInt(localStorage.getItem('CURRENT_PAGE'), 10);
        }

        set currentPage(n) {
            localStorage.setItem('CURRENT_PAGE', n);
        }

        async show() {
            const records = await this.getPage();

            if (undefined === records.results) {
                return false;
            } else {
                this.cards.innerHTML = '';
            }

            records.results.forEach(function(record) {
                this.drawCard(record);
            }.bind(this));

            return true;
        }

        async getPage() {
            const url = `/api/records/record-detail/?page=${this.currentPage}`;
            const response = await Auth.fetch(url);
            return response.json();
        }

        bind() {
            $('#nextRecordsPageLink').on('click', async function(e) {
                e.preventDefault();
                this.currentPage++;
                const success = await this.show();
                if (!success) { this.currentPage--; }
                this.updateCurrentPageLabel();
            }.bind(this));

            $('#prevRecordsPageLink').on('click', async function(e) {
                e.preventDefault();

                if (1 === this.currentPage) { return; }

                this.currentPage--;
                const success = await this.show();
                if (!success) { this.currentPage++; }
                this.updateCurrentPageLabel();
            }.bind(this));

            BUS.subscribe(SIGN_IN_CHANNEL, function() {
                this.show();
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function(widget) {
                if (Auth.instance.isSignedIn) { this.show(); }
            }.bind(this));

            BUS.subscribe(NEW_RECORD_CHANNEL, function(record) {
                this.drawCard(record, false);
                this.cards.lastElementChild.remove();
            }.bind(this));
        }

        updateCurrentPageLabel() {
            this.currentPageNumberLabel.innerText = this.currentPage;
        }

        drawCard(record, append=true) {
            const card = this.template.cloneNode(true);
            card.classList.remove('cardTemplate');

            // card look
            const suffix = record.transaction_type == 'EXP' ? 'warning' : 'success';
            const extraClass = `bd-callout-${suffix}`;
            card.classList.add(extraClass);

            // amount
            const amount = Number.parseFloat(record.amount.amount).toFixed(2);
            card.querySelector('.amount').textContent = amount;

            // date
            const offset = new Date().getTimezoneOffset() * 60 * 1000;
            const date = new Date(record.created_at * 1000 - offset);
            const dateString = date.toLocaleString('en', DATETIME_FORMAT_OPTIONS);
            card.querySelector('.date').textContent = dateString;

            // tags
            const tagsString = Object.values(record.tags).join(', ');
            card.querySelector('.tags').textContent = tagsString;

            card.removeAttribute('hidden');
            if (append) {
                this.cards.appendChild(card);
            } else {
                this.cards.prepend(card);
            }
        }
    }

    class NewRecordForm extends Widget {
        constructor() {
            super();
            this.container = $$('newRecordForm');

            this.dom.amountField = $$('newRecordAmount');

            this.template = document.querySelector('.tag-template');
            this.tagsContainer = $$('tagsContainer');
            this.bind();
        }

        bind() {
            $(this.tagsContainer).on('click', 'div.btn', function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                // ignore clicks on label
                if (undefined === e.target.value) { return; }
                $(e.currentTarget).toggleClass('btn-outline-info btn-outline-danger');
            });

            $('#butCalculateResult').click(function() {
                const input = this.dom.amountField;
                let result;

                try {
                    result = Number.parseFloat(eval(input.value));
                } catch (e) {
                    input.value = '';
                }

                if (Number.isFinite(result)) {
                    input.value = result.toFixed(2);
                }

                input.focus();
            });

            $('#newRecordSubmit').on('click', async function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                await this.addNewRecord();
                this.reset();
                BUS.notify(HIDE_WIDGET_CHANNEL);
            }.bind(this));

            $('#newRecordAddAnother').on('click', async function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                await this.addNewRecord();
                this.reset();
                this.dom.amountField.focus();
            }.bind(this));

            BUS.subscribe(SIGN_IN_CHANNEL, function() {
                this.setup();
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function() {
                if (Auth.instance.isSignedIn) {
                    this.setup();
                }
            }.bind(this));
        }

        reset() {
            this.dom.amountField.value = '';

            $('#tagsContainer .btn')
                .addClass('btn-outline-info')
                .removeClass('btn-outline-danger')
            ;

            $('#tagsContainer input').map(function(i, input) {
                input.checked = false
            });
        }

        async addNewRecord() {
            APP.showSpinner();

            const data = {
                amount: {
                    amount: this.dom.amountField.value,
                    currency: 'CAD',
                },
                tags: {},
                transaction_type: $('#newRecordTransactionType').val(),
            };

            $('#tagsContainer input:checked').each(function(i, input) {
                const description = input.labels[0].textContent.trim();
                data.tags[input.value] = description;
            });

            const res = await Auth.fetch('/api/records/record-detail/', {
                method: 'POST',
                body: JSON.stringify(data),
            });

            if (res.ok) {
                const record = await res.json();
                BUS.notify(NEW_RECORD_CHANNEL, record);
            }

            APP.hideSpinner();
        }

        toggle(visible) {
            super.toggle(visible);
            if (visible) { this.dom.amountField.focus(); }
        }

        setup() {
            const tagsContainer = this.tagsContainer;
            const tags = Auth.profile.tags;
            const template = this.template;

            Object.keys(tags).forEach(function(id) {
                const name = tags[id];
                const domId = `id_tags_${id}`;

                const tag = template.cloneNode(true);
                tag.classList.remove('tag-template');

                const label = tag.querySelector('label');
                label.setAttribute('for', domId);
                const text = document.createTextNode(name);
                label.appendChild(text);

                const input = tag.querySelector('input');
                input.setAttribute('id', domId);
                input.value = id;

                tag.removeAttribute('hidden');

                tagsContainer.appendChild(tag);
            });
        }
    }

    class BudgetsPage extends Widget {
        constructor() {
            super();
            this.container = $$('budgets');
        }
    }

    class App {
        constructor() {
            this.spinner = document.querySelector('.loader');

            // XXX: first widget added will be visible by default
            this.addFullScreenWidget(IndexPage);
            this.addFullScreenWidget(BudgetsPage);
            this.addFullScreenWidget(NewRecordForm);

            this.signInForm = new SignInForm();
            this.auth = new Auth(this.signInForm);

            this.bind();
        }

        showSpinner() {
            this.spinner.removeAttribute('hidden');
        }

        hideSpinner() {
            this.spinner.setAttribute('hidden', true);
        }

        addFullScreenWidget(constructor) {
            const widget = new constructor();

            if (!this.fullScreenWidgets) {
                this.fullScreenWidgets = {};
                this.firstWidget = widget;
            }

            this.fullScreenWidgets[constructor.name] = widget;
        }

        showWidget(widgetToShow) {
            this.previouslyVisibleWidget = this.currentlyVisibleWidget;
            this.currentlyVisibleWidget = widgetToShow;

            Object.keys(this.fullScreenWidgets).forEach(function(constructorName) {
                const widget = this.fullScreenWidgets[constructorName];
                widget.toggle(widget === widgetToShow);
            }.bind(this));
        }

        bind() {
            $('a.nav-link').on('click', function(e) {
                const targetName = e.currentTarget.dataset.target;
                const widget = this.fullScreenWidgets[targetName];
                if (widget) {
                    BUS.notify(SHOW_WIDGET_CHANNEL, widget);
                }
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function(widget) {
                this.showWidget(this.firstWidget);
            }.bind(this));

            BUS.subscribe(SHOW_WIDGET_CHANNEL, function(widget) {
                this.showWidget(widget);
            }.bind(this));

            BUS.subscribe(HIDE_WIDGET_CHANNEL, function() {
                if (!this.previouslyVisibleWidget) {
                    this.previouslyVisibleWidget = this.firstWidget;
                }

                this.showWidget(this.previouslyVisibleWidget);
            }.bind(this));
        }

        run() {
            BUS.notify(START_CHANNEL);
        }
    }

    /* PAGE INITIALIZATION */

    const BUS = new Bus();
    const APP = new App();
    APP.run();

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker
            .register('./service-worker.js')
            .then(function() {
                console.log('Service Worker Registered');
            });
    }
})();
<commit_msg>fix calc<commit_after>(function() {
    'use strict';
    const DATETIME_FORMAT_OPTIONS = {
        month: 'short',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false,
    };

    const $$ = function(id) { return document.getElementById(id) };

    const SIGN_IN_CHANNEL = Symbol.for('sign-in');
    const START_CHANNEL = Symbol.for('start');
    const NEW_RECORD_CHANNEL = Symbol.for('new-record');
    const SHOW_WIDGET_CHANNEL = Symbol.for('show-widget');
    const HIDE_WIDGET_CHANNEL = Symbol.for('hide-widget');

    class Bus {
        constructor() {
            this.callbacks = {};
        }

        subscribe(channelName, callback) {
            const callbacks = this.callbacks[channelName] || [];
            callbacks.push(callback);
            this.callbacks[channelName] = callbacks;
        }

        notify(channelName, payload={}) {
            console.log('notify: ' + channelName.toString());
            this.callbacks[channelName].forEach(function(callback) {
                callback(payload);
            });
        }
    }

    class Widget {
        constructor() {
            this.dom = {};
        }

        toggle(visible) {
            if (visible) {
                this.container.removeAttribute('hidden');
            } else {
                this.container.setAttribute('hidden', true);
            }
        }
    }

    class SignInForm {
        constructor() {
            this.callbacks = [];
            this.dom = this.cacheDom();
            this.bind();
        }

        cacheDom() {
            const dom = {};

            dom.$modal = $('#signInModal');

            dom.username = $$('username');
            dom.password = $$('password');

            dom.errorMessages = {};
            dom.errorMessages.username = $$('usernameError');
            dom.errorMessages.password = $$('passwordError');
            dom.errorMessages.non_field_errors = $$('nonFieldErrors');

            return dom;
        }

        toggle(visible) {
            if (visible) {
                // hide errors
                $('.sign-in-error').text('');
                $$('nonFieldErrors').setAttribute('hidden', true);

                // show popup
                this.dom.$modal.modal('show');
                this.dom.username.focus();
            } else {
                this.dom.$modal.modal('hide');
            }
        }

        showErrors(response) {
            const errorMessages = this.dom.errorMessages;

            Object.keys(errorMessages).forEach(function(fieldName) {
                const element = errorMessages[fieldName];
                const errors = response[fieldName];

                if (errors) {
                    element.textContent = errors.join(';');
                    element.removeAttribute('hidden');
                } else {
                    element.textContent = '';
                    element.setAttribute('hidden', true);
                }
            })
        }

        bind() {
            $('#butSubmitSignIn').on('click', async function() {
                APP.showSpinner();
                const tokenResponse = await fetch('/auth/jwt/create/', {
                    method: 'POST',
                    body: JSON.stringify({
                        username: $$('username').value,
                        password: $$('password').value,
                    }),
                    headers: {
                        'User-Agent': 'Home Budget PWA',
                        'Content-Type': 'application/json',
                    },
                });

                if (!tokenResponse.ok) {
                    const responseBody = await tokenResponse.json();
                    this.showErrors(responseBody);
                    return;
                }

                const tokenData = await tokenResponse.json();
                Auth.token = tokenData.token;

                const success = await Auth.fetchProfile();
                if (!success) {
                    this.showErrors({ non_field_errors: ['Failed to load profile.'] });
                    return;
                }

                this.toggle(false);
                APP.hideSpinner();
                BUS.notify(SIGN_IN_CHANNEL);
            }.bind(this));

            $('#butCancelSignIn').on('click', function() {
                this.toggle(false);
            }.bind(this));
        }
    }

    class Auth {
        constructor(form) {
            this.form = form;
            this.bind();
            Auth.instance = this;
        }

        bind() {
            BUS.subscribe(START_CHANNEL, function() {
                if (!this.isSignedIn) {
                    this.form.toggle(true);
                }
            }.bind(this));
        }

        get isSignedIn() {
            return Auth.token && Auth.profile && Auth.profile.tags
        }

        static async fetchProfile() {
            const tokenPayload = this.parsedToken;

            const profileResponse = await this.fetch(`/api/user/${tokenPayload.user_id}/`);

            if (!profileResponse.ok) {
                return false;
            }

            this.profile = await profileResponse.json();
            return true;
        }

        static get parsedToken() {
            const base64Url = this.token.split('.')[1];
            const base64 = base64Url.replace('-', '+').replace('_', '/');
            return JSON.parse(window.atob(base64));
        }

        static set token(token) {
            localStorage.setItem(this.TOKEN_KEY, token);
        }

        static get token() {
            return localStorage.getItem(this.TOKEN_KEY);
        }

        static set profile(profile) {
            localStorage.setItem(this.PROFILE_KEY, JSON.stringify(profile));
        }

        static get profile() {
            const json = localStorage.getItem(this.PROFILE_KEY);
            return JSON.parse(json);
        }

        static get TOKEN_KEY() {
            return 'AUTH_TOKEN'
        }

        static get PROFILE_KEY() {
            return 'PROFILE'
        }

        static async refresh() {
            const response = await fetch('/auth/jwt/refresh/', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ token: this.token }),
            });

            if (!response.ok && 400 === response.status) {
                this.token = null;
                this.profile = null;
                this.instance.form.toggle(true);
            } else {
                debugger
            }
        }

        static async fetch(url, options={}) {
            APP.showSpinner();

            const token = localStorage.getItem(Auth.TOKEN_KEY);

            if (!options.headers) { options.headers = {}; }
            options.headers.Authorization = `JWT ${token}`;
            options.headers['User-Agent'] = 'Home Budget PWA';
            options.headers['Content-Type'] = 'application/json';

            const result = await fetch(url, options);

            if (!result.ok && result.status === 401) {
                await this.refresh();
                if (this.instance.isSignedIn) {
                    return this.fetch(url, options);
                } else {
                    this.instance.form.toggle(true);
                }
            }

            APP.hideSpinner();

            return result
        }
    }

    class IndexPage extends Widget {
        constructor() {
            super();
            this.currentPageNumberLabel = $$('currentPageNumberLabel');
            this.initCurrentPage();
            this.template = document.querySelector('.record-item.cardTemplate');
            this.container = document.querySelector('.records-list');
            this.cards = $$('cards');
            this.bind();
        }

        initCurrentPage() {
            if (Number.isNaN(this.currentPage)) {
                this.currentPage = 1;
            }
            this.updateCurrentPageLabel();
        }

        get currentPage() {
            return parseInt(localStorage.getItem('CURRENT_PAGE'), 10);
        }

        set currentPage(n) {
            localStorage.setItem('CURRENT_PAGE', n);
        }

        async show() {
            const records = await this.getPage();

            if (undefined === records.results) {
                return false;
            } else {
                this.cards.innerHTML = '';
            }

            records.results.forEach(function(record) {
                this.drawCard(record);
            }.bind(this));

            return true;
        }

        async getPage() {
            const url = `/api/records/record-detail/?page=${this.currentPage}`;
            const response = await Auth.fetch(url);
            return response.json();
        }

        bind() {
            $('#nextRecordsPageLink').on('click', async function(e) {
                e.preventDefault();
                this.currentPage++;
                const success = await this.show();
                if (!success) { this.currentPage--; }
                this.updateCurrentPageLabel();
            }.bind(this));

            $('#prevRecordsPageLink').on('click', async function(e) {
                e.preventDefault();

                if (1 === this.currentPage) { return; }

                this.currentPage--;
                const success = await this.show();
                if (!success) { this.currentPage++; }
                this.updateCurrentPageLabel();
            }.bind(this));

            BUS.subscribe(SIGN_IN_CHANNEL, function() {
                this.show();
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function(widget) {
                if (Auth.instance.isSignedIn) { this.show(); }
            }.bind(this));

            BUS.subscribe(NEW_RECORD_CHANNEL, function(record) {
                this.drawCard(record, false);
                this.cards.lastElementChild.remove();
            }.bind(this));
        }

        updateCurrentPageLabel() {
            this.currentPageNumberLabel.innerText = this.currentPage;
        }

        drawCard(record, append=true) {
            const card = this.template.cloneNode(true);
            card.classList.remove('cardTemplate');

            // card look
            const suffix = record.transaction_type == 'EXP' ? 'warning' : 'success';
            const extraClass = `bd-callout-${suffix}`;
            card.classList.add(extraClass);

            // amount
            const amount = Number.parseFloat(record.amount.amount).toFixed(2);
            card.querySelector('.amount').textContent = amount;

            // date
            const offset = new Date().getTimezoneOffset() * 60 * 1000;
            const date = new Date(record.created_at * 1000 - offset);
            const dateString = date.toLocaleString('en', DATETIME_FORMAT_OPTIONS);
            card.querySelector('.date').textContent = dateString;

            // tags
            const tagsString = Object.values(record.tags).join(', ');
            card.querySelector('.tags').textContent = tagsString;

            card.removeAttribute('hidden');
            if (append) {
                this.cards.appendChild(card);
            } else {
                this.cards.prepend(card);
            }
        }
    }

    class NewRecordForm extends Widget {
        constructor() {
            super();
            this.container = $$('newRecordForm');

            this.dom.amountField = $$('newRecordAmount');

            this.template = document.querySelector('.tag-template');
            this.tagsContainer = $$('tagsContainer');
            this.bind();
        }

        bind() {
            $(this.tagsContainer).on('click', 'div.btn', function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                // ignore clicks on label
                if (undefined === e.target.value) { return; }
                $(e.currentTarget).toggleClass('btn-outline-info btn-outline-danger');
            });

            $('#butCalculateResult').click(function() {
                const input = this.dom.amountField;
                let result;

                try {
                    result = Number.parseFloat(eval(input.value));
                } catch (e) {
                    input.value = '';
                }

                if (Number.isFinite(result)) {
                    input.value = result.toFixed(2);
                }

                input.focus();
            }.bind(this));

            $('#newRecordSubmit').on('click', async function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                await this.addNewRecord();
                this.reset();
                BUS.notify(HIDE_WIDGET_CHANNEL);
            }.bind(this));

            $('#newRecordAddAnother').on('click', async function(e) {
                e.stopImmediatePropagation();
                e.stopPropagation();

                await this.addNewRecord();
                this.reset();
                this.dom.amountField.focus();
            }.bind(this));

            BUS.subscribe(SIGN_IN_CHANNEL, function() {
                this.setup();
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function() {
                if (Auth.instance.isSignedIn) {
                    this.setup();
                }
            }.bind(this));
        }

        reset() {
            this.dom.amountField.value = '';

            $('#tagsContainer .btn')
                .addClass('btn-outline-info')
                .removeClass('btn-outline-danger')
            ;

            $('#tagsContainer input').map(function(i, input) {
                input.checked = false
            });
        }

        async addNewRecord() {
            APP.showSpinner();

            const data = {
                amount: {
                    amount: this.dom.amountField.value,
                    currency: 'CAD',
                },
                tags: {},
                transaction_type: $('#newRecordTransactionType').val(),
            };

            $('#tagsContainer input:checked').each(function(i, input) {
                const description = input.labels[0].textContent.trim();
                data.tags[input.value] = description;
            });

            const res = await Auth.fetch('/api/records/record-detail/', {
                method: 'POST',
                body: JSON.stringify(data),
            });

            if (res.ok) {
                const record = await res.json();
                BUS.notify(NEW_RECORD_CHANNEL, record);
            }

            APP.hideSpinner();
        }

        toggle(visible) {
            super.toggle(visible);
            if (visible) { this.dom.amountField.focus(); }
        }

        setup() {
            const tagsContainer = this.tagsContainer;
            const tags = Auth.profile.tags;
            const template = this.template;

            Object.keys(tags).forEach(function(id) {
                const name = tags[id];
                const domId = `id_tags_${id}`;

                const tag = template.cloneNode(true);
                tag.classList.remove('tag-template');

                const label = tag.querySelector('label');
                label.setAttribute('for', domId);
                const text = document.createTextNode(name);
                label.appendChild(text);

                const input = tag.querySelector('input');
                input.setAttribute('id', domId);
                input.value = id;

                tag.removeAttribute('hidden');

                tagsContainer.appendChild(tag);
            });
        }
    }

    class BudgetsPage extends Widget {
        constructor() {
            super();
            this.container = $$('budgets');
        }
    }

    class App {
        constructor() {
            this.spinner = document.querySelector('.loader');

            // XXX: first widget added will be visible by default
            this.addFullScreenWidget(IndexPage);
            this.addFullScreenWidget(BudgetsPage);
            this.addFullScreenWidget(NewRecordForm);

            this.signInForm = new SignInForm();
            this.auth = new Auth(this.signInForm);

            this.bind();
        }

        showSpinner() {
            this.spinner.removeAttribute('hidden');
        }

        hideSpinner() {
            this.spinner.setAttribute('hidden', true);
        }

        addFullScreenWidget(constructor) {
            const widget = new constructor();

            if (!this.fullScreenWidgets) {
                this.fullScreenWidgets = {};
                this.firstWidget = widget;
            }

            this.fullScreenWidgets[constructor.name] = widget;
        }

        showWidget(widgetToShow) {
            this.previouslyVisibleWidget = this.currentlyVisibleWidget;
            this.currentlyVisibleWidget = widgetToShow;

            Object.keys(this.fullScreenWidgets).forEach(function(constructorName) {
                const widget = this.fullScreenWidgets[constructorName];
                widget.toggle(widget === widgetToShow);
            }.bind(this));
        }

        bind() {
            $('a.nav-link').on('click', function(e) {
                const targetName = e.currentTarget.dataset.target;
                const widget = this.fullScreenWidgets[targetName];
                if (widget) {
                    BUS.notify(SHOW_WIDGET_CHANNEL, widget);
                }
            }.bind(this));

            BUS.subscribe(START_CHANNEL, function(widget) {
                this.showWidget(this.firstWidget);
            }.bind(this));

            BUS.subscribe(SHOW_WIDGET_CHANNEL, function(widget) {
                this.showWidget(widget);
            }.bind(this));

            BUS.subscribe(HIDE_WIDGET_CHANNEL, function() {
                if (!this.previouslyVisibleWidget) {
                    this.previouslyVisibleWidget = this.firstWidget;
                }

                this.showWidget(this.previouslyVisibleWidget);
            }.bind(this));
        }

        run() {
            BUS.notify(START_CHANNEL);
        }
    }

    /* PAGE INITIALIZATION */

    const BUS = new Bus();
    const APP = new App();
    APP.run();

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker
            .register('./service-worker.js')
            .then(function() {
                console.log('Service Worker Registered');
            });
    }
})();
<|endoftext|>


<commit_before>// import Nevow.Athena

/**
 * Add a class to an element's "className" attribute.
 *
 * This operation is intended to preserve any other values that were already
 * present.
 */
Methanal.Util.addElementClass = function addElementClass(node, cls) {
    var current = node.className;

    // trivial case, no className yet
    if (current == undefined || current.length === 0) {
        node.className = cls;
        return;
    }

    // the other trivial case, already set as the only class
    if (current == cls) {
        return;
    }

    var classes = current.split(' ');
    for (var i = 0; i < classes.length; ++i) {
        if (classes[i] === cls) {
            return;
        }
    }
    node.className += ' ' + cls;
};

/**
 * Remove a class from an element's "className" attribute.
 *
 * This operation is intended to preserve any other values that were already
 * present.
 */
Methanal.Util.removeElementClass = function removeElementClass(node, cls) {
    var current = node.className;

    // trivial case, no className yet
    if (current == undefined || current.length === 0) {
        return;
    }

    // other trivial case, set only to className
    if (current == cls) {
        node.className = "";
        return;
    }

    // non-trivial case
    var classes = current.split(' ');
    for (var i = 0; i < classes.length; ++i) {
        if (classes[i] === cls) {
            classes.splice(i, 1);
            node.className = classes.join(' ');
            return;
        }
    }
};

/**
 * Remove all the children of a node.
 */
Methanal.Util.removeNodeContent = function removeNodeContent(node) {
    while (node.childNodes.length)
        node.removeChild(node.firstChild);
};

/**
 * Replace all of a node's children with new ones.
 *
 * @type children: C{Array}
 */
Methanal.Util.replaceNodeContent = function replaceNodeContent(node, children) {
    Methanal.Util.removeNodeContent(node);
    for (var i = 0; i < children.length; ++i)
        node.appendChild(children[i]);
};

/**
 * Replace a node's text node with new text.
 */
Methanal.Util.replaceNodeText = function replaceNodeText(node, text) {
    Methanal.Util.replaceNodeContent(node, [node.ownerDocument.createTextNode(text)]);
};

// XXX: what does this do that's special again?
// XXX: i think maybe it exposes more information when called by IE
Methanal.Util.formatFailure = function formatFailure(failure) {
    var text = failure.error.description;
    if (!text)
        text = failure.toString();
    return text;
};

/**
 * Convert a string to a base-10 integer.
 *
 * Not quite as simple to do properly as one might think.
 */
Methanal.Util.strToInt = function strToInt(s) {
    if (typeof s !== 'string')
        return undefined;

    if (s.indexOf('x') !== -1)
        return undefined;

    if (isNaN(s))
        return undefined;

    return parseInt(s, 10);
};

/**
 * Pretty print a decimal number.
 *
 * Useful for formatting large currency amounts in a human-readable way.
 */
Methanal.Util.formatDecimal = function formatDecimal(value) {
    var value = value.toString();
    var l = value.split('')
    var pointIndex = value.indexOf('.');
    pointIndex = pointIndex > -1 ? pointIndex : value.length;

    for (var i = pointIndex - 3; i >= 1; i -= 3) {
        l.splice(i, 0, ',');
    }

    return l.join('');
};

/**
 * Create a callable that cycles through the initial inputs when called.
 */
Methanal.Util.cycle = function cycle(/*...*/) {
    var i = -1;
    var args = arguments;
    var n = args.length;
    return function () {
        i = ++i % n;
        return args[i];
    };
}

/**
 * Find the index of C{v} in the array C{a}.
 *
 * @return: Index of C{v} in C{a} or C{-1} if not found.
 */
Methanal.Util.arrayIndexOf = function arrayIndexOf(a, v) {
    for (var i = 0; i < a.length; ++i)
        if (a[i] === v)
            return i;
    return -1;
};

Methanal.Util.detachWidget = function detachWidget(widget) {
    var children = widget.widgetParent.childWidgets;
    var index = Methanal.Util.arrayIndexOf(children, widget);
    if (index !== -1)
        children.splice(index, 1);

    delete Nevow.Athena.Widget._athenaWidgets[widget._athenaID];
};

Methanal.Util.nodeInserted = function nodeInserted(widget) {
    if (widget.nodeInserted !== undefined)
        widget.nodeInserted();

    for (var i = 0; i < widget.childWidgets.length; ++i)
        Methanal.Util.nodeInserted(widget.childWidgets[i]);
};
<commit_msg>Remove nodes backwards in removeNodeContent, as this is a little faster.<commit_after>// import Nevow.Athena

/**
 * Add a class to an element's "className" attribute.
 *
 * This operation is intended to preserve any other values that were already
 * present.
 */
Methanal.Util.addElementClass = function addElementClass(node, cls) {
    var current = node.className;

    // trivial case, no className yet
    if (current == undefined || current.length === 0) {
        node.className = cls;
        return;
    }

    // the other trivial case, already set as the only class
    if (current == cls) {
        return;
    }

    var classes = current.split(' ');
    for (var i = 0; i < classes.length; ++i) {
        if (classes[i] === cls) {
            return;
        }
    }
    node.className += ' ' + cls;
};

/**
 * Remove a class from an element's "className" attribute.
 *
 * This operation is intended to preserve any other values that were already
 * present.
 */
Methanal.Util.removeElementClass = function removeElementClass(node, cls) {
    var current = node.className;

    // trivial case, no className yet
    if (current == undefined || current.length === 0) {
        return;
    }

    // other trivial case, set only to className
    if (current == cls) {
        node.className = "";
        return;
    }

    // non-trivial case
    var classes = current.split(' ');
    for (var i = 0; i < classes.length; ++i) {
        if (classes[i] === cls) {
            classes.splice(i, 1);
            node.className = classes.join(' ');
            return;
        }
    }
};

/**
 * Remove all the children of a node.
 */
Methanal.Util.removeNodeContent = function removeNodeContent(node) {
    while (node.lastChild)
        node.removeChild(node.lastChild);
};

/**
 * Replace all of a node's children with new ones.
 *
 * @type children: C{Array}
 */
Methanal.Util.replaceNodeContent = function replaceNodeContent(node, children) {
    Methanal.Util.removeNodeContent(node);
    for (var i = 0; i < children.length; ++i)
        node.appendChild(children[i]);
};

/**
 * Replace a node's text node with new text.
 */
Methanal.Util.replaceNodeText = function replaceNodeText(node, text) {
    Methanal.Util.replaceNodeContent(node, [node.ownerDocument.createTextNode(text)]);
};

// XXX: what does this do that's special again?
// XXX: i think maybe it exposes more information when called by IE
Methanal.Util.formatFailure = function formatFailure(failure) {
    var text = failure.error.description;
    if (!text)
        text = failure.toString();
    return text;
};

/**
 * Convert a string to a base-10 integer.
 *
 * Not quite as simple to do properly as one might think.
 */
Methanal.Util.strToInt = function strToInt(s) {
    if (typeof s !== 'string')
        return undefined;

    if (s.indexOf('x') !== -1)
        return undefined;

    if (isNaN(s))
        return undefined;

    return parseInt(s, 10);
};

/**
 * Pretty print a decimal number.
 *
 * Useful for formatting large currency amounts in a human-readable way.
 */
Methanal.Util.formatDecimal = function formatDecimal(value) {
    var value = value.toString();
    var l = value.split('')
    var pointIndex = value.indexOf('.');
    pointIndex = pointIndex > -1 ? pointIndex : value.length;

    for (var i = pointIndex - 3; i >= 1; i -= 3) {
        l.splice(i, 0, ',');
    }

    return l.join('');
};

/**
 * Create a callable that cycles through the initial inputs when called.
 */
Methanal.Util.cycle = function cycle(/*...*/) {
    var i = -1;
    var args = arguments;
    var n = args.length;
    return function () {
        i = ++i % n;
        return args[i];
    };
}

/**
 * Find the index of C{v} in the array C{a}.
 *
 * @return: Index of C{v} in C{a} or C{-1} if not found.
 */
Methanal.Util.arrayIndexOf = function arrayIndexOf(a, v) {
    for (var i = 0; i < a.length; ++i)
        if (a[i] === v)
            return i;
    return -1;
};

Methanal.Util.detachWidget = function detachWidget(widget) {
    var children = widget.widgetParent.childWidgets;
    var index = Methanal.Util.arrayIndexOf(children, widget);
    if (index !== -1)
        children.splice(index, 1);

    delete Nevow.Athena.Widget._athenaWidgets[widget._athenaID];
};

Methanal.Util.nodeInserted = function nodeInserted(widget) {
    if (widget.nodeInserted !== undefined)
        widget.nodeInserted();

    for (var i = 0; i < widget.childWidgets.length; ++i)
        Methanal.Util.nodeInserted(widget.childWidgets[i]);
};
<|endoftext|>


<commit_before>function initCategoryFilter() {
  var cin = document.getElementById("category-input");
  var chi = document.getElementById("hidden-category-input");
  var options = document.getElementsByTagName("option");

  var currentCategoryId = chi.value;
  for (var i = 0; i < options.length; i++) {
    if (options[i].value === currentCategoryId) {
      cin.value = options[i].label;
      chi.value = options[i].value;
    }
  }

  function filterEmptyMatchesAll(text, input) {
    return (input === "") || Awesomplete.FILTER_CONTAINS(text, input);
  }

  var awc = new Awesomplete(cin);
  awc.minChars = 0;
  awc.maxItems = options.length;
  awc.sort = undefined;

  cin.addEventListener('click', function() {
    awc.evaluate();
  });

  cin.addEventListener("awesomplete-open", function() {
    this.classList.add("open");
  })

  cin.addEventListener("awesomplete-close", function() {
    this.classList.remove("open");
  })

  function set_hidden_category() {
    var catname = cin.value.toLocaleLowerCase();

    chi.value = "all";
    for (var i = 0; i < options.length; i++) {
      if (options[i].label.toLocaleLowerCase() == catname) {
        chi.value = options[i].value;
      }
    }
  }

  cin.addEventListener("awesomplete-selectcomplete", function() {
    set_hidden_category();
    this.form.submit();
  });

  cin.form.addEventListener("submit", function() {
    set_hidden_category();
    return true;
  });
}

var iframe = document.getElementById("categories-iframe");
iframe.addEventListener("load", function() {
  var catlist = iframe.contentDocument.getElementById("categories");
  document.body.appendChild(catlist);

  if (document.readyState !== "loading") {
    initCategoryFilter();
  } else {
    window.addEventListener("DOMContentLoaded", function() {
      initCategoryFilter();
    });
  }
});
<commit_msg>Fix previous commit keeping input invisible.<commit_after>function initCategoryFilter() {
  var cin = document.getElementById("category-input");
  var chi = document.getElementById("hidden-category-input");
  var options = document.getElementsByTagName("option");

  var currentCategoryId = chi.value;
  for (var i = 0; i < options.length; i++) {
    if (options[i].value === currentCategoryId) {
      cin.value = options[i].label;
      chi.value = options[i].value;
    }
  }

  function filterEmptyMatchesAll(text, input) {
    return (input === "") || Awesomplete.FILTER_CONTAINS(text, input);
  }

  var awc = new Awesomplete(cin);
  awc.minChars = 0;
  awc.maxItems = options.length;
  awc.sort = undefined;

  cin.addEventListener('click', function() {
    awc.evaluate();
  });

  cin.addEventListener("awesomplete-open", function() {
    this.classList.add("open");
  })

  cin.addEventListener("awesomplete-close", function() {
    this.classList.remove("open");
  })

  function set_hidden_category() {
    var catname = cin.value.toLocaleLowerCase();

    chi.value = "all";
    for (var i = 0; i < options.length; i++) {
      if (options[i].label.toLocaleLowerCase() == catname) {
        chi.value = options[i].value;
      }
    }
  }

  cin.addEventListener("awesomplete-selectcomplete", function() {
    set_hidden_category();
    this.form.submit();
  });

  cin.form.addEventListener("submit", function() {
    set_hidden_category();
    return true;
  });

  cin.style.visibility = '';
}

var iframe = document.getElementById("categories-iframe");
iframe.addEventListener("load", function() {
  var catlist = iframe.contentDocument.getElementById("categories");
  document.body.appendChild(catlist);

  if (document.readyState !== "loading") {
    initCategoryFilter();
  } else {
    window.addEventListener("DOMContentLoaded", function() {
      initCategoryFilter();
    });
  }
});
<|endoftext|>


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

'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name anova1
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/anova1}
*/
setReadOnly( ns, 'anova1', require( '@stdlib/stats/anova1' ) );

/**
* @name bartlettTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/bartlett-test}
*/
setReadOnly( ns, 'bartlettTest', require( '@stdlib/stats/bartlett-test' ) );

/**
* @name binomialTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/binomial-test}
*/
setReadOnly( ns, 'binomialTest', require( '@stdlib/stats/binomial-test' ) );

/**
* @name chi2gof
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/chi2gof}
*/
setReadOnly( ns, 'chi2gof', require( '@stdlib/stats/chi2gof' ) );

/**
* @name chi2test
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/chi2test}
*/
setReadOnly( ns, 'chi2test', require( '@stdlib/stats/chi2test' ) );

/**
* @name flignerTest
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/fligner-test}
*/
setReadOnly( ns, 'flignerTest', require( '@stdlib/stats/fligner-test' ) );

/**
* @name incr
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/incr}
*/
setReadOnly( ns, 'incr', require( '@stdlib/stats/incr' ) );

/**
* @name iterators
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/iter}
*/
setReadOnly( ns, 'iterators', require( '@stdlib/stats/iter' ) );

/**
* @name kde2d
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kde2d}
*/
setReadOnly( ns, 'kde2d', require( '@stdlib/stats/kde2d' ) );

/**
* @name kruskalTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kruskal-test}
*/
setReadOnly( ns, 'kruskalTest', require( '@stdlib/stats/kruskal-test' ) );

/**
* @name kstest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kstest}
*/
setReadOnly( ns, 'kstest', require( '@stdlib/stats/kstest' ) );

/**
* @name lowess
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/lowess}
*/
setReadOnly( ns, 'lowess', require( '@stdlib/stats/lowess' ) );

/**
* @name padjust
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/padjust}
*/
setReadOnly( ns, 'padjust', require( '@stdlib/stats/padjust' ) );

/**
* @name pcorrtest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/pcorrtest}
*/
setReadOnly( ns, 'pcorrtest', require( '@stdlib/stats/pcorrtest' ) );

/**
* @name ranks
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ranks}
*/
setReadOnly( ns, 'ranks', require( '@stdlib/stats/ranks' ) );

/**
* @name ttest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ttest}
*/
setReadOnly( ns, 'ttest', require( '@stdlib/stats/ttest' ) );

/**
* @name ttest2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ttest2}
*/
setReadOnly( ns, 'ttest2', require( '@stdlib/stats/ttest2' ) );

/**
* @name vartest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/vartest}
*/
setReadOnly( ns, 'vartest', require( '@stdlib/stats/vartest' ) );

/**
* @name wilcoxon
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/wilcoxon}
*/
setReadOnly( ns, 'wilcoxon', require( '@stdlib/stats/wilcoxon' ) );

/**
* @name ztest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ztest}
*/
setReadOnly( ns, 'ztest', require( '@stdlib/stats/ztest' ) );

/**
* @name ztest2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ztest2}
*/
setReadOnly( ns, 'ztest2', require( '@stdlib/stats/ztest2' ) );

// EXPORTS //

module.exports = ns;
<commit_msg>Add Levene's test to namespace<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name anova1
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/anova1}
*/
setReadOnly( ns, 'anova1', require( '@stdlib/stats/anova1' ) );

/**
* @name bartlettTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/bartlett-test}
*/
setReadOnly( ns, 'bartlettTest', require( '@stdlib/stats/bartlett-test' ) );

/**
* @name binomialTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/binomial-test}
*/
setReadOnly( ns, 'binomialTest', require( '@stdlib/stats/binomial-test' ) );

/**
* @name chi2gof
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/chi2gof}
*/
setReadOnly( ns, 'chi2gof', require( '@stdlib/stats/chi2gof' ) );

/**
* @name chi2test
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/chi2test}
*/
setReadOnly( ns, 'chi2test', require( '@stdlib/stats/chi2test' ) );

/**
* @name flignerTest
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/fligner-test}
*/
setReadOnly( ns, 'flignerTest', require( '@stdlib/stats/fligner-test' ) );

/**
* @name incr
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/incr}
*/
setReadOnly( ns, 'incr', require( '@stdlib/stats/incr' ) );

/**
* @name iterators
* @memberof ns
* @readonly
* @type {Namespace}
* @see {@link module:@stdlib/stats/iter}
*/
setReadOnly( ns, 'iterators', require( '@stdlib/stats/iter' ) );

/**
* @name kde2d
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kde2d}
*/
setReadOnly( ns, 'kde2d', require( '@stdlib/stats/kde2d' ) );

/**
* @name kruskalTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kruskal-test}
*/
setReadOnly( ns, 'kruskalTest', require( '@stdlib/stats/kruskal-test' ) );

/**
* @name kstest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/kstest}
*/
setReadOnly( ns, 'kstest', require( '@stdlib/stats/kstest' ) );

/**
* @name leveneTest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/levene-test}
*/
setReadOnly( ns, 'leveneTest', require( '@stdlib/stats/levene-test' ) );

/**
* @name lowess
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/lowess}
*/
setReadOnly( ns, 'lowess', require( '@stdlib/stats/lowess' ) );

/**
* @name padjust
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/padjust}
*/
setReadOnly( ns, 'padjust', require( '@stdlib/stats/padjust' ) );

/**
* @name pcorrtest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/pcorrtest}
*/
setReadOnly( ns, 'pcorrtest', require( '@stdlib/stats/pcorrtest' ) );

/**
* @name ranks
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ranks}
*/
setReadOnly( ns, 'ranks', require( '@stdlib/stats/ranks' ) );

/**
* @name ttest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ttest}
*/
setReadOnly( ns, 'ttest', require( '@stdlib/stats/ttest' ) );

/**
* @name ttest2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ttest2}
*/
setReadOnly( ns, 'ttest2', require( '@stdlib/stats/ttest2' ) );

/**
* @name vartest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/vartest}
*/
setReadOnly( ns, 'vartest', require( '@stdlib/stats/vartest' ) );

/**
* @name wilcoxon
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/wilcoxon}
*/
setReadOnly( ns, 'wilcoxon', require( '@stdlib/stats/wilcoxon' ) );

/**
* @name ztest
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ztest}
*/
setReadOnly( ns, 'ztest', require( '@stdlib/stats/ztest' ) );

/**
* @name ztest2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/stats/ztest2}
*/
setReadOnly( ns, 'ztest2', require( '@stdlib/stats/ztest2' ) );

// EXPORTS //

module.exports = ns;
<|endoftext|>


<commit_before>'use strict';

var request = require('request');

var ONLY_PDF = true;

var defineUrlFromFile = function(file) {
  if(file.exportLinks) { // Gdocs ! Yay !
    console.log("Google docs type");
    var el = file.exportLinks;
    if(el["text/html"] && !ONLY_PDF) {
      return {
        url: el["text/html"],
        type: "document"
      };
    } else if(el["application/pdf"]) {
      return {
        url: el["application/pdf"],
        type: "file"
      }
    } else if(el["text/plain"]) {
      return {
        url: el["text/plain"],
        type: "document"
      };
    }
  }
  console.log("Standard file");
  return {
    url: file.downloadUrl,
    type: "file"
  }
};

/**
 * Run the task of uploading a document to anyfetch.
 * This function will be used as a queue
 * @see https://github.com/caolan/async#queueworker-concurrency
 *
 * @param {Object} task Task param
 * @param {AnyFetchClient} anyfetchClient The anyfetch client
 * @param {String}
 * @param {Function} cb Callback once task has been processed.
 */
module.exports = function(task, anyfetchClient, authToken, cb) {
  console.log(" ");
  console.log("====== " + task.id + " ======");
  if(task.deleted) {
    console.log("DELETE");
    // File has been removed
    return anyfetchClient.deletedocument(task.id, anyfetchClient, cb);
  }

  var fInfos = defineUrlFromFile(task);

  var isHTML = false;

  // Upload file onto AnyFetch
  var pathExt = "";
  if(task.exportLinks) {
    if(task.exportLinks["text/html"]) {
      pathExt = ".html";
      isHTML = true;
    } else {
      pathExt = ".pdf";
    }
  }

  var document = {
    identifier: task.id,
    actions: {
      'show': task.alternateLink,
    },
    metadatas: {
      title: task.title,
      path: "/" + task.id + "/" + task.title + pathExt,
    },
    document_type: fInfos.type,
    user_access: [anyfetchClient.accessToken]
  };
  console.log("Requesting file: " + document.metadatas.title);
  console.log("Requesting url: " + fInfos.url);
  var opts = {
    url: fInfos.url,
    headers: {
      'Authorization': task.authTokens.token_type + ' ' + task.authTokens.access_token
    },
    encoding: null
  };
  if(opts.url !== undefined) {
    request(opts, function(err, res, body) {
      if(err) {
        return cb(err);
      }
      var fBuffer = new Buffer(body, 'binary');
      var fConfig = {
        file: isHTML ? fBuffer.toString() : fBuffer,
        filename: task.originalFilename || task.title,
        knownLength: fBuffer.length
      };
      console.log("Size: " + fBuffer.length)
      console.log("CREATE/UPDATE");
      anyfetchClient.sendDocumentAndFile(document, fConfig, function(err) {
        if(err) {
          console.log(err);
          return cb(err);
        }
        return cb();
      });
    });
  } else {
    console.log("Folder: SKIP");
    return cb();
  }
};
<commit_msg>Forgot this<commit_after>'use strict';

var request = require('request');

var ONLY_PDF = true;

var defineUrlFromFile = function(file) {
  if(file.exportLinks) { // Gdocs ! Yay !
    console.log("Google docs type");
    var el = file.exportLinks;
    if(el["text/html"] && !ONLY_PDF) {
      return {
        url: el["text/html"],
        type: "document"
      };
    } else if(el["application/pdf"]) {
      return {
        url: el["application/pdf"],
        type: "file"
      }
    } else if(el["text/plain"]) {
      return {
        url: el["text/plain"],
        type: "document"
      };
    }
  }
  console.log("Standard file");
  return {
    url: file.downloadUrl,
    type: "file"
  }
};

/**
 * Run the task of uploading a document to anyfetch.
 * This function will be used as a queue
 * @see https://github.com/caolan/async#queueworker-concurrency
 *
 * @param {Object} task Task param
 * @param {AnyFetchClient} anyfetchClient The anyfetch client
 * @param {String}
 * @param {Function} cb Callback once task has been processed.
 */
module.exports = function(task, anyfetchClient, authToken, cb) {
  console.log(" ");
  console.log("====== " + task.id + " ======");
  if(task.deleted) {
    console.log("DELETE");
    // File has been removed
    return anyfetchClient.deletedocument(task.id, anyfetchClient, cb);
  }

  var fInfos = defineUrlFromFile(task);

  var isHTML = false;

  // Upload file onto AnyFetch
  var pathExt = "";
  if(task.exportLinks) {
    if(task.exportLinks["text/html"] && !ONLY_PDF) {
      pathExt = ".html";
      isHTML = true;
    } else {
      pathExt = ".pdf";
    }
  }

  var document = {
    identifier: task.id,
    actions: {
      'show': task.alternateLink,
    },
    metadatas: {
      title: task.title,
      path: "/" + task.id + "/" + task.title + pathExt,
    },
    document_type: fInfos.type,
    user_access: [anyfetchClient.accessToken]
  };
  console.log("Requesting file: " + document.metadatas.title);
  console.log("Requesting url: " + fInfos.url);
  var opts = {
    url: fInfos.url,
    headers: {
      'Authorization': task.authTokens.token_type + ' ' + task.authTokens.access_token
    },
    encoding: null
  };
  if(opts.url !== undefined) {
    request(opts, function(err, res, body) {
      if(err) {
        return cb(err);
      }
      var fBuffer = new Buffer(body, 'binary');
      var fConfig = {
        file: isHTML ? fBuffer.toString() : fBuffer,
        filename: task.originalFilename || task.title,
        knownLength: fBuffer.length
      };
      console.log("Size: " + fBuffer.length)
      console.log("CREATE/UPDATE");
      anyfetchClient.sendDocumentAndFile(document, fConfig, function(err) {
        if(err) {
          console.log(err);
          return cb(err);
        }
        return cb();
      });
    });
  } else {
    console.log("Folder: SKIP");
    return cb();
  }
};
<|endoftext|>


<commit_before>'use strict';

var Promise = require('bluebird');
var getOpConfig = require('../utils/config').getOpConfig;
var mocker = require('mocker-data-generator');
var dataSchema = require('../utils/data_utils');

function parsedSchema(opConfig) {
    var schema = false;

    if (opConfig.json_schema) {
        var firstPath = opConfig.json_schema;
        var nextPath = process.cwd() + '/' + opConfig.json_schema;

        try {
            if (fs.existsSync(firstPath)) {
                schema = require(firstPath);
            }
            else {
                schema = require(nextPath);
            }
        }
        catch (e) {
            throw new Error('Could not retrieve code for: ' + opConfig + '\n' + e);
        }
    }
    else {
        return dataSchema(opConfig, schema);
    }
}

function newReader(context, opConfig, jobConfig) {
    var schema = parsedSchema(opConfig);

    return function(msg) {
        if (opConfig.stress_test) {
            var results = [];
            var data = mocker()
                .schema('schema', schema, 1)
                .build(function(data) {
                    return data.schema[0]
                });
            for (var i = 0; i < msg; i++) {
                results.push(data)
            }
            return results;
        }
        else {
            return mocker()
                .schema('schema', schema, msg)
                .build(function(data) {
                    return data.schema
                });
        }
    }
}

function onceGenerator(context, opConfig, jobConfig) {
    var numOfRecords = opConfig.size;
    var lastOp = jobConfig.operations[jobConfig.operations.length - 1];
    var interval = lastOp.size ? lastOp.size : 5000;

    return function() {
        if (numOfRecords <= 0) {
            return null;
        }
        else {
            if (numOfRecords - interval >= 0) {
                numOfRecords = numOfRecords - interval;
                return interval;
            }
            else {
                var finalCount = numOfRecords;
                numOfRecords = 0;
                return finalCount;
            }
        }
    }
}

function persistentGenerator(context, opConfig, jobConfig) {
    return function() {
        return opConfig.size;
    }
}

function newSlicer(context, job, retryData, slicerAnalytics, logger) {
    var jobConfig = job.jobConfig;
    var opConfig = getOpConfig(jobConfig, 'elasticsearch_data_generator');
    var slicers = [];

    if (jobConfig.lifecycle === 'once') {
        slicers.push(onceGenerator(context, opConfig, jobConfig))
    }
    else {
        slicers.push(persistentGenerator(context, opConfig, jobConfig))
    }

    return Promise.resolve(slicers)
}

function schema() {
    return {
        json_schema: {
            doc: 'file path to custom data schema',
            default: "",
            format: 'optional_String'
        },
        size: {
            doc: 'The limit to the number of docs pulled in a chunk, if the number of docs retrieved ' +
            'by the interval exceeds this number, it will cause the function to recurse to provide a smaller batch',
            default: 5000,
            format: function(val) {
                if (isNaN(val)) {
                    throw new Error('size parameter for elasticsearch_data_generator must be a number')
                }
                else {
                    if (val <= 0) {
                        throw new Error('size parameter for elasticsearch_data_generator must be greater than zero')
                    }
                }
            }
        },
        start: {
            doc: 'The start date (ISOstring or in ms) to which it will read from ',
            default: "",
            format: 'optional_Date'
        },
        end: {
            doc: 'The end date (ISOstring or in ms) to which it will read to',
            default: "",
            format: 'optional_Date'
        },
        format: {
            doc: 'This is only used with the teraslice provided schema, can elect different time structures' +
            'such as dateNow, utcDate, utcBetween and isoBetween',
            default: "",
            format: 'optional_String'
        },
        stress_test: {
            doc: 'used to speed up the creation process to test elasticsearch load',
            default: false,
            format: Boolean
        },
        date_key: {
            doc: 'key value on schema where date should reside',
            default: 'created',
            format: String
        },
        set_id: {
            doc: 'used to make an id on the data that will be used for the doc _id for elasticsearch, values: base64url, hexadecimal, HEXADECIMAL',
            default: "",
            format: 'optional_String'
        },
        id_start_key: {
            doc: 'set if you would like to force the first part of the ID to a certain character',
            default: "",
            format: 'optional_String'
        }
    };
}

function op_validation(op) {
    if (op.id_start_key && !op.set_id) {
        throw new Error('elasticsearch_data_generator is mis-configured, id_start_key must be used with set_id parameter, please set the missing parameters')
    }
}

function post_validation(job, sysconfig) {
    var opConfig = getOpConfig(job, 'elasticsearch_data_generator');

    if (opConfig.set_id) {
        var indexSelectorConfig = job.operations.find(function(op) {
            return op._op === 'elasticsearch_index_selector';
        });

        if (!indexSelectorConfig.id_field) {
            throw new Error('elasticsearch_data_generator is mis-configured, set_id must be used in tandem with id_field which is set in elasticsearch_index_selector')
        }

        if (indexSelectorConfig.id_field !== 'id') {
            throw new Error('id_field set in elasticsearch_index_selector must be set to "id" when elasticsearch_data_generator is creating ids')
        }
    }
}

module.exports = {
    newReader: newReader,
    newSlicer: newSlicer,
    op_validation: op_validation,
    post_validation: post_validation,
    schema: schema
};

<commit_msg>Controlling the slicer queue length on data generator (#529)<commit_after>'use strict';

var Promise = require('bluebird');
var getOpConfig = require('../utils/config').getOpConfig;
var mocker = require('mocker-data-generator');
var dataSchema = require('../utils/data_utils');

function parsedSchema(opConfig) {
    var schema = false;

    if (opConfig.json_schema) {
        var firstPath = opConfig.json_schema;
        var nextPath = process.cwd() + '/' + opConfig.json_schema;

        try {
            if (fs.existsSync(firstPath)) {
                schema = require(firstPath);
            }
            else {
                schema = require(nextPath);
            }
        }
        catch (e) {
            throw new Error('Could not retrieve code for: ' + opConfig + '\n' + e);
        }
    }
    else {
        return dataSchema(opConfig, schema);
    }
}

function newReader(context, opConfig, jobConfig) {
    var schema = parsedSchema(opConfig);

    return function(msg) {
        if (opConfig.stress_test) {
            var results = [];
            var data = mocker()
                .schema('schema', schema, 1)
                .build(function(data) {
                    return data.schema[0]
                });
            for (var i = 0; i < msg; i++) {
                results.push(data)
            }
            return results;
        }
        else {
            return mocker()
                .schema('schema', schema, msg)
                .build(function(data) {
                    return data.schema
                });
        }
    }
}

function onceGenerator(context, opConfig, jobConfig) {
    var numOfRecords = opConfig.size;
    var lastOp = jobConfig.operations[jobConfig.operations.length - 1];
    var interval = lastOp.size ? lastOp.size : 5000;

    return function() {
        if (numOfRecords <= 0) {
            return null;
        }
        else {
            if (numOfRecords - interval >= 0) {
                numOfRecords = numOfRecords - interval;
                return interval;
            }
            else {
                var finalCount = numOfRecords;
                numOfRecords = 0;
                return finalCount;
            }
        }
    }
}

function persistentGenerator(context, opConfig, jobConfig) {
    return function() {
        return opConfig.size;
    }
}

function newSlicer(context, job, retryData, slicerAnalytics, logger) {
    var jobConfig = job.jobConfig;
    var opConfig = getOpConfig(jobConfig, 'elasticsearch_data_generator');
    var slicers = [];

    if (jobConfig.lifecycle === 'once') {
        slicers.push(onceGenerator(context, opConfig, jobConfig))
    }
    else {
        slicers.push(persistentGenerator(context, opConfig, jobConfig))
    }

    return Promise.resolve(slicers)
}

function schema() {
    return {
        json_schema: {
            doc: 'file path to custom data schema',
            default: "",
            format: 'optional_String'
        },
        size: {
            doc: 'The limit to the number of docs pulled in a chunk, if the number of docs retrieved ' +
            'by the interval exceeds this number, it will cause the function to recurse to provide a smaller batch',
            default: 5000,
            format: function(val) {
                if (isNaN(val)) {
                    throw new Error('size parameter for elasticsearch_data_generator must be a number')
                }
                else {
                    if (val <= 0) {
                        throw new Error('size parameter for elasticsearch_data_generator must be greater than zero')
                    }
                }
            }
        },
        start: {
            doc: 'The start date (ISOstring or in ms) to which it will read from ',
            default: "",
            format: 'optional_Date'
        },
        end: {
            doc: 'The end date (ISOstring or in ms) to which it will read to',
            default: "",
            format: 'optional_Date'
        },
        format: {
            doc: 'This is only used with the teraslice provided schema, can elect different time structures' +
            'such as dateNow, utcDate, utcBetween and isoBetween',
            default: "",
            format: 'optional_String'
        },
        stress_test: {
            doc: 'used to speed up the creation process to test elasticsearch load',
            default: false,
            format: Boolean
        },
        date_key: {
            doc: 'key value on schema where date should reside',
            default: 'created',
            format: String
        },
        set_id: {
            doc: 'used to make an id on the data that will be used for the doc _id for elasticsearch, values: base64url, hexadecimal, HEXADECIMAL',
            default: "",
            format: 'optional_String'
        },
        id_start_key: {
            doc: 'set if you would like to force the first part of the ID to a certain character',
            default: "",
            format: 'optional_String'
        }
    };
}

function op_validation(op) {
    if (op.id_start_key && !op.set_id) {
        throw new Error('elasticsearch_data_generator is mis-configured, id_start_key must be used with set_id parameter, please set the missing parameters')
    }
}

function post_validation(job, sysconfig) {
    var opConfig = getOpConfig(job, 'elasticsearch_data_generator');

    if (opConfig.set_id) {
        var indexSelectorConfig = job.operations.find(function(op) {
            return op._op === 'elasticsearch_index_selector';
        });

        if (!indexSelectorConfig.id_field) {
            throw new Error('elasticsearch_data_generator is mis-configured, set_id must be used in tandem with id_field which is set in elasticsearch_index_selector')
        }

        if (indexSelectorConfig.id_field !== 'id') {
            throw new Error('id_field set in elasticsearch_index_selector must be set to "id" when elasticsearch_data_generator is creating ids')
        }
    }
}

function slicerQueueLength() {
    // Queue is not really needed so we just want the smallest queue size available.
    return 'QUEUE_MINIMUM_SIZE';
}

module.exports = {
    newReader: newReader,
    newSlicer: newSlicer,
    op_validation: op_validation,
    post_validation: post_validation,
    schema: schema,
    slicerQueueLength: slicerQueueLength
};

<|endoftext|>