<commit_before>/* Author: Loconomics */
// OUR namespace (abbreviated Loconomics)
var LC = window['LC'] || {};

function bookingChangeLocation() {
    var sel = $(this);
    // :hidden selectors is a hack because jQuery doesn't
    // hide elements that are inside a hidden parent.
    if (sel.val() == "0") {
        sel.siblings('.enter-new-location').show('fast');
    } else {
        sel.siblings('.enter-new-location, .enter-new-location:hidden').hide('fast');
    }
}
/* DEPRECATED: use LC.timeSpan defined at script.js, ever available */
function lcTime (hour, minute, second) {
    this.hour = Math.floor(parseFloat(hour || 0));
    this.minute = Math.floor(parseFloat(minute || 0));
    this.second = Math.floor(parseFloat(second || 0));

    this.toString = function () {
        var h = this.hour.toString();
        if (h.length == 1)
            h = '0' + h;
        var m = this.minute.toString();
        if (m.length == 1)
            m = '0' + m;
        var s = this.second.toString();
        if (s.length == 1)
            s = '0' + s;
        return h + lcTime.delimiter + m + lcTime.delimiter + s;
    };
    /* Print only hours and minutes in the common time of the day format
        (AM/PM in US, 24h on ES)
       TODO: Needs migration to LC.timeSpan
    */
    this.toDayTimeString = function () {
        switch ($('html').attr('lang')) {
            case 'es':
                var h = this.hour.toString();
                if (h.length == 1)
                    h = '0' + h;
                var m = this.minute.toString();
                if (m.length == 1)
                    m = '0' + m;
                return h + lcTime.delimiter + m;
            case 'en':
            default:
                var h = this.hour.toString();
                var sufix = ' AM';
                if (h >= 12 && h < 24)
                    sufix = ' PM';
                if (h > 12)
                    h = h % 12;
                if (h == 0)
                    h = 12;
                var m = this.minute.toString();
                if (m.length == 1)
                    m = '0' + m;
                return h + lcTime.delimiter + m + sufix;
        }
    };
    /* TODO: Needs migration to LC.timeSpan
    */
    this.addHours = function (hours) {
        this.addMinutes(hours * 60);
        return this;
    };
    /* TODO: Needs migration to LC.timeSpan
    */
    this.addMinutes = function (minutes) {
        var m = this.hour * 60 + this.minute;
        var ntime = minutes + m;
        this.hour = Math.floor(ntime / 60);
        this.minute = ntime % 60;
        return this;
    };
};
// For spanish and english works good ':' as delimiter
lcTime.delimiter = ':';
lcTime.parse = function (strtime) {
    strtime = (strtime || '').split(this.delimiter);
    if (strtime.length > 1) {
        return new lcTime(strtime[0], strtime[1], (strtime.length > 2 ? strtime[2] : 0));
    }
    return null;
};

LC.showDateHours = function (date) {
    // Load date hours:
    var $day = $('#dayHoursSelector');
    var strdate = LC.dateToInterchangleString(date);
    var minutes = $day.data('duration-minutes');
    var userid = $day.data('user-id');
    $day.reload(LcUrl.LangPath + "Booking/$ScheduleCalendarElements/DayHoursSelector/" +
            encodeURIComponent(strdate) + '/' + minutes + '/' + userid + '/', LC.markSelectedDates);
    $day.data('date', date);
};
LC.showWeek = function (date) {
    var $week = $('#weekDaySelector');

    if (/(previous|next)/.test(date.toString())) {
        var x = date;
        date = new Date($week.data('date'));
        if (/previous/.test(x))
            date.setDate(date.getDate() - 7);
        else if (/next/.test(x))
            date.setDate(date.getDate() + 7);
    }

    var strdate = LC.dateToInterchangleString(date);
    $week.reload(LcUrl.LangPath + "Booking/$ScheduleCalendarElements/WeekDaySelector/" +
        encodeURIComponent(strdate) + '/',
        function () { LC.selectWeekDay(date); LC.showDateHours(date); });
    $week.data('date', date);
};
LC.selectWeekDay = function (date) {
    var $week = $('#weekDaySelector');
    // Mark selected day in calendar
    $week.find('.day-selection-action').removeClass('current')
    .filter(function () {
        var elDate = new Date($(this).data('date'));
        return (elDate.toDateString() == date.toDateString());
    }).addClass('current');
};
LC.setupScheduleCalendar = function () {
    var $scheduleStep = $('#booking-schedule')
    .on('click', '#weekDaySelector .week-slider', function () {

        LC.showWeek(this.getAttribute('href').substring(1));

        return false;
    })
    .on('click', '#weekDaySelector .day-selection-action', function () {
        var date = new Date($(this).data('date'));

        LC.showDateHours(date);
        LC.selectWeekDay(date);

        return false;
    })
    .on('change', '#dayHoursSelector select.choice-selector', function () {
        var $s = $(this), v = $s.val();
        if (v) {
            // Get row date and time
            var $row = $s.closest('tr');
            var $table = $row.closest('table');
            var start = $row.find('.start').text();
            var end = $row.find('.end').text();
            var date = new Date($table.data('date'));
            var dateshowed = $table.find('caption').text();

            // Show date and time
            var choice = $(this).closest('form').find('.selected-schedule').find('.' + v + '-choice');
            choice.addClass('has-values');
            choice.find('span.date-showed').text(dateshowed);
            choice.find('span.start-time').text(start);
            choice.find('span.end-time').text(end);
            choice.find('input.date').val(LC.dateToInterchangleString(date));
            choice.find('input.start-time').val(start);

            // Others Select with this same option selected must be reset:
            $s.closest('tr').siblings().find('option[value=' + v + ']:selected').closest('select').val('');
        }
    })
    .on('click', '.unselect-action', function () {
        // Remove values from view-selection panel and form
        $(this).siblings('span').text('')
        .closest('.has-values').removeClass('has-values')
        .find('input').val('');
        // Remove selection from day-hours panel
        var v = $(this).closest('.choice').data('choice');
        $('#dayHoursSelector').find('.selector option[value=' + v + ']').prop('selected', false);
        return false;
    });

    // Select current day in week selector
    LC.selectWeekDay(new Date($('#dayHoursSelector').data('date')));
};
LC.getSelectedDates = function (filterDate) {
    var selected = { first: 0, second: 0, third: 0 };
    for (var v in selected) {
        var choice = $('.selected-schedule').find('.' + v + '-choice');
        var d = choice.find('input.date').val();
        var t = choice.find('input.start-time').val();
        if (filterDate == d)
            selected[v] = { Date: d, Time: t };
    }
    return selected;
};
LC.markSelectedDates = function () {
    var $day = $('#dayHoursSelector');
    var selected = LC.getSelectedDates(LC.dateToInterchangleString(new Date($day.data('date'))));
    $day.find('tr > .start').each(function () {
        for (var v in selected)
            if (selected[v] && $(this).text() == selected[v].Time)
                $(this).siblings('.selector').find('option[value=' + v + ']').prop('selected', true);
    });
};
LC.setupServiceMap = function () {
    LC.mapReady(function () {
        $('.serviceRadiusMap').each(function () {
            var m = $(this);
            var radius = m.data('service-radius');
            var latlng = new google.maps.LatLng(parseFloat(m.data('latitude')), parseFloat(m.data('longitude')));
            //latlng = new google.maps.LatLng(37.75334439226298, -122.4254606035156);
            var mapOptions = {
                zoom: 10,
                center: latlng,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            }
            var map = new google.maps.Map(m.get(0), mapOptions);
            var radiusUnit = LC.distanceUnits[LC.getCurrentCulture().country];
            var circle = new google.maps.Circle({
                center: latlng,
                map: map,
                clickable: false,
                radius: (radiusUnit == 'miles' ? convertMilesKm(radius, radiusUnit) : radius) * 1000, // in meters
                fillColor: '#00989A',
                fillOpacity: .3,
                strokeWeight: 0
            });
        });
    });
};

LC.initScheduleStep = function () {
    var tab = $('#schedule');

    // Execute first time when showing the step
    $.proxy(bookingChangeLocation, $('.select-location'))();
    $('.select-location').change(bookingChangeLocation);

    applyDatePicker(tab);

    // Read current hidden date to be set in show date (previous apply datepicker options)
    var date = $('#hideDate').val();

    $("#showDate")
    .datepicker('setDate', date)
    .datepicker('option', 'dateFormat', 'DD, M d, yy')
    .datepicker('option', 'altField', $('#hideDate'))
    .datepicker('option', 'altFormat', $.datepicker._defaults.dateFormat)
    .datepicker('option', 'numberOfMonths', 2)
    .datepicker('option', 'onSelect', function () {
        // Hour is added with GMT (+0000) to avoid problems on getting date because by
        // default javascript adds the local time zone
        var date = new Date($('#hideDate').val() + ' 00:00:00 GMT');
        LC.showDateHours(date);
        LC.showWeek(date);
    });

    LC.setupScheduleCalendar();

    LC.setupServiceMap();
};
// Sliders on Housekeeper price:
LC.initCustomerPackageSliders = function () {
    /* Houseekeeper pricing */
    function updateAverage($c, value) {
        // Update input value and trigger change event to notify standard listeners
        $c.find('input').val(value).change();
    }
    function calculateHousekeeperPackage($sliderContent) {
        // Look for the housekeeper pricing container for this package
        // that contains all fields for calculation
        var calcContext = $sliderContent.closest('.housekeeper-pricing');
        // Get the package container, where contents to be update are
        var pak = calcContext.closest('tr');
        // Calculation formula for housekeeper pricing
        var formula = function (numbedrooms, numbathrooms) {
            var formulaA = parseFloat(calcContext.data('formula-a')),
                formulaB = parseFloat(calcContext.data('formula-b')),
                formulaC = parseFloat(calcContext.data('formula-c')),
                rate = parseFloat(calcContext.data('provider-rate'));
            // It returns a time in float minutes
            return (formulaA * numbedrooms + formulaB * numbathrooms + formulaC) * rate;
        };
        // Getting var-values from form and calculating
        var numbedrooms = calcContext.find('.housekeeper-pricing-bedrooms input').val(),
            numbathrooms = calcContext.find('.housekeeper-pricing-bathrooms input').val();
        // ...Gets duration rounded up to quarter-hours.
        var duration = LC.roundTimeToQuarterHour(
        // ..create a time object..
            LC.timeSpan.fromMinutes(
        // Computes formula with customer values...
                formula(numbedrooms, numbathrooms)
            )
        , LC.roundingTypeEnum.Up);
        // Updating user-viewed time, show it in the smart way
        pak.find('.package-duration').text(duration.toSmartString());
        // Recalculating price with new time, using the package hourly-rate
        var hourlyRate = parseFloat(calcContext.data('hourly-rate'));
        var hourlyFee = parseFloat(calcContext.data('hourly-fee'));
        // Calculate the price for the total time,
        // with the hourleRate (already with fees), hourlyFeeAmount
        // and the already rounded duration:
        var totalTimePrice = LC.calculateHourlyPrice(duration, hourlyRate, hourlyFee);
        // Set new item-price and trigger a change event to allow the items-fees calculation
        // system do their job and showing the total price
        LC.setMoneyNumber(totalTimePrice.totalPrice, pak.find('.calculate-item-price'));
        LC.setMoneyNumber(totalTimePrice.feePrice, pak.find('.calculate-item-fee'));
        pak.find('.calculate-item-price').trigger('change');
    }
    $(".customer-slider").each(function () {
        var $c = $(this);
        // Creating sliders
        var average = $c.data('slider-value'),
            step = $c.data('slider-step') || 1;
        if (!average) return;
        var slider = $('<div class="slider"/>').appendTo($c);
        var setup = {
            range: "min",
            value: average,
            min: average - 3 * step,
            max: average + 3 * step,
            step: step,
            change: function (event, ui) {
                updateAverage($c, ui.value);
            }
        };
        slider.slider(setup);
        LC.createLabelsForUISlider(slider);
        // Setup the input field, hidden and with initial value synchronized with slider
        var field = $c.find('input');
        field.hide();
        var currentValue = field.val() || average;
        updateAverage($c, currentValue);
        slider.slider('value', currentValue);

        // Perform calculation of time-prices for the package on
        // init and on variables changes
        calculateHousekeeperPackage($c);
        $c.on('change', 'input', function () { calculateHousekeeperPackage($c) });

        // Switching sliders visualization on active package
        $('.pricing-wizard .packages-list input[name="provider-package"]').change(function () {
            var $t = $(this),
                row = $t.closest('tr');
            if ($t.is(':checked'))
                row.find('.package-extra-data').slideDown('fast');
            else
                row.find('.package-extra-data').slideUp('fast');
        }).change();
    });
};

$(document).ready(function () {
    LC.initCustomerPackageSliders();

    // Setup Schedule step:
    $('#schedule').on('endLoadWizardStep', function () {
        // Getting the tab content for payment that is not loaded at the start
        var tab = $('#schedule');

        // Loading, with retard
        var loadingtimer = setTimeout(function () {
            tab.block(loadingBlock);
        }, gLoadingRetard);

        $.ajax({
            // Request $Payment partial page, with all the original url parameters
            url: LcUrl.LangPath + "Booking/$Schedule/" + location.search,
            type: 'GET',
            success: function (data, text, jx) {
                // load tab content
                tab.html(data);

                LC.initScheduleStep();
            },
            error: ajaxErrorPopupHandler,
            complete: function () {
                // Disable loading
                clearTimeout(loadingtimer);
                // Unblock
                tab.unblock();
            }
        });
    }).on('reloadedHtmlWizardStep', function () {
        LC.initScheduleStep();
    });

    // Load payment content on step change:
    $('#payment').bind('endLoadWizardStep', function () {
        // Getting the tab content for payment that is not loaded at the start
        var paymentTab = $('#payment');

        // Loading, with retard
        var loadingtimer = setTimeout(function () {
            paymentTab.block(loadingBlock);
        }, gLoadingRetard);

        $.ajax({
            // Request $Payment partial page, with all the original url parameters
            url: LcUrl.LangPath + "Booking/$Payment/" + location.search,
            type: 'GET',
            success: function (data, text, jx) {
                // load tab content
                paymentTab.html(data);
            },
            error: ajaxErrorPopupHandler,
            complete: function () {
                // Disable loading
                clearTimeout(loadingtimer);
                // Unblock
                paymentTab.unblock();
            }
        });
    });
});
<commit_msg>Removed the behavior 'autofocus' on reload calendar elements on booking/schedule/ because there its a bit annoying instead of helping.<commit_after>/* Author: Loconomics */
// OUR namespace (abbreviated Loconomics)
var LC = window['LC'] || {};

function bookingChangeLocation() {
    var sel = $(this);
    // :hidden selectors is a hack because jQuery doesn't
    // hide elements that are inside a hidden parent.
    if (sel.val() == "0") {
        sel.siblings('.enter-new-location').show('fast');
    } else {
        sel.siblings('.enter-new-location, .enter-new-location:hidden').hide('fast');
    }
}
/* DEPRECATED: use LC.timeSpan defined at script.js, ever available */
function lcTime (hour, minute, second) {
    this.hour = Math.floor(parseFloat(hour || 0));
    this.minute = Math.floor(parseFloat(minute || 0));
    this.second = Math.floor(parseFloat(second || 0));

    this.toString = function () {
        var h = this.hour.toString();
        if (h.length == 1)
            h = '0' + h;
        var m = this.minute.toString();
        if (m.length == 1)
            m = '0' + m;
        var s = this.second.toString();
        if (s.length == 1)
            s = '0' + s;
        return h + lcTime.delimiter + m + lcTime.delimiter + s;
    };
    /* Print only hours and minutes in the common time of the day format
        (AM/PM in US, 24h on ES)
       TODO: Needs migration to LC.timeSpan
    */
    this.toDayTimeString = function () {
        switch ($('html').attr('lang')) {
            case 'es':
                var h = this.hour.toString();
                if (h.length == 1)
                    h = '0' + h;
                var m = this.minute.toString();
                if (m.length == 1)
                    m = '0' + m;
                return h + lcTime.delimiter + m;
            case 'en':
            default:
                var h = this.hour.toString();
                var sufix = ' AM';
                if (h >= 12 && h < 24)
                    sufix = ' PM';
                if (h > 12)
                    h = h % 12;
                if (h == 0)
                    h = 12;
                var m = this.minute.toString();
                if (m.length == 1)
                    m = '0' + m;
                return h + lcTime.delimiter + m + sufix;
        }
    };
    /* TODO: Needs migration to LC.timeSpan
    */
    this.addHours = function (hours) {
        this.addMinutes(hours * 60);
        return this;
    };
    /* TODO: Needs migration to LC.timeSpan
    */
    this.addMinutes = function (minutes) {
        var m = this.hour * 60 + this.minute;
        var ntime = minutes + m;
        this.hour = Math.floor(ntime / 60);
        this.minute = ntime % 60;
        return this;
    };
};
// For spanish and english works good ':' as delimiter
lcTime.delimiter = ':';
lcTime.parse = function (strtime) {
    strtime = (strtime || '').split(this.delimiter);
    if (strtime.length > 1) {
        return new lcTime(strtime[0], strtime[1], (strtime.length > 2 ? strtime[2] : 0));
    }
    return null;
};

LC.showDateHours = function (date) {
    // Load date hours:
    var $day = $('#dayHoursSelector');
    var strdate = LC.dateToInterchangleString(date);
    var minutes = $day.data('duration-minutes');
    var userid = $day.data('user-id');
    $day.reload({
        url: LcUrl.LangPath + "Booking/$ScheduleCalendarElements/DayHoursSelector/" +
            encodeURIComponent(strdate) + '/' + minutes + '/' + userid + '/',
        complete: LC.markSelectedDates,
        autofocus: false
    });
    $day.data('date', date);
};
LC.showWeek = function (date) {
    var $week = $('#weekDaySelector');

    if (/(previous|next)/.test(date.toString())) {
        var x = date;
        date = new Date($week.data('date'));
        if (/previous/.test(x))
            date.setDate(date.getDate() - 7);
        else if (/next/.test(x))
            date.setDate(date.getDate() + 7);
    }

    var strdate = LC.dateToInterchangleString(date);
    $week.reload({
        url: LcUrl.LangPath + "Booking/$ScheduleCalendarElements/WeekDaySelector/" +
            encodeURIComponent(strdate) + '/',
        complete: function () { LC.selectWeekDay(date); LC.showDateHours(date); },
        autofocus: false
    });
    $week.data('date', date);
};
LC.selectWeekDay = function (date) {
    var $week = $('#weekDaySelector');
    // Mark selected day in calendar
    $week.find('.day-selection-action').removeClass('current')
    .filter(function () {
        var elDate = new Date($(this).data('date'));
        return (elDate.toDateString() == date.toDateString());
    }).addClass('current');
};
LC.setupScheduleCalendar = function () {
    var $scheduleStep = $('#booking-schedule')
    .on('click', '#weekDaySelector .week-slider', function () {

        LC.showWeek(this.getAttribute('href').substring(1));

        return false;
    })
    .on('click', '#weekDaySelector .day-selection-action', function () {
        var date = new Date($(this).data('date'));

        LC.showDateHours(date);
        LC.selectWeekDay(date);

        return false;
    })
    .on('change', '#dayHoursSelector select.choice-selector', function () {
        var $s = $(this), v = $s.val();
        if (v) {
            // Get row date and time
            var $row = $s.closest('tr');
            var $table = $row.closest('table');
            var start = $row.find('.start').text();
            var end = $row.find('.end').text();
            var date = new Date($table.data('date'));
            var dateshowed = $table.find('caption').text();

            // Show date and time
            var choice = $(this).closest('form').find('.selected-schedule').find('.' + v + '-choice');
            choice.addClass('has-values');
            choice.find('span.date-showed').text(dateshowed);
            choice.find('span.start-time').text(start);
            choice.find('span.end-time').text(end);
            choice.find('input.date').val(LC.dateToInterchangleString(date));
            choice.find('input.start-time').val(start);

            // Others Select with this same option selected must be reset:
            $s.closest('tr').siblings().find('option[value=' + v + ']:selected').closest('select').val('');
        }
    })
    .on('click', '.unselect-action', function () {
        // Remove values from view-selection panel and form
        $(this).siblings('span').text('')
        .closest('.has-values').removeClass('has-values')
        .find('input').val('');
        // Remove selection from day-hours panel
        var v = $(this).closest('.choice').data('choice');
        $('#dayHoursSelector').find('.selector option[value=' + v + ']').prop('selected', false);
        return false;
    });

    // Select current day in week selector
    LC.selectWeekDay(new Date($('#dayHoursSelector').data('date')));
};
LC.getSelectedDates = function (filterDate) {
    var selected = { first: 0, second: 0, third: 0 };
    for (var v in selected) {
        var choice = $('.selected-schedule').find('.' + v + '-choice');
        var d = choice.find('input.date').val();
        var t = choice.find('input.start-time').val();
        if (filterDate == d)
            selected[v] = { Date: d, Time: t };
    }
    return selected;
};
LC.markSelectedDates = function () {
    var $day = $('#dayHoursSelector');
    var selected = LC.getSelectedDates(LC.dateToInterchangleString(new Date($day.data('date'))));
    $day.find('tr > .start').each(function () {
        for (var v in selected)
            if (selected[v] && $(this).text() == selected[v].Time)
                $(this).siblings('.selector').find('option[value=' + v + ']').prop('selected', true);
    });
};
LC.setupServiceMap = function () {
    LC.mapReady(function () {
        $('.serviceRadiusMap').each(function () {
            var m = $(this);
            var radius = m.data('service-radius');
            var latlng = new google.maps.LatLng(parseFloat(m.data('latitude')), parseFloat(m.data('longitude')));
            //latlng = new google.maps.LatLng(37.75334439226298, -122.4254606035156);
            var mapOptions = {
                zoom: 10,
                center: latlng,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            }
            var map = new google.maps.Map(m.get(0), mapOptions);
            var radiusUnit = LC.distanceUnits[LC.getCurrentCulture().country];
            var circle = new google.maps.Circle({
                center: latlng,
                map: map,
                clickable: false,
                radius: (radiusUnit == 'miles' ? convertMilesKm(radius, radiusUnit) : radius) * 1000, // in meters
                fillColor: '#00989A',
                fillOpacity: .3,
                strokeWeight: 0
            });
        });
    });
};

LC.initScheduleStep = function () {
    var tab = $('#schedule');

    // Execute first time when showing the step
    $.proxy(bookingChangeLocation, $('.select-location'))();
    $('.select-location').change(bookingChangeLocation);

    applyDatePicker(tab);

    // Read current hidden date to be set in show date (previous apply datepicker options)
    var date = $('#hideDate').val();

    $("#showDate")
    .datepicker('setDate', date)
    .datepicker('option', 'dateFormat', 'DD, M d, yy')
    .datepicker('option', 'altField', $('#hideDate'))
    .datepicker('option', 'altFormat', $.datepicker._defaults.dateFormat)
    .datepicker('option', 'numberOfMonths', 2)
    .datepicker('option', 'onSelect', function () {
        // Hour is added with GMT (+0000) to avoid problems on getting date because by
        // default javascript adds the local time zone
        var date = new Date($('#hideDate').val() + ' 00:00:00 GMT');
        LC.showDateHours(date);
        LC.showWeek(date);
    });

    LC.setupScheduleCalendar();

    LC.setupServiceMap();
};
// Sliders on Housekeeper price:
LC.initCustomerPackageSliders = function () {
    /* Houseekeeper pricing */
    function updateAverage($c, value) {
        // Update input value and trigger change event to notify standard listeners
        $c.find('input').val(value).change();
    }
    function calculateHousekeeperPackage($sliderContent) {
        // Look for the housekeeper pricing container for this package
        // that contains all fields for calculation
        var calcContext = $sliderContent.closest('.housekeeper-pricing');
        // Get the package container, where contents to be update are
        var pak = calcContext.closest('tr');
        // Calculation formula for housekeeper pricing
        var formula = function (numbedrooms, numbathrooms) {
            var formulaA = parseFloat(calcContext.data('formula-a')),
                formulaB = parseFloat(calcContext.data('formula-b')),
                formulaC = parseFloat(calcContext.data('formula-c')),
                rate = parseFloat(calcContext.data('provider-rate'));
            // It returns a time in float minutes
            return (formulaA * numbedrooms + formulaB * numbathrooms + formulaC) * rate;
        };
        // Getting var-values from form and calculating
        var numbedrooms = calcContext.find('.housekeeper-pricing-bedrooms input').val(),
            numbathrooms = calcContext.find('.housekeeper-pricing-bathrooms input').val();
        // ...Gets duration rounded up to quarter-hours.
        var duration = LC.roundTimeToQuarterHour(
        // ..create a time object..
            LC.timeSpan.fromMinutes(
        // Computes formula with customer values...
                formula(numbedrooms, numbathrooms)
            )
        , LC.roundingTypeEnum.Up);
        // Updating user-viewed time, show it in the smart way
        pak.find('.package-duration').text(duration.toSmartString());
        // Recalculating price with new time, using the package hourly-rate
        var hourlyRate = parseFloat(calcContext.data('hourly-rate'));
        var hourlyFee = parseFloat(calcContext.data('hourly-fee'));
        // Calculate the price for the total time,
        // with the hourleRate (already with fees), hourlyFeeAmount
        // and the already rounded duration:
        var totalTimePrice = LC.calculateHourlyPrice(duration, hourlyRate, hourlyFee);
        // Set new item-price and trigger a change event to allow the items-fees calculation
        // system do their job and showing the total price
        LC.setMoneyNumber(totalTimePrice.totalPrice, pak.find('.calculate-item-price'));
        LC.setMoneyNumber(totalTimePrice.feePrice, pak.find('.calculate-item-fee'));
        pak.find('.calculate-item-price').trigger('change');
    }
    $(".customer-slider").each(function () {
        var $c = $(this);
        // Creating sliders
        var average = $c.data('slider-value'),
            step = $c.data('slider-step') || 1;
        if (!average) return;
        var slider = $('<div class="slider"/>').appendTo($c);
        var setup = {
            range: "min",
            value: average,
            min: average - 3 * step,
            max: average + 3 * step,
            step: step,
            change: function (event, ui) {
                updateAverage($c, ui.value);
            }
        };
        slider.slider(setup);
        LC.createLabelsForUISlider(slider);
        // Setup the input field, hidden and with initial value synchronized with slider
        var field = $c.find('input');
        field.hide();
        var currentValue = field.val() || average;
        updateAverage($c, currentValue);
        slider.slider('value', currentValue);

        // Perform calculation of time-prices for the package on
        // init and on variables changes
        calculateHousekeeperPackage($c);
        $c.on('change', 'input', function () { calculateHousekeeperPackage($c) });

        // Switching sliders visualization on active package
        $('.pricing-wizard .packages-list input[name="provider-package"]').change(function () {
            var $t = $(this),
                row = $t.closest('tr');
            if ($t.is(':checked'))
                row.find('.package-extra-data').slideDown('fast');
            else
                row.find('.package-extra-data').slideUp('fast');
        }).change();
    });
};

$(document).ready(function () {
    LC.initCustomerPackageSliders();

    // Setup Schedule step:
    $('#schedule').on('endLoadWizardStep', function () {
        // Getting the tab content for payment that is not loaded at the start
        var tab = $('#schedule');

        // Loading, with retard
        var loadingtimer = setTimeout(function () {
            tab.block(loadingBlock);
        }, gLoadingRetard);

        $.ajax({
            // Request $Payment partial page, with all the original url parameters
            url: LcUrl.LangPath + "Booking/$Schedule/" + location.search,
            type: 'GET',
            success: function (data, text, jx) {
                // load tab content
                tab.html(data);

                LC.initScheduleStep();
            },
            error: ajaxErrorPopupHandler,
            complete: function () {
                // Disable loading
                clearTimeout(loadingtimer);
                // Unblock
                tab.unblock();
            }
        });
    }).on('reloadedHtmlWizardStep', function () {
        LC.initScheduleStep();
    });

    // Load payment content on step change:
    $('#payment').bind('endLoadWizardStep', function () {
        // Getting the tab content for payment that is not loaded at the start
        var paymentTab = $('#payment');

        // Loading, with retard
        var loadingtimer = setTimeout(function () {
            paymentTab.block(loadingBlock);
        }, gLoadingRetard);

        $.ajax({
            // Request $Payment partial page, with all the original url parameters
            url: LcUrl.LangPath + "Booking/$Payment/" + location.search,
            type: 'GET',
            success: function (data, text, jx) {
                // load tab content
                paymentTab.html(data);
            },
            error: ajaxErrorPopupHandler,
            complete: function () {
                // Disable loading
                clearTimeout(loadingtimer);
                // Unblock
                paymentTab.unblock();
            }
        });
    });
});
<|endoftext|>


<commit_before>
if(typeof define !== 'function') {
  var define = require('amdefine')(module);
}

define(function(require) {
  var View = inServer ? require('../../libraries/View') : require('View')
    , template = inServer ? content_appTemplates : require('contentTemplates')
    , _ = require('underscore');

  if(inClient) {
    var minTimer = require('minTimer')
      , SinusWave = require('../../libraries/client/SinusWave');
  }

  return View.extend({

    /**
     * Initializer
     *
     * @return {void}
     * @api public
     */

    initialize: function(model) {
      this.model = model;
      if(inClient) {
        this._lastSave = Date.now();
        this._bindMethods();
      }
    },

    /**
     * Bind view
     *
     * @return {void}
     * @api private
     */

    bindDOM: function() {
      this._setElements();
      this._addDesktopInteractions();
    },

    /**
     * Bind methods
     *
     * @return {void}
     * @api private
     */

    _bindMethods: function() {
      _.bindAll(
        this,
        '_save',
        '_syncValue',
        '_delayedResize',
        '_resizeTextArea',
        '_addPluralFormatedText',
        '_addSelectFormatedText',
        '_addSelectordinalFormatedText',
        '_addNumberFormatedText',
        '_addCurrencyFormatedText',
        '_addVariableFormatedText'
      );
    },

    /**
     * Resize textarea
     *
     * @api private
     * @handler
     */

    _resizeTextArea: function(event) {
      if(typeof event !== 'undefined' && event.keyCode === 8) {
        this.$textAreaMirror.val('');
      }
      else if(typeof event !== 'undefined' && event.keyCode === 13) {
        this.$textAreaMirror.val(this.$textArea.val() + '\nsuffix');
      }
      else {
        this.$textAreaMirror.val(this.$textArea.val() + 'suffix');
      }
      var height = this.$textAreaMirror[0].scrollHeight -
        parseInt(this.$textAreaMirror.css('padding-top').replace('px', ''), 10) -
        parseInt(this.$textAreaMirror.css('padding-bottom').replace('px', ''), 10);
      this.$textArea.css('height', height + 'px');
    },

    /**
     * Delay resize
     *
     * @api private
     * @handler
     */

    _delayedResize: function() {
      _.defer(this._resizeTextArea);
    },

    /**
     * Syn value with model
     *
     * @return {void}
     * @api private
     * @handler
     */

    _syncValue: function() {
      this.model.set('value', this.$textArea.val());
    },

    /**
     * Add desktop listeners
     *
     * @return {void}
     * @api private
     */

    _addDesktopInteractions: function() {
      this.$('[disabled]').removeAttr('disabled');
      this.$el.on('click', '.save', this._save);
      this.$textArea.on('change', this._resizeTextArea);
      this.$textArea.on('change', this._syncValue);
      this.$textArea.on('cut', this._resizeTextArea);
      this.$textArea.on('paste', this._resizeTextArea);
      this.$textArea.on('drop', this._resizeTextArea);
      this.$textArea.on('keydown', this._resizeTextArea);
      this.$addPluralButton.on('mousedown', this._addPluralFormatedText);
      this.$addSelectButton.on('mousedown', this._addSelectFormatedText);
      this.$addSelectordinalButton.on('mousedown', this._addSelectordinalFormatedText);
      this.$addNumberButton.on('mousedown', this._addNumberFormatedText);
      this.$addCurrencyButton.on('mousedown', this._addCurrencyFormatedText);
      this.$variable.on('mousedown', this._addVariableFormatedText);
      this.$save.on('mouseup', this._save);
      this._resizeTextArea();
    },

    /**
     * Replace selected text in textarea with text
     *
     * @param {String} text
     * @return {void}
     * @api private
     */

    _replaceTextSelectionWithText: function(text) {
      var _this = this
        , string = this.$textArea.val()
        , start = this.$textArea[0].selectionStart
        , end = this.$textArea[0].selectionEnd
        , startString = string.substring(0, start)
        , endString = string.substring(end, string.length);

      this.$textArea.val(startString + text + endString);
      this._syncValue();
      this._resizeTextArea();

      setTimeout(function() {
        if(_this.$textArea[0].createTextRange) {
          var range = _this.$textArea[0].createTextRange();
          range.move('character', end);
          range.select();
        }
        else {
          if(_this.$textArea[0].selectionStart) {
            _this.$textArea[0].focus();
            _this.$textArea[0].setSelectionRange(start, text.length + start);
          }
          else {
            _this.$textArea[0].focus();
          }
        }
      }, 0);
    },

    /**
     * Add variable formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addVariableFormatedText: function(event) {
      var string = this.$textArea.val()
        , start = this.$textArea[0].selectionStart
        , end = this.$textArea[0].selectionEnd
        , startString = string.charAt(start - 1)
        , variable = event.currentTarget.getAttribute('data-value')
        , text;

      if(startString === '{') {
        text = event.currentTarget.getAttribute('data-value');
      }
      else {
        text = '{' + event.currentTarget.getAttribute('data-value') + '}';
      }

      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(_this.model.get('message'));
    },

    /**
     * Add plural formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addPluralFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', plural,';
      var keywords =  Object.keys(this.model.get('pluralRules'));
      for(var index = 0; index < keywords.length; index++) {
        text +=  ' ' + keywords[index] + ' {message-' + keywords[index] + '}';
      }
      text += '}';
      this._replaceTextSelectionWithText(text);
      var exampleText = 'Example numbers of CLDR\'s plural forms:
';
      var pluralRules = this.model.get('pluralRules');
      for(var rule in pluralRules) {
        exampleText += '' + rule + ': ' + pluralRules[rule].example.slice(0, 4).join(', ') + ' ';
      }
      this.$messageText.removeClass('has-error').html(exampleText);
    },

    /**
     * Add select formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addSelectFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', select , other {message-other}}';
      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html('Please add the missing cases in your select format.');
    },

    /**
     * Add select ordinal formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addSelectordinalFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', selectordinal,';
      var keywords =  Object.keys(this.model.get('ordinalRules'));
      for(var index = 0; index < keywords.length; index++) {
        text +=  ' ' + keywords[index] + ' {message-' + keywords[index] + '}';
      }
      text += '}';
      this._replaceTextSelectionWithText(text);
      var exampleText = 'Example numbers of CLDR\'s ordinal forms:
';
      var ordinalRules = this.model.get('ordinalRules');
      for(var rule in ordinalRules) {
        exampleText += '' + rule + ': ' + ordinalRules[rule].example.slice(0, 4).join(', ') + ' ';
      }
      this.$messageText.removeClass('has-error').html(exampleText);
    },

    /**
     * Add number formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addNumberFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', number, integer}'
      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(
        'You can provide integer, percentage, permille and a decimal pattern ' +
        'as an argument. For greater customization please use decimal pattern.'
      );
    },

    /**
     * Add number formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addCurrencyFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', currency, local, symbol}'
      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(
        'Arguments should be a local(context) symbol(type) for $
' +
        'Arguments should be a global(context) symbol(type) for US$
' +
        'Arguments should be a local(context) text(type) for dollar
' +
        'Arguments should be a global(context) text(type) for US dollar
'
      );
    },

    /**
     * Set elements
     *
     * @return {void}
     * @api private
     */

    _setElements: function() {
      this.$region = $('[data-region=localization]');
      this.$textArea = this.$('.localization-textarea-real');
      this.$textAreaMirror = this.$('.localization-textarea-mirror');
      this.$textAreaHeightHelper = this.$('.localization-textarea-height-helper');
      this.$addPluralButton = this.$('.localization-action-plural');
      this.$addSelectButton = this.$('.localization-action-select');
      this.$addSelectordinalButton = this.$('.localization-action-selectordinal');
      this.$addNumberButton = this.$('.localization-action-number');
      this.$addCurrencyButton = this.$('.localization-action-currency');
      this.$messageText = this.$('.localization-message-text');
      this.$buttons = this.$('.localization-buttons');
      this.$save = this.$('.localization-save');
      this.$loadingCanvas = this.$('.localization-loading-canvas');
      this.$variable = this.$('.localization-variable');
    },

    /**
     * Save
     *
     * @delegate
     */

    _save: function(event) {
      var _this = this;

      if(Date.now() - this._lastSave < 1000) {
        return;
      }

      this._lastSave = Date.now();

      var sinusWave = new SinusWave;
      sinusWave.setCanvas(this.$loadingCanvas[0]);
      sinusWave.start();
      minTimer.start(1000);
      this.$buttons.removeClass('is-revealed').addClass('is-hidden');

      event.preventDefault();
      this.model.save(null, {
        success: function(model, response, options) {
          minTimer.end(function() {
            sinusWave.stop();
            _this.$buttons.removeClass('is-hidden').addClass('is-revealed');
            _this.$messageText.removeClass('has-error').html(_this.model.get('message'));
          });
        },
        error: function(model, response, options) {
          minTimer.end(function() {
            sinusWave.stop();
            _this.$buttons.removeClass('is-hidden').addClass('is-revealed');
            _this.$messageText.addClass('has-error').html(response);
          });
        }
      });
    },

    /**
     * To HTML
     *
     * @return {void}
     * @api public
     */

    toHTML: function() {
      var _this = this
        , html = ''
        , values = ''
        , json = this.model.toJSON();

      json.values = values;

      html += template['Localization'](json);

      return html;
    },

    /**
     * On finish rendering callback
     *
     * @return {void}
     * @handler
     * @api public
     */

    onFinishRendering: function() {
      var _this = this;

      this.$region.show();

      setTimeout(function() {
        _this.$region.removeClass('is-hidden').addClass('is-revealed');
      }, 300);
    },

    /**
     * Determine whether to render or not
     *
     * @return {String}
     * @api public
     * @autocalled
     */

    should: function(path) {
      return 'update';
    }
  });
});
<commit_msg>Adds references in docmentation<commit_after>
if(typeof define !== 'function') {
  var define = require('amdefine')(module);
}

define(function(require) {
  var View = inServer ? require('../../libraries/View') : require('View')
    , template = inServer ? content_appTemplates : require('contentTemplates')
    , _ = require('underscore');

  if(inClient) {
    var minTimer = require('minTimer')
      , SinusWave = require('../../libraries/client/SinusWave');
  }

  return View.extend({

    /**
     * Initializer
     *
     * @return {void}
     * @api public
     */

    initialize: function(model) {
      this.model = model;
      if(inClient) {
        this._lastSave = Date.now();
        this._bindMethods();
      }
    },

    /**
     * Bind view
     *
     * @return {void}
     * @api private
     */

    bindDOM: function() {
      this._setElements();
      this._addDesktopInteractions();
    },

    /**
     * Bind methods
     *
     * @return {void}
     * @api private
     */

    _bindMethods: function() {
      _.bindAll(
        this,
        '_save',
        '_syncValue',
        '_delayedResize',
        '_resizeTextArea',
        '_addPluralFormatedText',
        '_addSelectFormatedText',
        '_addSelectordinalFormatedText',
        '_addNumberFormatedText',
        '_addCurrencyFormatedText',
        '_addVariableFormatedText'
      );
    },

    /**
     * Resize textarea
     *
     * @api private
     * @handler
     */

    _resizeTextArea: function(event) {
      if(typeof event !== 'undefined' && event.keyCode === 8) {
        this.$textAreaMirror.val('');
      }
      else if(typeof event !== 'undefined' && event.keyCode === 13) {
        this.$textAreaMirror.val(this.$textArea.val() + '\nsuffix');
      }
      else {
        this.$textAreaMirror.val(this.$textArea.val() + 'suffix');
      }
      var height = this.$textAreaMirror[0].scrollHeight -
        parseInt(this.$textAreaMirror.css('padding-top').replace('px', ''), 10) -
        parseInt(this.$textAreaMirror.css('padding-bottom').replace('px', ''), 10);
      this.$textArea.css('height', height + 'px');
    },

    /**
     * Delay resize
     *
     * @api private
     * @handler
     */

    _delayedResize: function() {
      _.defer(this._resizeTextArea);
    },

    /**
     * Syn value with model
     *
     * @return {void}
     * @api private
     * @handler
     */

    _syncValue: function() {
      this.model.set('value', this.$textArea.val());
    },

    /**
     * Add desktop listeners
     *
     * @return {void}
     * @api private
     */

    _addDesktopInteractions: function() {
      this.$('[disabled]').removeAttr('disabled');
      this.$el.on('click', '.save', this._save);
      this.$textArea.on('change', this._resizeTextArea);
      this.$textArea.on('change', this._syncValue);
      this.$textArea.on('cut', this._resizeTextArea);
      this.$textArea.on('paste', this._resizeTextArea);
      this.$textArea.on('drop', this._resizeTextArea);
      this.$textArea.on('keydown', this._resizeTextArea);
      this.$addPluralButton.on('mousedown', this._addPluralFormatedText);
      this.$addSelectButton.on('mousedown', this._addSelectFormatedText);
      this.$addSelectordinalButton.on('mousedown', this._addSelectordinalFormatedText);
      this.$addNumberButton.on('mousedown', this._addNumberFormatedText);
      this.$addCurrencyButton.on('mousedown', this._addCurrencyFormatedText);
      this.$variable.on('mousedown', this._addVariableFormatedText);
      this.$save.on('mouseup', this._save);
      this._resizeTextArea();
    },

    /**
     * Replace selected text in textarea with text
     *
     * @param {String} text
     * @return {void}
     * @api private
     */

    _replaceTextSelectionWithText: function(text) {
      var _this = this
        , string = this.$textArea.val()
        , start = this.$textArea[0].selectionStart
        , end = this.$textArea[0].selectionEnd
        , startString = string.substring(0, start)
        , endString = string.substring(end, string.length);

      this.$textArea.val(startString + text + endString);
      this._syncValue();
      this._resizeTextArea();

      setTimeout(function() {
        if(_this.$textArea[0].createTextRange) {
          var range = _this.$textArea[0].createTextRange();
          range.move('character', end);
          range.select();
        }
        else {
          if(_this.$textArea[0].selectionStart) {
            _this.$textArea[0].focus();
            _this.$textArea[0].setSelectionRange(start, text.length + start);
          }
          else {
            _this.$textArea[0].focus();
          }
        }
      }, 0);
    },

    /**
     * Add variable formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addVariableFormatedText: function(event) {
      var string = this.$textArea.val()
        , start = this.$textArea[0].selectionStart
        , end = this.$textArea[0].selectionEnd
        , startString = string.charAt(start - 1)
        , variable = event.currentTarget.getAttribute('data-value')
        , text;

      if(startString === '{') {
        text = event.currentTarget.getAttribute('data-value');
      }
      else {
        text = '{' + event.currentTarget.getAttribute('data-value') + '}';
      }

      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(_this.model.get('message'));
    },

    /**
     * Add plural formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addPluralFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', plural,';
      var keywords =  Object.keys(this.model.get('pluralRules'));
      for(var index = 0; index < keywords.length; index++) {
        text +=  ' ' + keywords[index] + ' {message-' + keywords[index] + '}';
      }
      text += '}';
      this._replaceTextSelectionWithText(text);
      var exampleText = 'Example numbers of CLDR\'s plural forms:
';
      var pluralRules = this.model.get('pluralRules');
      for(var rule in pluralRules) {
        exampleText += '' + rule + ': ' + pluralRules[rule].example.slice(0, 4).join(', ') + ' ';
      }
      exampleText += '
';
      exampleText += 'For more info please checkout our docs.';
      this.$messageText.removeClass('has-error').html(exampleText);
    },

    /**
     * Add select formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addSelectFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', select , other {message-other}}';
      this._replaceTextSelectionWithText(text);
      var exampleText = 'Please add the missing cases in your select format.';
      exampleText += '
';
      exampleText += 'For more info please checkout our docs.';
      this.$messageText.removeClass('has-error').html(exampleText);
    },

    /**
     * Add select ordinal formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addSelectordinalFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', selectordinal,';
      var keywords =  Object.keys(this.model.get('ordinalRules'));
      for(var index = 0; index < keywords.length; index++) {
        text +=  ' ' + keywords[index] + ' {message-' + keywords[index] + '}';
      }
      text += '}';
      this._replaceTextSelectionWithText(text);
      var exampleText = 'Example numbers of CLDR\'s ordinal forms:
';
      var ordinalRules = this.model.get('ordinalRules');
      for(var rule in ordinalRules) {
        exampleText += '' + rule + ': ' + ordinalRules[rule].example.slice(0, 4).join(', ') + ' ';
      }
      exampleText += '
';
      exampleText += 'For more info please checkout our docs.';
      this.$messageText.removeClass('has-error').html(exampleText);
    },

    /**
     * Add number formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addNumberFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', number, integer}'
      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(
        'You can provide integer, percentage, permille and a decimal pattern ' +
        'as an argument. For greater customization please use decimal pattern.'
      );
    },

    /**
     * Add number formated text to textarea
     *
     * @return {void}
     * @api private
     */

    _addCurrencyFormatedText: function() {
      var variable = 'variable';
      if(this.model.get('variables').length === 1) {
        variable = this.model.get('variables')[0];
      }
      var text = '{' + variable + ', currency, local, symbol}'
      this._replaceTextSelectionWithText(text);
      this.$messageText.removeClass('has-error').html(
        'Arguments should be a local(context) symbol(type) for $
' +
        'Arguments should be a global(context) symbol(type) for US$
' +
        'Arguments should be a local(context) text(type) for dollar
' +
        'Arguments should be a global(context) text(type) for US dollar
' +
        'For more info please checkout our docs.'
      );
    },

    /**
     * Set elements
     *
     * @return {void}
     * @api private
     */

    _setElements: function() {
      this.$region = $('[data-region=localization]');
      this.$textArea = this.$('.localization-textarea-real');
      this.$textAreaMirror = this.$('.localization-textarea-mirror');
      this.$textAreaHeightHelper = this.$('.localization-textarea-height-helper');
      this.$addPluralButton = this.$('.localization-action-plural');
      this.$addSelectButton = this.$('.localization-action-select');
      this.$addSelectordinalButton = this.$('.localization-action-selectordinal');
      this.$addNumberButton = this.$('.localization-action-number');
      this.$addCurrencyButton = this.$('.localization-action-currency');
      this.$messageText = this.$('.localization-message-text');
      this.$buttons = this.$('.localization-buttons');
      this.$save = this.$('.localization-save');
      this.$loadingCanvas = this.$('.localization-loading-canvas');
      this.$variable = this.$('.localization-variable');
    },

    /**
     * Save
     *
     * @delegate
     */

    _save: function(event) {
      var _this = this;

      if(Date.now() - this._lastSave < 1000) {
        return;
      }

      this._lastSave = Date.now();

      var sinusWave = new SinusWave;
      sinusWave.setCanvas(this.$loadingCanvas[0]);
      sinusWave.start();
      minTimer.start(1000);
      this.$buttons.removeClass('is-revealed').addClass('is-hidden');

      event.preventDefault();
      this.model.save(null, {
        success: function(model, response, options) {
          minTimer.end(function() {
            sinusWave.stop();
            _this.$buttons.removeClass('is-hidden').addClass('is-revealed');
            _this.$messageText.removeClass('has-error').html(_this.model.get('message'));
          });
        },
        error: function(model, response, options) {
          minTimer.end(function() {
            sinusWave.stop();
            _this.$buttons.removeClass('is-hidden').addClass('is-revealed');
            _this.$messageText.addClass('has-error').html(response);
          });
        }
      });
    },

    /**
     * To HTML
     *
     * @return {void}
     * @api public
     */

    toHTML: function() {
      var _this = this
        , html = ''
        , values = ''
        , json = this.model.toJSON();

      json.values = values;

      html += template['Localization'](json);

      return html;
    },

    /**
     * On finish rendering callback
     *
     * @return {void}
     * @handler
     * @api public
     */

    onFinishRendering: function() {
      var _this = this;

      this.$region.show();

      setTimeout(function() {
        _this.$region.removeClass('is-hidden').addClass('is-revealed');
      }, 300);
    },

    /**
     * Determine whether to render or not
     *
     * @return {String}
     * @api public
     * @autocalled
     */

    should: function(path) {
      return 'update';
    }
  });
});
<|endoftext|>


<commit_before>var assert = require('assert'),
    Promise = require('bluebird'),
    _ = require('lodash');

describe('Association Interface', function() {

  describe('n:m association :: .update()', function() {
    describe('update nested associations()', function() {

      describe('with single level depth', function() {

        describe('when association doesn\'t exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            Associations.Driver.create({ name: 'm:m update nested' }).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should create a new taxi and associate it with a driver', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: [
                { medallion: 1 }
              ]
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 1);
                assert(model.taxis[0].medallion === 1);
                done();
              });

            });
          });
        });

        describe('when associations already exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            var data = {
              name: 'm:m update nested',
              taxis: [
                { medallion: 1 },
                { medallion: 2 }
              ]
            };

            Associations.Driver.create(data).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should reset associations with the updated associations', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: [
                { medallion: 3 },
                { medallion: 4 },
                { medallion: 5 }
              ]
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .sort('taxis')
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 3);
                assert(model.taxis[0].medallion === 3);
                assert(model.taxis[1].medallion === 4);
                assert(model.taxis[2].medallion === 5);
                done();
              });

            });
          });
        });

        describe('when associations have primary keys', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver, Taxis;

          before(function(done) {

            var taxiData = [
              { medallion: 1 },
              { medallion: 2 }
            ];

            var data = {
              name: 'm:m update nested',
              taxis: [
                { medallion: 100 },
                { medallion: 200 }
              ]
            };

            Associations.Taxi.create(taxiData).exec(function(err, taxis) {
              if(err) return done(err);
              Taxis = taxis;

              Associations.Driver.create(data).exec(function(err, driver) {
                if(err) return done(err);
                Driver = driver;
                done();
              });
            });
          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should update association values', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: Taxis.map(function(taxi) { return taxi.toObject(); })
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 2);

                // Ensure association values were updated
                assert(model.taxis[0].medallion === 1);
                assert(model.taxis[1].medallion === 2);

                done();
              });

            });
          });

        });

        describe('when associations already exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            var data = {
              name: 'm:m update nested save',
              taxis: [ { medallion: 1000 } ]
            };

            Associations.Driver.create(data).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should update association values with save()', function(done) {

            Associations.Driver.findOne({ id: Driver.id })
            .populate('taxis')
            .exec(function(err, model) {
              var taxi = model.taxis[0];
              taxi.medallion = 1001;
              taxi.save(function(err){
                assert(!err);

                Associations.Driver.findOne({ id: Driver.id })
                .populate('taxis')
                .exec(function(err, model) {
                  assert(!err);
                  assert(model.taxis.length === 1);
                  assert(model.taxis[0].medallion === 1001);
                  done();
                });
              });

            });
          });
        });

        describe('when associations are sync\'ed rapidly', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Drivers;
          var Taxis;

          before(function(done) {

            var driversData = [
              {name: "Rapid 0"},
              {name: "Rapid 1"},
              {name: "Rapid 2"},
              {name: "Rapid 3"}
            ];

            var taxisData = [
              {medallion: 200},
              {medallion: 201},
              {medallion: 202},
              {medallion: 203}
            ];

            Associations.Driver.createEach(driversData).exec(function(err, drivers) {
              if(err) return done(err);
              Drivers = drivers;

              Associations.Taxi.createEach(taxisData).exec(function(err, taxis) {
                if(err) return done(err);
                Taxis = taxis;

                done();
              });

            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should create the correct associations', function(done) {

            // Construct promises
            var associationMap = {};
            var associationPromises = [];

            var driverId, taxiId;
            for (var i = 0, il = Drivers.length; i < il; ++i) {

              driverId = _.findWhere(Drivers, {name: "Rapid " + i}).id;
              taxiId   = _.findWhere(Taxis,   {medallion: 200 + i}).id;

              // Set map for testing later.  Maps driver to taxi being associated.
              associationMap[driverId] = taxiId;

              associationPromises.push(
                Associations.Driver.update(driverId, {taxis: [{id: taxiId}]})
              );
            }

            // Perform all updates at once
            Promise.all(associationPromises)
            .then(function(results){

              // Check to see if associations were created as expected
              Associations.Driver.find({id: _.map(Drivers, 'id')})
              .populate('taxis')
              .exec(function(err, drivers) {

                if (err) return done(err);

                assert.equal(drivers.length, Drivers.length);

                // Ensure the correct operations happened per record using associationMap
                var driver;
                for (var i = 0, il = drivers.length; i < il; ++i) {

                  driver = drivers[i];

                  assert(typeof driver === "object" && driver !== null);
                  assert(Array.isArray(driver.taxis));
                  assert.equal(driver.taxis.length, 1);
                  assert.equal(driver.taxis[0].id, associationMap[driver.id]);
                }

                done();

              });

            })
            .catch(done);

          });
        });

      });
    });
  });
});
<commit_msg>Nested update: output error if assertion fails<commit_after>var assert = require('assert'),
    Promise = require('bluebird'),
    _ = require('lodash');

describe('Association Interface', function() {

  describe('n:m association :: .update()', function() {
    describe('update nested associations()', function() {

      describe('with single level depth', function() {

        describe('when association doesn\'t exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            Associations.Driver.create({ name: 'm:m update nested' }).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should create a new taxi and associate it with a driver', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: [
                { medallion: 1 }
              ]
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 1);
                assert(model.taxis[0].medallion === 1);
                done();
              });

            });
          });
        });

        describe('when associations already exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            var data = {
              name: 'm:m update nested',
              taxis: [
                { medallion: 1 },
                { medallion: 2 }
              ]
            };

            Associations.Driver.create(data).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should reset associations with the updated associations', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: [
                { medallion: 3 },
                { medallion: 4 },
                { medallion: 5 }
              ]
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .sort('taxis')
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err, 'Error: ' + err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 3);
                assert(model.taxis[0].medallion === 3);
                assert(model.taxis[1].medallion === 4);
                assert(model.taxis[2].medallion === 5);
                done();
              });

            });
          });
        });

        describe('when associations have primary keys', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver, Taxis;

          before(function(done) {

            var taxiData = [
              { medallion: 1 },
              { medallion: 2 }
            ];

            var data = {
              name: 'm:m update nested',
              taxis: [
                { medallion: 100 },
                { medallion: 200 }
              ]
            };

            Associations.Taxi.create(taxiData).exec(function(err, taxis) {
              if(err) return done(err);
              Taxis = taxis;

              Associations.Driver.create(data).exec(function(err, driver) {
                if(err) return done(err);
                Driver = driver;
                done();
              });
            });
          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should update association values', function(done) {

            var taxiData = {
              name: 'm:m update nested - updated',
              taxis: Taxis.map(function(taxi) { return taxi.toObject(); })
            };

            Associations.Driver.update({ id: Driver.id }, taxiData).exec(function(err, values) {
              assert(!err);

              // Look up the driver again to be sure the taxis were added
              Associations.Driver.findOne(values[0].id)
              .populate('taxis')
              .exec(function(err, model) {
                assert(!err);
                assert(model.name === 'm:m update nested - updated');
                assert(model.taxis.length === 2);

                // Ensure association values were updated
                assert(model.taxis[0].medallion === 1);
                assert(model.taxis[1].medallion === 2);

                done();
              });

            });
          });

        });

        describe('when associations already exist', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Driver;

          before(function(done) {

            var data = {
              name: 'm:m update nested save',
              taxis: [ { medallion: 1000 } ]
            };

            Associations.Driver.create(data).exec(function(err, driver) {
              if(err) return done(err);
              Driver = driver;
              done();
            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should update association values with save()', function(done) {

            Associations.Driver.findOne({ id: Driver.id })
            .populate('taxis')
            .exec(function(err, model) {
              var taxi = model.taxis[0];
              taxi.medallion = 1001;
              taxi.save(function(err){
                assert(!err);

                Associations.Driver.findOne({ id: Driver.id })
                .populate('taxis')
                .exec(function(err, model) {
                  assert(!err);
                  assert(model.taxis.length === 1);
                  assert(model.taxis[0].medallion === 1001);
                  done();
                });
              });

            });
          });
        });

        describe('when associations are sync\'ed rapidly', function() {

          /////////////////////////////////////////////////////
          // TEST SETUP
          ////////////////////////////////////////////////////

          var Drivers;
          var Taxis;

          before(function(done) {

            var driversData = [
              {name: "Rapid 0"},
              {name: "Rapid 1"},
              {name: "Rapid 2"},
              {name: "Rapid 3"}
            ];

            var taxisData = [
              {medallion: 200},
              {medallion: 201},
              {medallion: 202},
              {medallion: 203}
            ];

            Associations.Driver.createEach(driversData).exec(function(err, drivers) {
              if(err) return done(err);
              Drivers = drivers;

              Associations.Taxi.createEach(taxisData).exec(function(err, taxis) {
                if(err) return done(err);
                Taxis = taxis;

                done();
              });

            });

          });

          /////////////////////////////////////////////////////
          // TEST METHODS
          ////////////////////////////////////////////////////

          it('should create the correct associations', function(done) {

            // Construct promises
            var associationMap = {};
            var associationPromises = [];

            var driverId, taxiId;
            for (var i = 0, il = Drivers.length; i < il; ++i) {

              driverId = _.findWhere(Drivers, {name: "Rapid " + i}).id;
              taxiId   = _.findWhere(Taxis,   {medallion: 200 + i}).id;

              // Set map for testing later.  Maps driver to taxi being associated.
              associationMap[driverId] = taxiId;

              associationPromises.push(
                Associations.Driver.update(driverId, {taxis: [{id: taxiId}]})
              );
            }

            // Perform all updates at once
            Promise.all(associationPromises)
            .then(function(results){

              // Check to see if associations were created as expected
              Associations.Driver.find({id: _.map(Drivers, 'id')})
              .populate('taxis')
              .exec(function(err, drivers) {

                if (err) return done(err);

                assert.equal(drivers.length, Drivers.length);

                // Ensure the correct operations happened per record using associationMap
                var driver;
                for (var i = 0, il = drivers.length; i < il; ++i) {

                  driver = drivers[i];

                  assert(typeof driver === "object" && driver !== null);
                  assert(Array.isArray(driver.taxis));
                  assert.equal(driver.taxis.length, 1);
                  assert.equal(driver.taxis[0].id, associationMap[driver.id]);
                }

                done();

              });

            })
            .catch(done);

          });
        });

      });
    });
  });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Dimensions,
  Button,
  ActivityIndicator,
  Image,
  ListView
} from 'react-native';
// import Camera from 'react-native-camera';

// PARENT

export default class FoodrFrontend extends Component {
  constructor() {
    super()
    this.state = {
      currentPage: 'ProductPage',
      previousPage: 'DefaultPage',

      foundProduct: {
        "found": true,
        "product": {
            "id": 1,
            "upc": "03077504",
            "name": "Quaker Chewy Chocolate Chip Bar",
            "score": 3,
            "img_url": "https://images-na.ssl-images-amazon.com/images/I/81wqeA8l9CL._SL1500_.jpg",
            "created_at": "2017-07-29T02:12:28.868Z",
            "updated_at": "2017-07-29T02:12:28.868Z"
          },
        "ingredients": [
            {
                "id": 1,
                "name": "Pumpkin Seed",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.909Z",
                "updated_at": "2017-07-29T02:12:28.909Z"
            },
            {
                "id": 3,
                "name": "Blue Swimmer Crab",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.913Z",
                "updated_at": "2017-07-29T02:12:28.913Z"
            },
            {
                "id": 5,
                "name": "White rice",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.917Z",
                "updated_at": "2017-07-29T02:12:28.917Z"
            }
        ]
      },
      loggedIn: false,
      userId: false,
      searchTerm: ''
    }
    this.updateCurrentPage = this.updateCurrentPage.bind(this)
    this.searchProduct = this.searchProduct.bind(this)
  }

  searchProduct(upc) {
    this.updateCurrentPage('SearchingPage')

    fetch('https://dbc-foodr-api.herokuapp.com/products/' + upc + "?user_id=" + this.state.userId)
    // fetch('http://localhost:3000/products/' + upc + "?user_id=" + this.state.userId)
    .then((data) => data.json())
    .then((jsonData) => {
      this.setState({ foundProduct: jsonData })
      if (this.state.foundProduct.found) {
        this.updateCurrentPage('ProductPage')
      } else {
        this.updateCurrentPage('NoResultsPage')
      }
    });
  }

  updateCurrentPage(pageName) {
    this.setState({currentPage: pageName})
  }

  render() {
    switch(this.state.currentPage) {
      case 'IndexPage':
        return(
          <IndexPage />
        )
      case 'SearchPage':
        return(
          <SearchPage />
        )
      case 'CameraPage':
        return(
          <CameraPage
            searchProduct = {this.searchProduct}
          />
        )
      case 'ProductPage':
        return(
          <ProductPage
            foundProduct = {this.state.foundProduct}
            updateCurrentPage = {this.updateCurrentPage}
          />
        )
      case 'IngredientPage':
        return(
          <IngredientPage  />
        )
      case 'NoResultsPage':
        return(
          <NoResultsPage
            updateCurrentPage = {this.updateCurrentPage}
          />
        )
      case 'SearchingPage':
        return(
          <SearchingPage />
        )
      default:
        return(
          <DefaultPage />
        )
    }
  }
}

// CHILDREN

// XANDER

class LayoutPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Layout Page</Text>
      </View>
    )
  }
}

// VICTORIA

class CameraPage extends Component {
  constructor() {
    super()
    this.onBarCodeRead = this.onBarCodeRead.bind(this)
    this.existingItem = this.existingItem.bind(this)
    this.nonExistingItem = this.nonExistingItem.bind(this)
  }

  onBarCodeRead(e) {
    this.props.searchProduct(e.data)
  }

  // for testing
  existingItem() {
    this.props.searchProduct('03077504')
  }

  nonExistingItem() {
    this.props.searchProduct('asdf')
  }

  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>Place Barcode in View</Text>
        <Camera
          ref={(cam) => {this.camera = cam;}}
          style={styles.preview}
          onBarCodeRead = {this.onBarCodeRead}
          aspect={Camera.constants.Aspect.fill}>
        </Camera>
        <Text style={styles.instructions}>The camera will automatically detect when a barcode is present</Text>

        <Text style={styles.capture} onPress={this.existingItem}>[EXISTING ITEM]</Text>
        <Text style={styles.capture} onPress={this.nonExistingItem}>[NONEXISTING ITEM]</Text>
      </View>
    );
  }
}

// TIFF

class ProductPage extends Component {
  constructor() {
    super()
    this.goToCamera = this.goToCamera.bind(this)
    this.goToSearch = this.goToSearch.bind(this)
  }

  goToCamera () {
    this.props.updateCurrentPage('CameraPage')
  }

  goToSearch() {
    this.props.updateCurrentPage('SearchPage')
  }

  scoreConverter() {
    let numberScore = this.props.foundProduct.product.score
      switch(numberScore) {
        case 5:
          return ('A');
        case 4:
          return ('B');
        case 3:
          return ('C');
        case 2:
          return ('D');
        case 1:
          return ('F');
        default:
          ('Not Found');
      }
  }

  render() {
    return(
      <View style={styles.container}>
        <Image
          style={{width: 375, height: 200}}
        source={{uri: this.props.foundProduct.product.img_url}}
        />
        <Text style={styles.welcome}>{this.props.foundProduct.product.name}</Text>
        <Text>Score: {this.scoreConverter()}</Text>

        <IngredientList foundProduct={this.props.foundProduct} />
      </View>
    )
  }
}

class IngredientList extends Component {
  state = { ingredients: [] };

  renderIngredient() {
    return this.props.foundProduct.ingredients.map(ingredient =>
      <Text>{ingredient.name}</Text>);
  }

  render () {
    return (
      <View>
        <Text>Ingredients</Text>
        {this.renderIngredient()}
      </View>
    );
  }
}

// KANAN

class NoResultsPage extends Component {
  constructor() {
    super()
    this.goToCamera = this.goToCamera.bind(this)
    this.goToSearch = this.goToSearch.bind(this)
  }

  goToCamera () {
    this.props.updateCurrentPage('CameraPage')
  }

  goToSearch() {
    this.props .updateCurrentPage('SearchPage')
  }

  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>No Products Found</Text>
        <Text>Sorry we do not have that item in our database{"\n\n"}</Text>
        <Button
            onPress={this.goToCamera}
            title="Scan Another Item"
          />
        <Text>or</Text>
        <Button
            onPress={this.goToSearch}
            title="Search for an Item"
          />
      </View>
    )
  }
}

// KANAN

class SearchPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Search Page</Text>
      </View>
    );
  }
}

// KANAN

class IndexPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Index Page</Text>
      </View>
    );
  }
}

// DONE

class SearchingPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <ActivityIndicator
          animating = {true}
          size = 'large'
          style={styles.activityIndicator}
        />
        <Text>Searching...</Text>
      </View>
    );
  }
}

// FOR TESTING

class DefaultPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>Default Page</Text>
        <Text>The case statement hit default</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    margin: 10,
  },
  preview: {
    height: 300,
    width: Dimensions.get('window').width
  },
  activityIndicator: {
    marginBottom: 20,
  },
  // for testing
  capture: {
    flex: 0,
    backgroundColor: 'lightblue',
    borderRadius: 5,
    padding: 10,
    margin: 5
  },

});

AppRegistry.registerComponent('FoodrFrontend', () => FoodrFrontend);
<commit_msg>Add modal to display each ingredient of a product<commit_after>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Dimensions,
  Button,
  ActivityIndicator,
  Image,
  Modal,
  TouchableHighlight
} from 'react-native';
// import Camera from 'react-native-camera';

// PARENT

export default class FoodrFrontend extends Component {
  constructor() {
    super()
    this.state = {
      currentPage: 'ProductPage',
      previousPage: 'DefaultPage',

      foundProduct: {
        "found": true,
        "product": {
            "id": 1,
            "upc": "03077504",
            "name": "Quaker Chewy Chocolate Chip Bar",
            "score": 3,
            "img_url": "https://images-na.ssl-images-amazon.com/images/I/81wqeA8l9CL._SL1500_.jpg",
            "created_at": "2017-07-29T02:12:28.868Z",
            "updated_at": "2017-07-29T02:12:28.868Z"
          },
        "ingredients": [
            {
                "id": 1,
                "name": "Pumpkin Seed",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.909Z",
                "updated_at": "2017-07-29T02:12:28.909Z"
            },
            {
                "id": 3,
                "name": "Blue Swimmer Crab",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.913Z",
                "updated_at": "2017-07-29T02:12:28.913Z"
            },
            {
                "id": 5,
                "name": "White rice",
                "description": "this is healthy",
                "img_url": null,
                "created_at": "2017-07-29T02:12:28.917Z",
                "updated_at": "2017-07-29T02:12:28.917Z"
            }
        ]
      },
      loggedIn: false,
      userId: false,
      searchTerm: ''
    }
    this.updateCurrentPage = this.updateCurrentPage.bind(this)
    this.searchProduct = this.searchProduct.bind(this)
  }

  searchProduct(upc) {
    this.updateCurrentPage('SearchingPage')

    fetch('https://dbc-foodr-api.herokuapp.com/products/' + upc + "?user_id=" + this.state.userId)
    // fetch('http://localhost:3000/products/' + upc + "?user_id=" + this.state.userId)
    .then((data) => data.json())
    .then((jsonData) => {
      this.setState({ foundProduct: jsonData })
      if (this.state.foundProduct.found) {
        this.updateCurrentPage('ProductPage')
      } else {
        this.updateCurrentPage('NoResultsPage')
      }
    });
  }

  updateCurrentPage(pageName) {
    this.setState({currentPage: pageName})
  }

  render() {
    switch(this.state.currentPage) {
      case 'IndexPage':
        return(
          <IndexPage />
        )
      case 'SearchPage':
        return(
          <SearchPage />
        )
      case 'CameraPage':
        return(
          <CameraPage
            searchProduct = {this.searchProduct}
          />
        )
      case 'ProductPage':
        return(
          <ProductPage
            foundProduct = {this.state.foundProduct}
            updateCurrentPage = {this.updateCurrentPage}
          />
        )
      case 'IngredientPage':
        return(
          <IngredientPage  />
        )
      case 'NoResultsPage':
        return(
          <NoResultsPage
            updateCurrentPage = {this.updateCurrentPage}
          />
        )
      case 'SearchingPage':
        return(
          <SearchingPage />
        )
      default:
        return(
          <DefaultPage />
        )
    }
  }
}

// CHILDREN

// XANDER

class LayoutPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Layout Page</Text>
      </View>
    )
  }
}

// VICTORIA

class CameraPage extends Component {
  constructor() {
    super()
    this.onBarCodeRead = this.onBarCodeRead.bind(this)
    this.existingItem = this.existingItem.bind(this)
    this.nonExistingItem = this.nonExistingItem.bind(this)
  }

  onBarCodeRead(e) {
    this.props.searchProduct(e.data)
  }

  // for testing
  existingItem() {
    this.props.searchProduct('03077504')
  }

  nonExistingItem() {
    this.props.searchProduct('asdf')
  }

  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>Place Barcode in View</Text>
        <Camera
          ref={(cam) => {this.camera = cam;}}
          style={styles.preview}
          onBarCodeRead = {this.onBarCodeRead}
          aspect={Camera.constants.Aspect.fill}>
        </Camera>
        <Text style={styles.instructions}>The camera will automatically detect when a barcode is present</Text>

        <Text style={styles.capture} onPress={this.existingItem}>[EXISTING ITEM]</Text>
        <Text style={styles.capture} onPress={this.nonExistingItem}>[NONEXISTING ITEM]</Text>
      </View>
    );
  }
}

// TIFF

class ProductPage extends Component {
  constructor() {
    super()
    this.goToCamera = this.goToCamera.bind(this)
    this.goToSearch = this.goToSearch.bind(this)
  }

  goToCamera () {
    this.props.updateCurrentPage('CameraPage')
  }

  goToSearch() {
    this.props.updateCurrentPage('SearchPage')
  }

  scoreConverter() {
    let numberScore = this.props.foundProduct.product.score
      switch(numberScore) {
        case 5:
          return ('A');
        case 4:
          return ('B');
        case 3:
          return ('C');
        case 2:
          return ('D');
        case 1:
          return ('F');
        default:
          ('Not Found');
      }
  }

  renderIngredient() {
    return this.props.foundProduct.ingredients.map(ingredient =>
      <IngredientModal ingredient = {ingredient} />
    );
  }

  render() {
    return(
      <View style={styles.container}>
        <Image
          style={{width: 375, height: 200}}
        source={{uri: this.props.foundProduct.product.img_url}}
        />
        <Text style={styles.welcome}>{this.props.foundProduct.product.name}</Text>
        <Text>Score: {this.scoreConverter()}</Text>

        <Text>Ingredients:</Text>
        {this.renderIngredient()}
      </View>
    )
  }
}

class IngredientModal extends Component {
  constructor() {
    super()
    this.state = {
      modalVisible: false
    }
    this.setModalVisible = this.setModalVisible.bind(this)
  }

  setModalVisible(visible) {
    this.setState({modalVisible: visible});
  }

  render() {
    return (
      <View style={{marginTop: 22}}>

        <Modal
          animationType={"slide"}
          transparent={false}
          visible={this.state.modalVisible}
          onRequestClose={() => {alert("Modal has been closed.")}}
          >
          <View style={styles.container}>
            <Text style={styles.welcome}>{this.props.ingredient.name}</Text>

            <TouchableHighlight onPress={() => {this.setModalVisible(!this.state.modalVisible)}}>
              <Text>Hide Modal</Text>
            </TouchableHighlight>

          </View>
        </Modal>

        <TouchableHighlight onPress={() => {this.setModalVisible(true)}}>
          <Text>{this.props.ingredient.name}</Text>
        </TouchableHighlight>

      </View>
    );
  }
}

// KANAN

class NoResultsPage extends Component {
  constructor() {
    super()
    this.goToCamera = this.goToCamera.bind(this)
    this.goToSearch = this.goToSearch.bind(this)
  }

  goToCamera () {
    this.props.updateCurrentPage('CameraPage')
  }

  goToSearch() {
    this.props .updateCurrentPage('SearchPage')
  }

  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>No Products Found</Text>
        <Text>Sorry we do not have that item in our database{"\n\n"}</Text>
        <Button
            onPress={this.goToCamera}
            title="Scan Another Item"
          />
        <Text>or</Text>
        <Button
            onPress={this.goToSearch}
            title="Search for an Item"
          />
      </View>
    )
  }
}

// KANAN

class SearchPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Search Page</Text>
      </View>
    );
  }
}

// KANAN

class IndexPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text>Index Page</Text>
      </View>
    );
  }
}

// DONE

class SearchingPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <ActivityIndicator
          animating = {true}
          size = 'large'
          style={styles.activityIndicator}
        />
        <Text>Searching...</Text>
      </View>
    );
  }
}

// FOR TESTING

class DefaultPage extends Component {
  render() {
    return(
      <View style={styles.container}>
        <Text style={styles.welcome}>Default Page</Text>
        <Text>The case statement hit default</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    margin: 10,
  },
  preview: {
    height: 300,
    width: Dimensions.get('window').width
  },
  activityIndicator: {
    marginBottom: 20,
  },
  // for testing
  capture: {
    flex: 0,
    backgroundColor: 'lightblue',
    borderRadius: 5,
    padding: 10,
    margin: 5
  },

});

AppRegistry.registerComponent('FoodrFrontend', () => FoodrFrontend);
<|endoftext|>


<commit_before>import {makeFormatter} from './utils'

export default class ExtendedTimeElement extends HTMLElement {
  static get observedAttributes() {
    return ['datetime', 'day', 'format', 'hour', 'minute', 'month', 'second', 'title', 'weekday', 'year']
  }

  // Internal: Refresh the time element's formatted date when an attribute changes.
  //
  // Returns nothing.
  attributeChangedCallback(attrName, oldValue, newValue) {
    if (attrName === 'datetime') {
      const millis = Date.parse(newValue)
      this._date = isNaN(millis) ? null : new Date(millis)
    }

    const title = this.getFormattedTitle()
    if (title) {
      this.setAttribute('title', title)
    }

    const text = this.getFormattedDate()
    if (text) {
      this.textContent = text
    }
  }

  // Internal: Format the ISO 8601 timestamp according to the user agent's
  // locale-aware formatting rules. The element's existing `title` attribute
  // value takes precedence over this custom format.
  //
  // Returns a formatted time String.
  getFormattedTitle() {
    if (!this._date) {
      return
    }

    if (this.hasAttribute('title')) {
      return this.getAttribute('title')
    }

    const formatter = makeFormatter({
      day: 'numeric',
      month: 'short',
      year: 'numeric',
      hour: 'numeric',
      minute: '2-digit',
      timeZoneName: 'short'
    })

    if (formatter) {
      return formatter.format(this._date)
    } else {
      try {
        return this._date.toLocaleString()
      } catch (e) {
        if (e instanceof RangeError) {
          return this._date.toString()
        } else {
          throw e
        }
      }
    }
  }
}
<commit_msg>Avoid setting title attribute if element already has a title Fixes maximum call stack size exceeded.<commit_after>import {makeFormatter} from './utils'

export default class ExtendedTimeElement extends HTMLElement {
  static get observedAttributes() {
    return ['datetime', 'day', 'format', 'hour', 'minute', 'month', 'second', 'title', 'weekday', 'year']
  }

  // Internal: Refresh the time element's formatted date when an attribute changes.
  //
  // Returns nothing.
  attributeChangedCallback(attrName, oldValue, newValue) {
    if (attrName === 'datetime') {
      const millis = Date.parse(newValue)
      this._date = isNaN(millis) ? null : new Date(millis)
    }

    const title = this.getFormattedTitle()
    if (title && !this.hasAttribute('title')) {
      this.setAttribute('title', title)
    }

    const text = this.getFormattedDate()
    if (text) {
      this.textContent = text
    }
  }

  // Internal: Format the ISO 8601 timestamp according to the user agent's
  // locale-aware formatting rules. The element's existing `title` attribute
  // value takes precedence over this custom format.
  //
  // Returns a formatted time String.
  getFormattedTitle() {
    if (!this._date) {
      return
    }

    const formatter = makeFormatter({
      day: 'numeric',
      month: 'short',
      year: 'numeric',
      hour: 'numeric',
      minute: '2-digit',
      timeZoneName: 'short'
    })

    if (formatter) {
      return formatter.format(this._date)
    } else {
      try {
        return this._date.toLocaleString()
      } catch (e) {
        if (e instanceof RangeError) {
          return this._date.toString()
        } else {
          throw e
        }
      }
    }
  }
}
<|endoftext|>