<commit_before>/* Bank Account and Credit Card forms
 *
 * These two forms share some common wiring under the Gratipay.payments
 * namespace, and each has unique code under the Gratipay.payments.{cc,ba}
 * namespaces. Each form gets its own page so we only instantiate one of these
 * at a time.
 *
 */

Gratipay.payments = {};

// Common code
// ===========

Gratipay.payments.init = function(participantId) {
    Gratipay.participantId = participantId;
    $('#delete form').submit(Gratipay.payments.submitDeleteForm);

    // Lazily depend on Balanced.
    var balanced_js = "https://js.balancedpayments.com/1.1/balanced.min.js";
    jQuery.getScript(balanced_js, function() {
        $('input[type!="hidden"]').eq(0).focus();
    }).fail(Gratipay.error);
}

Gratipay.payments.submitDeleteForm = function(e) {
    var item = $("#payout").length ? "bank account" : "credit card";
    var slug = $("#payout").length ? "bank-account" : "credit-card";
    var msg = "Really disconnect your " + item + "?";
    if (!confirm(msg)) {
        e.stopPropagation();
        e.preventDefault();
        return false;
    }

    jQuery.ajax(
        { url: '/' + slug + '.json'
        , data: {action: "delete"}
        , type: "POST"
        , success: function() {
            window.location.href = '/' + slug + '.html';
          }
        , error: Gratipay.error
         }
    );
    return false;
};

Gratipay.payments.onError = function(response) {
    $('button#save').css('opacity', 1);
    var msg = response.status_code + ": " +
        $.map(response.errors, function(obj) { return obj.description }).join(', ');
    Gratipay.forms.showFeedback(null, [msg]);
    return msg;
};

Gratipay.payments.onSuccess = function(data) {
    $('#status').text('working').addClass('highlight');
    setTimeout(function() {
        $('#status').removeClass('highlight');
    }, 8000);
    $('#delete').show();
    Gratipay.forms.clearFeedback();
    $('button#save').css('opacity', 1);
    setTimeout(function() {
        window.location.href = '/' + Gratipay.participantId + '/';
    }, 1000);
};

// Bank Accounts
// =============

Gratipay.payments.ba = {};

Gratipay.payments.ba.init = function(participantId) {
    Gratipay.payments.init(participantId);
    $('#payout').submit(Gratipay.payments.ba.submit);
};

Gratipay.payments.ba.submit = function (e) {
    e.preventDefault();

    $('button#save').css('opacity', 0.5);
    Gratipay.forms.clearFeedback();

    var bankAccount = {
        name: $('#account_name').val(),
        account_number: $('#account_number').val(),
        routing_number: $('#routing_number').val()
    };

    Gratipay.payments.ba.merchantData = {
        //type: 'person',  // Oooh, may need to vary this some day?
        street_address: $('#address_1').val(),
        postal_code: $('#zip').val(),
        phone_number: $('#phone_number').val(),
        region: $('#state').val(),
        dob_month: $('#dob-month').val(),
        dob_year: $('#dob-year').val(),
        dob_day: $('#dob-day').val(),
        name: $('#name').val()
    };
    var errors = [];

    // Require some fields.
    // ====================
    // We only require fields that are actually on the page. Since we don't
    // load the identity verification fields if they're already verified, not
    // all of these will necessarily be present at all.

    var requiredFields = {
        name: 'Your legal name is required.',
        address_1: 'Your street address is required.',
        zip: 'Your ZIP or postal code is required.',
        phone_number: 'A phone number is required.',
        account_name: 'The name on your bank account is required.',
        account_number: 'Your bank account number is required.',
        routing_number: 'A routing number is required.'
    };
    for (var field in requiredFields) {
        var $f = $('#' + field);
        if (!$f.length)  // Only validate if it's on the page.
            continue;
        var value = $f.val();

        if (!value) {
            $f.closest('div').addClass('error');
            errors.push(requiredFields[field]);
        } else {
            $f.closest('div').removeClass('error');
        }
    }

    // Validate routing number.
    // ========================

    var $rn = $('#routing_number');
    if (bankAccount.routing_number) {
        if (!balanced.bankAccount.validateRoutingNumber(bankAccount.routing_number)) {
            $rn.closest('div').addClass('error');
            errors.push("That routing number is invalid.");
        } else {
            $rn.closest('div').removeClass('error');
        }
    }

    if (errors.length) {
        $('button#save').css('opacity', 1);
        Gratipay.forms.showFeedback(null, errors);
    } else {
        balanced.bankAccount.create( bankAccount
                                   , Gratipay.payments.ba.handleResponse
                                    );
    }
};

Gratipay.payments.ba.handleResponse = function (response) {
    if (response.status_code !== 201) {
        var msg = Gratipay.payments.onError(response);
        $.post('/bank-account.json', {action: 'store-error', msg: msg});
        return;
    }

    /* The request to tokenize the bank account succeeded. Now we need to
     * validate the merchant information. We'll submit it to
     * /bank-accounts.json and check the response code to see what's going
     * on there.
     */

    function detailedFeedback(data) {
        $('#status').text('failing');
        $('#delete').show();
        var messages = [data.error];
        if (data.problem == 'More Info Needed') {
            messages = [ "Sorry, we couldn't verify your identity. Please "
                       + "check, correct, and resubmit your details."
            ];
        }
        Gratipay.forms.showFeedback(data.problem, messages);
        $('button#save').css('opacity', 1);
    }

    var detailsToSubmit = Gratipay.payments.ba.merchantData;
    detailsToSubmit.bank_account_uri = response.bank_accounts[0].href;

    Gratipay.forms.submit( "/bank-account.json"
                       , detailsToSubmit
                       , Gratipay.payments.onSuccess
                       , detailedFeedback
                        );
};

// Credit Cards
// ============

Gratipay.payments.cc = {};

Gratipay.payments.cc.init = function(participantId) {
    Gratipay.payments.init(participantId);
    $('form#payment').submit(Gratipay.payments.cc.submit);
    Gratipay.payments.cc.formatInputs(
        $('#card_number'),
        $('#expiration_month'),
        $('#expiration_year'),
        $('#cvv')
    );
};

/* Most of the following code is taken from https://github.com/wangjohn/creditly */

Gratipay.payments.cc.formatInputs = function (cardNumberInput, expirationMonthInput, expirationYearInput, cvvInput) {
    function getInputValue(e, element) {
        var inputValue = element.val().trim();
        inputValue = inputValue + String.fromCharCode(e.which);
        return inputValue.replace(/[^\d]/g, "");
    }

    function isEscapedKeyStroke(e) {
        // Key event is for a browser shortcut
        if (e.metaKey || e.ctrlKey) return true;

        // If keycode is a space
        if (e.which === 32) return false;

        // If keycode is a special char (WebKit)
        if (e.which === 0) return true;

        // If char is a special char (Firefox)
        if (e.which < 33) return true;

        return false;
    }

    function isNumberEvent(e) {
        return (/^\d+$/.test(String.fromCharCode(e.which)));
    }

    function onlyAllowNumeric(e, maximumLength, element) {
        e.preventDefault();
        // Ensure that it is a number and stop the keypress
        if (!isNumberEvent(e)) {
            return false;
        }
        return true;
    }

    var isAmericanExpress = function(number) {
        return number.match("^(34|37)");
    };

    function shouldProcessInput(e, maximumLength, element) {
        var target = e.currentTarget;
        if (getInputValue(e, element).length > maximumLength) {
          e.preventDefault();
          return false;
        }
        if ((target.selectionStart !== target.value.length)) {
          return false;
        }
        return (!isEscapedKeyStroke(e)) && onlyAllowNumeric(e, maximumLength, element);
    }

    function addSpaces(number, spaces) {
      var parts = []
      var j = 0;
      for (var i=0; i<spaces.length; i++) {
        if (number.length > spaces[i]) {
          parts.push(number.slice(j, spaces[i]));
          j = spaces[i];
        } else {
          if (i < spaces.length) {
            parts.push(number.slice(j, spaces[i]));
          } else {
            parts.push(number.slice(j));
          }
          break;
        }
      }

      if (parts.length > 0) {
        return parts.join(" ");
      } else {
        return number;
      }
    }

    var americanExpressSpaces = [4, 10, 15];
    var defaultSpaces = [4, 8, 12, 16];

    cardNumberInput.on("keypress", function(e) {
        var number = getInputValue(e, cardNumberInput);
        var isAmericanExpressCard = isAmericanExpress(number);
        var maximumLength = (isAmericanExpressCard ? 15 : 16);
        if (shouldProcessInput(e, maximumLength, cardNumberInput)) {
            var newInput;
            newInput = isAmericanExpressCard ? addSpaces(number, americanExpressSpaces) : addSpaces(number, defaultSpaces);
            cardNumberInput.val(newInput);
        }
    });

    expirationMonthInput.on("keypress", function(e) {
        var maximumLength = 2;
        if (shouldProcessInput(e, maximumLength, expirationMonthInput)) {
            var newInput = getInputValue(e, expirationMonthInput);
            if (newInput < 13) {
                expirationMonthInput.val(newInput);
            } else {
                e.preventDefault();
            }
        }
    });

    expirationYearInput.on("keypress", function(e) {
        var maximumLength = 2;
        if (shouldProcessInput(e, maximumLength, expirationYearInput)) {
            var newInput = getInputValue(e, expirationYearInput);
            expirationYearInput.val(newInput);
        }
    });

    cvvInput.on("keypress", function(e) {
        var number = getInputValue(e, cardNumberInput);
        var isAmericanExpressCard = isAmericanExpress(number);
        var maximumLength = (isAmericanExpressCard ? 4 : 3);
        if (shouldProcessInput(e, maximumLength, cvvInput)) {
            var newInput = getInputValue(e, cvvInput);
            cvvInput.val(newInput);
        }
    });
}

Gratipay.payments.cc.submit = function(e) {

    e.stopPropagation();
    e.preventDefault();
    $('button#save').css('opacity', 0.5);
    Gratipay.forms.clearFeedback();

    // Adapt our form lingo to balanced nomenclature.

    function val(field) {
        return $('form#payment input[id="' + field + '"]').val();
    }

    var credit_card = {};   // holds CC info

    credit_card.number = val('card_number').replace(/[^\d]/g, '');
    credit_card.cvv = val('cvv');
    credit_card.name = val('name');
    var country = val('country') || null;

    credit_card.meta = { 'address_2': val('address_2')
                       , 'region': val('state')
                       , 'city_town': val('city_town')
                       , 'country': country
                        };

    // XXX We're duping some of this info in both meta and address due to
    // evolution of the Balanced API and our stepwise keeping-up. See:
    // https://github.com/gratipay/gratipay.com/issues/2446 and links from
    // there.
    credit_card.address = { 'line1': val('address_1')
                          , 'line2': val('address_2')
                          , 'city': val('city_town')
                          , 'state': val('state')
                          , 'postal_code': val('zip')
                          , 'country_code': country
                           };

    credit_card.expiration_month = val('expiration_month');
    var year = val('expiration_year');
    credit_card.expiration_year = year.length == 2 ? '20' + year : year;

    if (!balanced.card.isCardNumberValid(credit_card.number)) {
        $('button#save').css('opacity', 1);
        Gratipay.forms.showFeedback(null, ["Your card number is bad."]);
    } else if (!balanced.card.isExpiryValid( credit_card.expiration_month
                                         , credit_card.expiration_year
                                          )) {
        $('button#save').css('opacity', 1);
        Gratipay.forms.showFeedback(null, ["Your expiration date is bad."]);
    } else if (!balanced.card.isSecurityCodeValid( credit_card.number
                                               , credit_card.cvv
                                                )) {
        $('button#save').css('opacity', 1);
        Gratipay.forms.showFeedback(null, ["Your CVV is bad."]);
    } else {
        balanced.card.create(credit_card, Gratipay.payments.cc.handleResponse);
    }

    return false;
};

Gratipay.payments.cc.handleResponse = function(response) {
    if (response.status_code !== 201) {
        var msg = Gratipay.payments.onError(response);
        $.post('/credit-card.json', {action: 'store-error', msg: msg});
        return;
    }

    /* The request to create the token succeeded. We now have a single-use
     * token associated with the credit card info. This token can be
     * used to associate the card with a customer. We want to do the
     * latter, and that happens on the server side. When the card is
     * tokenized Balanced performs card validation, so we alredy know the
     * card is good.
     */

    function detailedFeedback(data) {
        $('#status').text('failing');
        $('#delete').show();
        var details = [];
        Gratipay.forms.showFeedback(data.problem, [data.error]);
        $('button#save').css('opacity', 1);
    }

    Gratipay.forms.submit( "/credit-card.json"
                       , {card_uri: response.cards[0].href}
                       , Gratipay.payments.onSuccess
                       , detailedFeedback
                        );
};
<commit_msg>really disable the button instead of only changing its opacity<commit_after>/* Bank Account and Credit Card forms
 *
 * These two forms share some common wiring under the Gratipay.payments
 * namespace, and each has unique code under the Gratipay.payments.{cc,ba}
 * namespaces. Each form gets its own page so we only instantiate one of these
 * at a time.
 *
 */

Gratipay.payments = {};

// Common code
// ===========

Gratipay.payments.init = function(participantId) {
    Gratipay.participantId = participantId;
    $('#delete form').submit(Gratipay.payments.submitDeleteForm);

    // Lazily depend on Balanced.
    var balanced_js = "https://js.balancedpayments.com/1.1/balanced.min.js";
    jQuery.getScript(balanced_js, function() {
        $('input[type!="hidden"]').eq(0).focus();
    }).fail(Gratipay.error);
}

Gratipay.payments.submitDeleteForm = function(e) {
    var item = $("#payout").length ? "bank account" : "credit card";
    var slug = $("#payout").length ? "bank-account" : "credit-card";
    var msg = "Really disconnect your " + item + "?";
    if (!confirm(msg)) {
        e.stopPropagation();
        e.preventDefault();
        return false;
    }

    jQuery.ajax(
        { url: '/' + slug + '.json'
        , data: {action: "delete"}
        , type: "POST"
        , success: function() {
            window.location.href = '/' + slug + '.html';
          }
        , error: Gratipay.error
         }
    );
    return false;
};

Gratipay.payments.onError = function(response) {
    $('button#save').prop('disabled', false);
    var msg = response.status_code + ": " +
        $.map(response.errors, function(obj) { return obj.description }).join(', ');
    Gratipay.forms.showFeedback(null, [msg]);
    return msg;
};

Gratipay.payments.onSuccess = function(data) {
    $('#status').text('working').addClass('highlight');
    setTimeout(function() {
        $('#status').removeClass('highlight');
    }, 8000);
    $('#delete').show();
    Gratipay.forms.clearFeedback();
    $('button#save').prop('disabled', false);
    setTimeout(function() {
        window.location.href = '/' + Gratipay.participantId + '/';
    }, 1000);
};

// Bank Accounts
// =============

Gratipay.payments.ba = {};

Gratipay.payments.ba.init = function(participantId) {
    Gratipay.payments.init(participantId);
    $('#payout').submit(Gratipay.payments.ba.submit);
};

Gratipay.payments.ba.submit = function (e) {
    e.preventDefault();

    $('button#save').prop('disabled', true);
    Gratipay.forms.clearFeedback();

    var bankAccount = {
        name: $('#account_name').val(),
        account_number: $('#account_number').val(),
        routing_number: $('#routing_number').val()
    };

    Gratipay.payments.ba.merchantData = {
        //type: 'person',  // Oooh, may need to vary this some day?
        street_address: $('#address_1').val(),
        postal_code: $('#zip').val(),
        phone_number: $('#phone_number').val(),
        region: $('#state').val(),
        dob_month: $('#dob-month').val(),
        dob_year: $('#dob-year').val(),
        dob_day: $('#dob-day').val(),
        name: $('#name').val()
    };
    var errors = [];

    // Require some fields.
    // ====================
    // We only require fields that are actually on the page. Since we don't
    // load the identity verification fields if they're already verified, not
    // all of these will necessarily be present at all.

    var requiredFields = {
        name: 'Your legal name is required.',
        address_1: 'Your street address is required.',
        zip: 'Your ZIP or postal code is required.',
        phone_number: 'A phone number is required.',
        account_name: 'The name on your bank account is required.',
        account_number: 'Your bank account number is required.',
        routing_number: 'A routing number is required.'
    };
    for (var field in requiredFields) {
        var $f = $('#' + field);
        if (!$f.length)  // Only validate if it's on the page.
            continue;
        var value = $f.val();

        if (!value) {
            $f.closest('div').addClass('error');
            errors.push(requiredFields[field]);
        } else {
            $f.closest('div').removeClass('error');
        }
    }

    // Validate routing number.
    // ========================

    var $rn = $('#routing_number');
    if (bankAccount.routing_number) {
        if (!balanced.bankAccount.validateRoutingNumber(bankAccount.routing_number)) {
            $rn.closest('div').addClass('error');
            errors.push("That routing number is invalid.");
        } else {
            $rn.closest('div').removeClass('error');
        }
    }

    if (errors.length) {
        $('button#save').prop('disabled', false);
        Gratipay.forms.showFeedback(null, errors);
    } else {
        balanced.bankAccount.create( bankAccount
                                   , Gratipay.payments.ba.handleResponse
                                    );
    }
};

Gratipay.payments.ba.handleResponse = function (response) {
    if (response.status_code !== 201) {
        var msg = Gratipay.payments.onError(response);
        $.post('/bank-account.json', {action: 'store-error', msg: msg});
        return;
    }

    /* The request to tokenize the bank account succeeded. Now we need to
     * validate the merchant information. We'll submit it to
     * /bank-accounts.json and check the response code to see what's going
     * on there.
     */

    function detailedFeedback(data) {
        $('#status').text('failing');
        $('#delete').show();
        var messages = [data.error];
        if (data.problem == 'More Info Needed') {
            messages = [ "Sorry, we couldn't verify your identity. Please "
                       + "check, correct, and resubmit your details."
            ];
        }
        Gratipay.forms.showFeedback(data.problem, messages);
        $('button#save').prop('disabled', false);
    }

    var detailsToSubmit = Gratipay.payments.ba.merchantData;
    detailsToSubmit.bank_account_uri = response.bank_accounts[0].href;

    Gratipay.forms.submit( "/bank-account.json"
                       , detailsToSubmit
                       , Gratipay.payments.onSuccess
                       , detailedFeedback
                        );
};

// Credit Cards
// ============

Gratipay.payments.cc = {};

Gratipay.payments.cc.init = function(participantId) {
    Gratipay.payments.init(participantId);
    $('form#payment').submit(Gratipay.payments.cc.submit);
    Gratipay.payments.cc.formatInputs(
        $('#card_number'),
        $('#expiration_month'),
        $('#expiration_year'),
        $('#cvv')
    );
};

/* Most of the following code is taken from https://github.com/wangjohn/creditly */

Gratipay.payments.cc.formatInputs = function (cardNumberInput, expirationMonthInput, expirationYearInput, cvvInput) {
    function getInputValue(e, element) {
        var inputValue = element.val().trim();
        inputValue = inputValue + String.fromCharCode(e.which);
        return inputValue.replace(/[^\d]/g, "");
    }

    function isEscapedKeyStroke(e) {
        // Key event is for a browser shortcut
        if (e.metaKey || e.ctrlKey) return true;

        // If keycode is a space
        if (e.which === 32) return false;

        // If keycode is a special char (WebKit)
        if (e.which === 0) return true;

        // If char is a special char (Firefox)
        if (e.which < 33) return true;

        return false;
    }

    function isNumberEvent(e) {
        return (/^\d+$/.test(String.fromCharCode(e.which)));
    }

    function onlyAllowNumeric(e, maximumLength, element) {
        e.preventDefault();
        // Ensure that it is a number and stop the keypress
        if (!isNumberEvent(e)) {
            return false;
        }
        return true;
    }

    var isAmericanExpress = function(number) {
        return number.match("^(34|37)");
    };

    function shouldProcessInput(e, maximumLength, element) {
        var target = e.currentTarget;
        if (getInputValue(e, element).length > maximumLength) {
          e.preventDefault();
          return false;
        }
        if ((target.selectionStart !== target.value.length)) {
          return false;
        }
        return (!isEscapedKeyStroke(e)) && onlyAllowNumeric(e, maximumLength, element);
    }

    function addSpaces(number, spaces) {
      var parts = []
      var j = 0;
      for (var i=0; i<spaces.length; i++) {
        if (number.length > spaces[i]) {
          parts.push(number.slice(j, spaces[i]));
          j = spaces[i];
        } else {
          if (i < spaces.length) {
            parts.push(number.slice(j, spaces[i]));
          } else {
            parts.push(number.slice(j));
          }
          break;
        }
      }

      if (parts.length > 0) {
        return parts.join(" ");
      } else {
        return number;
      }
    }

    var americanExpressSpaces = [4, 10, 15];
    var defaultSpaces = [4, 8, 12, 16];

    cardNumberInput.on("keypress", function(e) {
        var number = getInputValue(e, cardNumberInput);
        var isAmericanExpressCard = isAmericanExpress(number);
        var maximumLength = (isAmericanExpressCard ? 15 : 16);
        if (shouldProcessInput(e, maximumLength, cardNumberInput)) {
            var newInput;
            newInput = isAmericanExpressCard ? addSpaces(number, americanExpressSpaces) : addSpaces(number, defaultSpaces);
            cardNumberInput.val(newInput);
        }
    });

    expirationMonthInput.on("keypress", function(e) {
        var maximumLength = 2;
        if (shouldProcessInput(e, maximumLength, expirationMonthInput)) {
            var newInput = getInputValue(e, expirationMonthInput);
            if (newInput < 13) {
                expirationMonthInput.val(newInput);
            } else {
                e.preventDefault();
            }
        }
    });

    expirationYearInput.on("keypress", function(e) {
        var maximumLength = 2;
        if (shouldProcessInput(e, maximumLength, expirationYearInput)) {
            var newInput = getInputValue(e, expirationYearInput);
            expirationYearInput.val(newInput);
        }
    });

    cvvInput.on("keypress", function(e) {
        var number = getInputValue(e, cardNumberInput);
        var isAmericanExpressCard = isAmericanExpress(number);
        var maximumLength = (isAmericanExpressCard ? 4 : 3);
        if (shouldProcessInput(e, maximumLength, cvvInput)) {
            var newInput = getInputValue(e, cvvInput);
            cvvInput.val(newInput);
        }
    });
}

Gratipay.payments.cc.submit = function(e) {

    e.stopPropagation();
    e.preventDefault();
    $('button#save').prop('disabled', true);
    Gratipay.forms.clearFeedback();

    // Adapt our form lingo to balanced nomenclature.

    function val(field) {
        return $('form#payment input[id="' + field + '"]').val();
    }

    var credit_card = {};   // holds CC info

    credit_card.number = val('card_number').replace(/[^\d]/g, '');
    credit_card.cvv = val('cvv');
    credit_card.name = val('name');
    var country = val('country') || null;

    credit_card.meta = { 'address_2': val('address_2')
                       , 'region': val('state')
                       , 'city_town': val('city_town')
                       , 'country': country
                        };

    // XXX We're duping some of this info in both meta and address due to
    // evolution of the Balanced API and our stepwise keeping-up. See:
    // https://github.com/gratipay/gratipay.com/issues/2446 and links from
    // there.
    credit_card.address = { 'line1': val('address_1')
                          , 'line2': val('address_2')
                          , 'city': val('city_town')
                          , 'state': val('state')
                          , 'postal_code': val('zip')
                          , 'country_code': country
                           };

    credit_card.expiration_month = val('expiration_month');
    var year = val('expiration_year');
    credit_card.expiration_year = year.length == 2 ? '20' + year : year;

    if (!balanced.card.isCardNumberValid(credit_card.number)) {
        $('button#save').prop('disabled', false);
        Gratipay.forms.showFeedback(null, ["Your card number is bad."]);
    } else if (!balanced.card.isExpiryValid( credit_card.expiration_month
                                         , credit_card.expiration_year
                                          )) {
        $('button#save').prop('disabled', false);
        Gratipay.forms.showFeedback(null, ["Your expiration date is bad."]);
    } else if (!balanced.card.isSecurityCodeValid( credit_card.number
                                               , credit_card.cvv
                                                )) {
        $('button#save').prop('disabled', false);
        Gratipay.forms.showFeedback(null, ["Your CVV is bad."]);
    } else {
        balanced.card.create(credit_card, Gratipay.payments.cc.handleResponse);
    }

    return false;
};

Gratipay.payments.cc.handleResponse = function(response) {
    if (response.status_code !== 201) {
        var msg = Gratipay.payments.onError(response);
        $.post('/credit-card.json', {action: 'store-error', msg: msg});
        return;
    }

    /* The request to create the token succeeded. We now have a single-use
     * token associated with the credit card info. This token can be
     * used to associate the card with a customer. We want to do the
     * latter, and that happens on the server side. When the card is
     * tokenized Balanced performs card validation, so we alredy know the
     * card is good.
     */

    function detailedFeedback(data) {
        $('#status').text('failing');
        $('#delete').show();
        var details = [];
        Gratipay.forms.showFeedback(data.problem, [data.error]);
        $('button#save').prop('disabled', false);
    }

    Gratipay.forms.submit( "/credit-card.json"
                       , {card_uri: response.cards[0].href}
                       , Gratipay.payments.onSuccess
                       , detailedFeedback
                        );
};
<|endoftext|>


<commit_before>(function($) {
    $.fn.newsticker = function(opts) {
        // default configuration
        var config = $.extend({}, {
            height: 30,
            speed: 800,
            start: 8,
            interval: 3000,
            move: null
        }, opts);
        // main function
        function init(obj) {
            var $newsticker = obj,
                $frame = $newsticker.find('.newsticker-list'),
                $item = $frame.find('.newsticker-item'),
                $next,
                stop = false;

            $newsticker.init = function(){
                $newsticker.on('mouseover mouseout', function(e) {
                    if (e.type == 'mouseover') {
                        stop = true;
                    } else { // mouseout
                        stop = false;
                    }
                });
                $item.eq(0).addClass('current');

                if($.isFunction(config.move)){
                    config.move.call(this); //same as "config.move();"
                }
                else{
                    $newsticker.move();
                }
            };

            $newsticker.move = function(){
                setInterval(function() {
                    if (!stop) {
                        var $current = $frame.find('.current');

                        $frame.animate({
                            top: '-=' + config.height + 'px'
                        }, config.speed, function() {
                            $next = $frame.find('.current').next();
                            $next.addClass('current');
                            $current.removeClass('current');
                            $current.clone().appendTo($frame);
                            $current.remove();
                            $frame.css('top', config.start + 'px');
                        });
                    }
                }, config.interval);
            };

            $newsticker.init();
        }
        // initialize every element
        this.each(function() {
            init($(this));
        });
        return this;
    };
    // start
    $(function() {
        $('.newsticker').newsticker();
    });
})(jQuery);<commit_msg>refactor<commit_after>(function($) {
    $.fn.newsticker = function(opts) {
        // default configuration
        var config = $.extend({}, {
            height: 30,
            speed: 800,
            start: 8,
            interval: 3000,
            move: null
        }, opts);
        // main function
        function init(obj) {
            var $newsticker = obj,
                $frame = $newsticker.find('.newsticker-list'),
                $item = $frame.find('.newsticker-item'),
                $next,
                stop = false;

            $newsticker.init = function(){
                $item.eq(0).addClass('current'); //set start item
                $newsticker.suspend();

                if($.isFunction(config.move)){
                    config.move.call(this);
                }
                else{
                    $newsticker.move();
                }
            };

            $newsticker.suspend = function(){
                $newsticker.on('mouseover mouseout', function(e) {
                    if (e.type == 'mouseover') {
                        stop = true;
                    } else { //mouseout
                        stop = false;
                    }
                });
            };

            $newsticker.move = function(){
                setInterval(function() {
                    if (!stop) {
                        var $current = $frame.find('.current');

                        $frame.animate({
                            top: '-=' + config.height + 'px'
                        }, config.speed, function() {
                            $next = $frame.find('.current').next();
                            $next.addClass('current');
                            $current.removeClass('current');
                            $current.clone().appendTo($frame);
                            $current.remove();
                            $frame.css('top', config.start + 'px');
                        });
                    }
                }, config.interval);
            };

            $newsticker.init();
        }
        // initialize every element
        this.each(function() {
            init($(this));
        });
        return this;
    };
    // start
    $(function() {
        $('.newsticker').newsticker();
    });
})(jQuery);<|endoftext|>


<commit_before>/*!
 * select v0.0.1
 * by Liuyuchao
 * Copyright 2015.2
 * Date: Tue Feb 25 2015
 */

 ;(function($,window,document){

    var pluginName = 'selectlist',
        defaults = {

            //默认属性配置
            enable: true,   //选择列表是否可用
            zIndex: null,  //选择列表z-index值，如需兼容IE6/7,必须加此属性
            width: null,   //选择列表宽度
            height: null,  //选择列表高度
            border: null,  //选择列表边框
            borderRadius: null,  //选择列表圆角
            showMaxHeight: null,  //选择列表显示最大高度
            optionHeight: null || 34,   //选择列表单项高度
            triangleSize: null || 6,   //右侧小三角大小
            triangleColor: null || '#333',  //右侧小三角颜色

            topPosition: false,  //选择列表项在列表框上部显示,默认在下边显示
            speed: null || 100,   //选择列表框显示动画速度（毫秒）
            onChange: null  //自定义模拟选择列表项change事件
        };

    function SelectList(element,options){
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    SelectList.prototype={

        init: function(){
            var that = this,
                element = this.element;

            that.replaceProtoypeSelect(element);
            that.setSelectEvent(element);
            that.setSelectStyle(element);
        },

        //获取原生选择列表id值
        getSelectID: function(element){
            var $this = $(element),
                selectID = $this.attr('id');

            if(typeof(selectID) !== 'undefined'){
                return selectID;
            }else{
                selectID='';
                return selectID;
            }
        },

        //获取原生选择列表name值
        getSelectName: function(element){
            var that = this,
                $this = $(element),
                selectName = $this.attr('name');

            if(typeof(selectName) !== 'undefined'){
                return selectName;
            }else{
                return that.getSelectID($this);
            }
        },

        //获取原生选择列表class名
        getSelectClassName: function(element){
            var $this = $(element),
                tempClassNameArray = [],
                classNameArray = [],
                className = $this.attr('class');

            if(typeof(className) !== 'undefined'){
                classNameArray = className.split(' ');
                classNameArray.sort();
                tempClassNameArray =[classNameArray[0]];
                for(var i = 1; i < classNameArray.length; i++){
                    if( classNameArray[i] !== tempClassNameArray[tempClassNameArray.length-1]){
                        tempClassNameArray.push(classNameArray[i]);
                    }
                }
                return tempClassNameArray.join(' ');
            }else{
                className='';
                return className;
            }
        },

        //获取原生选择列表选中索引值
        getSelectedIndex: function(element){
            var $this = $(element),
                selectedIndex = $this.get(0).selectedIndex || 0;

            return selectedIndex;
        },

        //获取原生选择列表option的数量
        getOptionCount: function(element){
            var $this = $(element);

            return  $this.children().length;
        },

        //获取原生选择列表option的内容
        getOptionText: function(element){
            var that = this,
                $this = $(element),
                $option = $this.children(),
                optionText = [],
                selectLength = that.getOptionCount($this);

            for(var i=0;i<selectLength;i++){
                optionText[i] = $.trim($option.eq(i).text());
            }
            return optionText;
        },

        //获取原生选择列表选中值
        getSelectedOptionText: function(element){
            var that = this,
                $this = $(element),
                selectedIndex = that.getSelectedIndex($this),
                optionText = that.getOptionText($this);

            return optionText[selectedIndex];

        },

        //获取原生选择列表选中option的value值
        getSelectedOptionValue: function(element){
            var that = this,
                $this = $(element),
                selectedIndex = that.getSelectedIndex($this),
                optionValue = that.getOptionValue($this);

            return optionValue[selectedIndex];
        },

        //获取原生选择列表所有option的value,返回数组
        getOptionValue: function(element){
            var that = this,
                $this = $(element),
                $option = $this.children(),
                optionValue = [],
                selectLength = that.getOptionCount($this);

            for(var i = 0; i < selectLength; i++ ){
                optionValue[i] = $option.eq(i).val();
            }
            return optionValue;
        },

        //生成模拟选择列表
        renderSelect: function(element){
            var that = this,
                $this = $(element),
                selectID = that.getSelectID($this),
                selectName = that.getSelectName($this),
                selectClassName = that.getSelectClassName($this),
                selectOptionText = that.getOptionText($this),
                selectedOptionText = that.getSelectedOptionText($this),
                selectOptionValue = that.getOptionValue($this),
                selectedIndex = that.getSelectedIndex($this),
                selectedValue = that.getSelectedOptionValue($this),
                selectLength = that.getOptionCount($this),
                selectHTML = '
<input type="hidden" name="' + selectName + '" value="' + selectedValue + '" /><input type="button" class="select-button" value="' + selectedOptionText + '" />

',
                selectListHTML = '';

            for(var i=0; i<selectLength; i++){
                if(i !== selectedIndex){
                    selectListHTML = selectListHTML + '
' + selectOptionText[i] + '
';
                }else{
                    selectListHTML = selectListHTML + '
' + selectOptionText[i] + '
';
                }

            }
            selectHTML = selectHTML + selectListHTML + '

';

            return selectHTML;
        },

        //替换原生选择列表
        replaceProtoypeSelect: function(element){
            var that = this,
                $this = $(element),
                selectHTML = that.renderSelect($this);

            $this.replaceWith(selectHTML);
        },

        //设置模拟选择列表不可用
        setSelectDisabled: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this);

            $(selectID).children('i').addClass('disabled').end()
                .children('.select-button').attr('disabled','disabled');
        },

        //设置模拟选择列表可用
        setSelectEnabled: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this);

            $(selectID).children('i').removeClass('disabled').end()
                .children('.select-button').removeAttr('disabled');
        },

        //设置模拟选择列表样式
        setSelectStyle: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this),
                selectLength = that.getOptionCount($this);

            //设置模拟选择列表外层样式
            $(selectID).css({
                'z-index': that.setStyleProperty(that.settings.zIndex),
                width: that.setStyleProperty(that.settings.width) + 'px',
                height: that.setStyleProperty(that.settings.height) + 'px'
            });

            //设置模拟选择列表向下箭头样式
            $(selectID).children('.select-down').css({
                top: that.setStyleProperty((that.settings.height - that.settings.triangleSize)/2) + 'px',
                'border-width': that.setStyleProperty(that.settings.triangleSize) + 'px',
                'border-color': that.setStyleProperty(that.settings.triangleColor) + ' transparent transparent transparent'
            });

            //设置模拟选择列表按钮样式
            $(selectID).children('.select-button').css({
                width: function(){
                    if(!that.settings.width){
                        return;
                    }else{
                        return that.settings.width - 2 + 'px';
                    }
                },
                height: that.setStyleProperty(that.settings.height) + 'px',
                border: that.setStyleProperty(that.settings.border),
                'border-radius': that.setStyleProperty(that.settings.borderRadius) + 'px'
            });

            //设置模拟选择列表下拉外层样式
            $(selectID).children('.select-list').css({
                width:  function(){
                    if(!that.settings.width){
                        return;
                    }else{
                        return that.settings.width - 2 + 'px';
                    }
                },
                'top': function(index,value){
                    if(!that.settings.height){
                        return;
                    }else{
                        if(!that.settings.topPosition){
                            return that.settings.height + 1 + 'px';
                        }else{
                            if(!that.settings.optionHeight){
                                //计算下拉列表高度
                            }else{
                                return -that.settings.optionHeight*selectLength - 3 + 'px';
                            }
                        }
                    }
                },
                'max-height': that.setStyleProperty(that.settings.showMaxHeight) + 'px'
            });

            //设置设置模拟选择列表选项外层样式
            $(selectID + ' ul').css({
                'max-height': that.setStyleProperty(that.settings.showMaxHeight) + 'px',
                'line-height': that.setStyleProperty(that.settings.optionHeight) + 'px'
            });

            //设置模拟选择列表选项样式
            $(selectID + ' li').css({
                height: that.setStyleProperty(that.settings.optionHeight) + 'px'
            });

        },

        //检测是否设置某个样式
        setStyleProperty: function(styleProperty){
            if(!styleProperty){
                return;
            }else{
                return styleProperty;
            }
        },

        //绑定模拟选择列表一系列事件
        setSelectEvent: function(element){
            var that = this,
                $this = $(element),
                showSpeed = that.settings.speed,
                selectID = '#' + that.getSelectID($this),
                selectName = that.getSelectName($this),
                selectedIndex = that.getSelectedIndex($this),
                selectLength = that.getOptionCount($this),
                $selectItem = $(selectID + ' li');

            if(that.settings.enable){
                $(selectID)
                    .click(function(){
                        $(this).children('.select-list').slideToggle(showSpeed);
                    })
                    .focusout(function(){
                        $(this).children('.select-list').slideUp(showSpeed);
                    })
                    .on('keyup','input[type="button"]',function(event){
                        //缓存第一个被选中的值
                        var $selectedItem = $(this).siblings('.select-list').children().children('li.selected');

                        switch(event.keyCode){
                            //Enter
                            case 13:
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                if ($.isFunction(that.settings.onChange)) {
                                    that.settings.onChange();
                                }
                                break;
                            //Esc
                            case 27:
                                $(this).siblings('.select-list').slideUp(showSpeed);
                                break;
                            //Up
                            case 38:
                                event.preventDefault();
                                if(selectedIndex !== 0){
                                    $selectedItem.removeClass('selected').prev().addClass('selected');
                                    selectedIndex =  selectedIndex - 1;
                                }else{
                                    $selectItem.last().addClass('selected').siblings().removeClass('selected');
                                    selectedIndex = selectLength - 1;
                                }
                                $selectedItem =  $(this).siblings('.select-list').children().children('li.selected');
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                break;
                            //Down
                            case 40:
                                event.preventDefault();
                                if(selectedIndex < selectLength - 1 ){
                                    $selectedItem.removeClass('selected').next().addClass('selected');
                                    selectedIndex =  selectedIndex + 1;
                                }else{
                                    $selectItem.first().addClass('selected').siblings().removeClass('selected');
                                    selectedIndex = 0;
                                }
                                $selectedItem =  $(this).siblings('.select-list').children().children('li.selected');
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                break;
                        }

                    })
                    .children('i').removeClass('disabled').end()
                    .children('.select-button').removeAttr('disabled');

                //绑定单击选项事件
                $(selectID + ' li').mousedown(function(event){
                    event.stopPropagation();
                    event.preventDefault();
                    $(this)
                        .addClass('selected').siblings().removeClass('selected')
                        .parent().parent().slideUp(showSpeed)
                        .prev().val($(this).text())
                        .siblings('input[type="hidden"]').val($(this).attr('data-value'));

                    if($.isFunction(that.settings.onChange)){
                        that.settings.onChange();
                    }
                    return false;
                }).hover(function(){
                    $(this).addClass('selected').siblings().removeClass('selected');
                });

            }else{
                $(selectID)
                    .children('i').addClass('disabled').end()
                    .children('.select-button').attr('disabled','disabled');
                return;
            }
        }

    };

    $.fn[pluginName] = function(options) {
        this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new SelectList(this, options));
                if(!options.topPosition){
                    options.zIndex--;
                }else{
                    options.zIndex++;
                };
            }
        });
        return this;
    };

 })(jQuery,window,document);<commit_msg>fix click scrollbar hide dropdwon list when selectlist has scrollbar<commit_after>/*!
 * select v0.0.1
 * by Liuyuchao
 * Copyright 2015.2
 * Date: Tue Feb 25 2015
 */

 ;(function($,window,document){

    var pluginName = 'selectlist',
        defaults = {

            //默认属性配置
            enable: true,   //选择列表是否可用
            zIndex: null,  //选择列表z-index值，如需兼容IE6/7,必须加此属性
            width: null,   //选择列表宽度
            height: null,  //选择列表高度
            border: null,  //选择列表边框
            borderRadius: null,  //选择列表圆角
            showMaxHeight: null,  //选择列表显示最大高度
            optionHeight: null || 34,   //选择列表单项高度
            triangleSize: null || 6,   //右侧小三角大小
            triangleColor: null || '#333',  //右侧小三角颜色

            topPosition: false,  //选择列表项在列表框上部显示,默认在下边显示
            speed: null || 100,   //选择列表框显示动画速度（毫秒）
            onChange: null  //自定义模拟选择列表项change事件
        };

    function SelectList(element,options){
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    SelectList.prototype={

        init: function(){
            var that = this,
                element = this.element;

            that.replaceProtoypeSelect(element);
            that.setSelectEvent(element);
            that.setSelectStyle(element);
        },

        //获取原生选择列表id值
        getSelectID: function(element){
            var $this = $(element),
                selectID = $this.attr('id');

            if(typeof(selectID) !== 'undefined'){
                return selectID;
            }else{
                selectID='';
                return selectID;
            }
        },

        //获取原生选择列表name值
        getSelectName: function(element){
            var that = this,
                $this = $(element),
                selectName = $this.attr('name');

            if(typeof(selectName) !== 'undefined'){
                return selectName;
            }else{
                return that.getSelectID($this);
            }
        },

        //获取原生选择列表class名
        getSelectClassName: function(element){
            var $this = $(element),
                tempClassNameArray = [],
                classNameArray = [],
                className = $this.attr('class');

            if(typeof(className) !== 'undefined'){
                classNameArray = className.split(' ');
                classNameArray.sort();
                tempClassNameArray =[classNameArray[0]];
                for(var i = 1; i < classNameArray.length; i++){
                    if( classNameArray[i] !== tempClassNameArray[tempClassNameArray.length-1]){
                        tempClassNameArray.push(classNameArray[i]);
                    }
                }
                return tempClassNameArray.join(' ');
            }else{
                className='';
                return className;
            }
        },

        //获取原生选择列表选中索引值
        getSelectedIndex: function(element){
            var $this = $(element),
                selectedIndex = $this.get(0).selectedIndex || 0;

            return selectedIndex;
        },

        //获取原生选择列表option的数量
        getOptionCount: function(element){
            var $this = $(element);

            return  $this.children().length;
        },

        //获取原生选择列表option的内容
        getOptionText: function(element){
            var that = this,
                $this = $(element),
                $option = $this.children(),
                optionText = [],
                selectLength = that.getOptionCount($this);

            for(var i=0;i<selectLength;i++){
                optionText[i] = $.trim($option.eq(i).text());
            }
            return optionText;
        },

        //获取原生选择列表选中值
        getSelectedOptionText: function(element){
            var that = this,
                $this = $(element),
                selectedIndex = that.getSelectedIndex($this),
                optionText = that.getOptionText($this);

            return optionText[selectedIndex];

        },

        //获取原生选择列表选中option的value值
        getSelectedOptionValue: function(element){
            var that = this,
                $this = $(element),
                selectedIndex = that.getSelectedIndex($this),
                optionValue = that.getOptionValue($this);

            return optionValue[selectedIndex];
        },

        //获取原生选择列表所有option的value,返回数组
        getOptionValue: function(element){
            var that = this,
                $this = $(element),
                $option = $this.children(),
                optionValue = [],
                selectLength = that.getOptionCount($this);

            for(var i = 0; i < selectLength; i++ ){
                optionValue[i] = $option.eq(i).val();
            }
            return optionValue;
        },

        //生成模拟选择列表
        renderSelect: function(element){
            var that = this,
                $this = $(element),
                selectID = that.getSelectID($this),
                selectName = that.getSelectName($this),
                selectClassName = that.getSelectClassName($this),
                selectOptionText = that.getOptionText($this),
                selectedOptionText = that.getSelectedOptionText($this),
                selectOptionValue = that.getOptionValue($this),
                selectedIndex = that.getSelectedIndex($this),
                selectedValue = that.getSelectedOptionValue($this),
                selectLength = that.getOptionCount($this),
                selectHTML = '
<input type="hidden" name="' + selectName + '" value="' + selectedValue + '" /><input type="button" class="select-button" value="' + selectedOptionText + '" />

',
                selectListHTML = '';

            for(var i=0; i<selectLength; i++){
                if(i !== selectedIndex){
                    selectListHTML = selectListHTML + '
' + selectOptionText[i] + '
';
                }else{
                    selectListHTML = selectListHTML + '
' + selectOptionText[i] + '
';
                }

            }
            selectHTML = selectHTML + selectListHTML + '

';

            return selectHTML;
        },

        //替换原生选择列表
        replaceProtoypeSelect: function(element){
            var that = this,
                $this = $(element),
                selectHTML = that.renderSelect($this);

            $this.replaceWith(selectHTML);
        },

        //设置模拟选择列表不可用
        setSelectDisabled: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this);

            $(selectID).children('i').addClass('disabled').end()
                .children('.select-button').attr('disabled','disabled');
        },

        //设置模拟选择列表可用
        setSelectEnabled: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this);

            $(selectID).children('i').removeClass('disabled').end()
                .children('.select-button').removeAttr('disabled');
        },

        //设置模拟选择列表样式
        setSelectStyle: function(element){
            var that = this,
                $this = $(element),
                selectID = '#' + that.getSelectID($this),
                selectLength = that.getOptionCount($this);

            //设置模拟选择列表外层样式
            $(selectID).css({
                'z-index': that.setStyleProperty(that.settings.zIndex),
                width: that.setStyleProperty(that.settings.width) + 'px',
                height: that.setStyleProperty(that.settings.height) + 'px'
            });

            //设置模拟选择列表向下箭头样式
            $(selectID).children('.select-down').css({
                top: that.setStyleProperty((that.settings.height - that.settings.triangleSize)/2) + 'px',
                'border-width': that.setStyleProperty(that.settings.triangleSize) + 'px',
                'border-color': that.setStyleProperty(that.settings.triangleColor) + ' transparent transparent transparent'
            });

            //设置模拟选择列表按钮样式
            $(selectID).children('.select-button').css({
                width: function(){
                    if(!that.settings.width){
                        return;
                    }else{
                        return that.settings.width - 2 + 'px';
                    }
                },
                height: that.setStyleProperty(that.settings.height) + 'px',
                border: that.setStyleProperty(that.settings.border),
                'border-radius': that.setStyleProperty(that.settings.borderRadius) + 'px'
            });

            //设置模拟选择列表下拉外层样式
            $(selectID).children('.select-list').css({
                width:  function(){
                    if(!that.settings.width){
                        return;
                    }else{
                        return that.settings.width - 2 + 'px';
                    }
                },
                'top': function(index,value){
                    if(!that.settings.height){
                        return;
                    }else{
                        if(!that.settings.topPosition){
                            return that.settings.height + 1 + 'px';
                        }else{
                            if(!that.settings.optionHeight){
                                //计算下拉列表高度
                            }else{
                                return -that.settings.optionHeight*selectLength - 3 + 'px';
                            }
                        }
                    }
                },
                'max-height': that.setStyleProperty(that.settings.showMaxHeight) + 'px'
            });

            //设置设置模拟选择列表选项外层样式
            $(selectID + ' ul').css({
                'max-height': that.setStyleProperty(that.settings.showMaxHeight) + 'px',
                'line-height': that.setStyleProperty(that.settings.optionHeight) + 'px'
            });

            //设置模拟选择列表选项样式
            $(selectID + ' li').css({
                height: that.setStyleProperty(that.settings.optionHeight) + 'px'
            });

        },

        //检测是否设置某个样式
        setStyleProperty: function(styleProperty){
            if(!styleProperty){
                return;
            }else{
                return styleProperty;
            }
        },

        //绑定模拟选择列表一系列事件
        setSelectEvent: function(element){
            var that = this,
                $this = $(element),
                showSpeed = that.settings.speed,
                selectID = '#' + that.getSelectID($this),
                selectName = that.getSelectName($this),
                selectedIndex = that.getSelectedIndex($this),
                selectLength = that.getOptionCount($this),
                $selectItem = $(selectID + ' li');

            if(that.settings.enable){
                $(selectID)
                    .click(function(event){
                        event.stopPropagation();
                        $(this).children('.select-list').slideToggle(showSpeed);
                    })
                    .on('focusin',function(){
                        $(this).siblings('.select-wrapper').children('.select-list').slideUp(showSpeed);
                    })
                    .on('keyup','input[type="button"]',function(event){
                        //缓存第一个被选中的值
                        var $selectedItem = $(this).siblings('.select-list').children().children('li.selected');

                        switch(event.keyCode){
                            //Enter
                            case 13:
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                if ($.isFunction(that.settings.onChange)) {
                                    that.settings.onChange();
                                }
                                break;
                            //Esc
                            case 27:
                                $(this).siblings('.select-list').slideUp(showSpeed);
                                break;
                            //Up
                            case 38:
                                event.preventDefault();
                                if(selectedIndex !== 0){
                                    $selectedItem.removeClass('selected').prev().addClass('selected');
                                    selectedIndex =  selectedIndex - 1;
                                }else{
                                    $selectItem.last().addClass('selected').siblings().removeClass('selected');
                                    selectedIndex = selectLength - 1;
                                }
                                $selectedItem =  $(this).siblings('.select-list').children().children('li.selected');
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                break;
                            //Down
                            case 40:
                                event.preventDefault();
                                if(selectedIndex < selectLength - 1 ){
                                    $selectedItem.removeClass('selected').next().addClass('selected');
                                    selectedIndex =  selectedIndex + 1;
                                }else{
                                    $selectItem.first().addClass('selected').siblings().removeClass('selected');
                                    selectedIndex = 0;
                                }
                                $selectedItem =  $(this).siblings('.select-list').children().children('li.selected');
                                $(this)
                                    .val($selectedItem.text())
                                    .prev().prev().val($selectedItem.attr('data-value'));
                                break;
                        }

                    })
                    .children('i').removeClass('disabled').end()
                    .children('.select-button').removeAttr('disabled');

                //绑定单击选项事件
                $(selectID + ' li').mousedown(function(event){
                    event.stopPropagation();
                    $(this)
                        .addClass('selected').siblings().removeClass('selected')
                        .parent().parent().slideUp(showSpeed)
                        .prev().val($(this).text())
                        .siblings('input[type="hidden"]').val($(this).attr('data-value'));

                    if($.isFunction(that.settings.onChange)){
                        that.settings.onChange();
                    }
                    return false;
                }).hover(function(){
                    $(this).addClass('selected').siblings().removeClass('selected');
                })

                $(document).on('click',function(){
                    $(this).find('.select-list').slideUp(showSpeed);
                })

            }else{
                $(selectID)
                    .children('i').addClass('disabled').end()
                    .children('.select-button').attr('disabled','disabled');
                return;
            }
        }

    };

    $.fn[pluginName] = function(options) {
        this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new SelectList(this, options));
                if(!options.topPosition){
                    options.zIndex--;
                }else{
                    options.zIndex++;
                };
            }
        });
        return this;
    };

 })(jQuery,window,document);<|endoftext|>


<commit_before>/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var chokidar = require('chokidar');
var path = require('path');
var _ = require('lodash');

module.exports = function() {

  var start = function(container, config, changedCb, cb) {
    var watcherArgs = {persistent: true,
                       ignoreInitial: true};

    if (config && config.exclude) {
      watcherArgs.ignored = config.exclude;
    }

    var override = container && _.get(config, ['overrides', container.name, 'monitor']);

    var monitor = (override !== false) ? true : override;

    if (!monitor) {
      return cb(null);
    }

    // do not monitor docker conatiners!!
    if (container.type === 'docker') {
      return cb(null);
    }

    if (Object(monitor) === monitor && monitor.exclude) {
      watcherArgs.ignored = !(config && config.exclude) ?
        monitor.exclude :
          Array.isArray(config.exclude) ?
            [monitor.exclude, config.exclude] :
            [monitor.exclude].concat(config.exclude);
    }

    if (container.specific.path[container.specific.path.length - 1] === '/' ||
        container.specific.path[container.specific.path.length - 1] === '\\') {
      container.specific.path = path.resolve(container.specific.path);
    }

    var watcher = chokidar.watch(container.specific.path, watcherArgs);
    watcher.on('add', changedCb);
    watcher.on('change', changedCb);
    watcher.on('unlink', changedCb);
    watcher.on('addDir', changedCb);
    watcher.on('unlinkdir', changedCb);
    cb(watcher);
  };

  var stop = function stop(watcher) {
    if (watcher && watcher.close) {
      watcher.close();
    }
  };

  return {
    start: start,
    stop: stop,
  };
};

<commit_msg>more accurate representation<commit_after>/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

var chokidar = require('chokidar');
var path = require('path');
var _ = require('lodash');

module.exports = function() {

  var start = function(container, config, changedCb, cb) {
    var watcherArgs = {persistent: true,
                       ignoreInitial: true};

    if (config && config.exclude) {
      watcherArgs.ignored = config.exclude;
    }

    var monitor = container && _.get(config,
      ['overrides', container.name, 'monitor'], true);

    if (!monitor) {
      return cb(null);
    }

    // do not monitor docker conatiners!!
    if (container.type === 'docker') {
      return cb(null);
    }

    if (Object(monitor) === monitor && monitor.exclude) {
      watcherArgs.ignored = !(config && config.exclude) ?
        monitor.exclude :
          Array.isArray(config.exclude) ?
            [monitor.exclude, config.exclude] :
            [monitor.exclude].concat(config.exclude);
    }

    if (container.specific.path[container.specific.path.length - 1] === '/' ||
        container.specific.path[container.specific.path.length - 1] === '\\') {
      container.specific.path = path.resolve(container.specific.path);
    }

    var watcher = chokidar.watch(container.specific.path, watcherArgs);
    watcher.on('add', changedCb);
    watcher.on('change', changedCb);
    watcher.on('unlink', changedCb);
    watcher.on('addDir', changedCb);
    watcher.on('unlinkdir', changedCb);
    cb(watcher);
  };

  var stop = function stop(watcher) {
    if (watcher && watcher.close) {
      watcher.close();
    }
  };

  return {
    start: start,
    stop: stop,
  };
};

<|endoftext|>


<commit_before>var promises = require("./promises");
var async = require("async");

var documents = require("./documents");
var htmlPaths = require("./html-paths");
var HtmlGenerator = require("./html-generator").HtmlGenerator;
var results = require("./results");
var images = require("./images");

exports.DocumentConverter = DocumentConverter;

// TODO: investigate tidier solutions
if (typeof setImmediate === "undefined") {
    var setImmediate = function(callback) {
        setTimeout(callback, 0);
    };
}

function DocumentConverter(options) {
    return {
        convertToHtml: function() {
            var conversion = new DocumentConversion(options);
            return conversion.convertToHtml.apply(this, arguments);
        }
    };
}

function DocumentConversion(options) {
    var noteNumber = 1;

    var noteReferences = [];

    options = options || {};
    var idPrefix = options.idPrefix === undefined ?
        Math.floor(Math.random() * 1000000000000000) :
        options.idPrefix;

    var defaultParagraphStyle = htmlPaths.topLevelElement("p");

    var styleMap = options.styleMap || [];

    function convertToHtml(document) {
        var messages = [];
        var html = new HtmlGenerator({prettyPrint: options.prettyPrint, markdown: options.markdown});

        return promises.nfcall(elementToHtml, document, html, messages).then(function() {
            html.closeAll();

            return new results.Result(html.asString(), messages);
        });
    }

    function convertElements(elements, html, messages, callback) {
        async.eachSeries(elements, function(element, callback) {
            return elementToHtml(element, html, messages, callback);
        }, callback);
    }

    function elementToHtml(element, html, messages, callback) {
        var handler = elementConverters[element.type];
        if (handler) {
            handler(element, html, messages, callback);
        } else {
            callback();
        }
    }

    function convertParagraph(element, html, messages, callback) {
        html.satisfyPath(styleForParagraph(element, messages));
        setImmediate(function() {
            convertElements(element.children, html, messages, callback);
        });
    }

    function styleForParagraph(element, messages) {
        var style = findStyle(element);

        if (style) {
            return style.to;
        } else {
            if (element.styleId) {
                messages.push(unrecognisedStyleWarning("paragraph", element));
            }
            return defaultParagraphStyle;
        }
    }

    function convertRun(run, html, messages, callback) {
        var runHtml = new HtmlGenerator({ markdown: options.markdown });
        if (run.styleId) {
            var style = findStyle(run);
            if (style) {
                runHtml.satisfyPath(style.to);
            } else {
                messages.push(unrecognisedStyleWarning("run", run));
            }
        }
        if (run.isBold) {
            runHtml.open(htmlPaths.element("strong"));
        }
        if (run.isItalic) {
            runHtml.open(htmlPaths.element("em"));
        }
        if (run.verticalAlignment === documents.verticalAlignment.superscript) {
            runHtml.open(htmlPaths.element("sup"));
        }
        if (run.verticalAlignment === documents.verticalAlignment.subscript) {
            runHtml.open(htmlPaths.element("sub"));
        }
        if (run.isUnderline && options.convertUnderline) {
            options.convertUnderline(runHtml);
        }
        convertElements(run.children, runHtml, messages, function(err) {
            if (err) {
                callback(err);
            } else {
                runHtml.closeAll();
                html.append(runHtml);
                callback();
            }
        });
    }

    function findStyle(element) {
        for (var i = 0; i < styleMap.length; i++) {
            if (styleMap[i].from.matches(element)) {
                return styleMap[i];
            }
        }
    }

    var defaultConvertImage = images.inline(function(element) {
        return element.read("base64").then(function(imageBuffer) {
            return {
                src: "data:" + element.contentType + ";base64," + imageBuffer
            };
        });
    });

    function noteHtmlId(note) {
        return idPrefix + "-" + note.noteType + "-" + note.noteId;
    }

    function noteRefHtmlId(note) {
        return idPrefix + "-" + note.noteType + "-ref-" + note.noteId;
    }

    function convertTable(element, html, messages, callback) {
        html.closeAll();
        wrapChildren(element, "table", html, messages, callback);
    }

    function convertTableRow(element, html, messages, callback) {
        wrapChildren(element, "tr", html, messages, callback);
    }

    function convertTableCell(element, html, messages, callback) {
        html.open(htmlPaths.element("td"), {alwaysWrite: true});

        async.map(element.children, function(child, callback) {
            var cellHtml = new HtmlGenerator({ markdown: options.markdown });
            elementToHtml(child, cellHtml, messages, function(error) {
                if (error) {
                    callback(error);
                } else {
                    cellHtml.closeAll();
                    callback(null, cellHtml);
                }
            });
        }, function(error, childrenHtml) {
            if (error) {
                callback(error);
            } else {
                childrenHtml.forEach(function(childHtml) {
                    html.append(childHtml);
                });
                html.close();
                callback();
            }
        });
    }

    function wrapChildren(element, wrapElementName, html, messages, callback) {
        html.open(htmlPaths.element(wrapElementName));
        convertElements(element.children, html, messages, function(err) {
            if (err) {
                callback(err);
            } else {
                html.close();
                callback();
            }
        });
    }

    var elementConverters = {
        "document": function(document, html, messages, callback) {
            convertElements(document.children, html, messages, function(error) {
                if (error) {
                    callback(error);
                } else {
                    html.closeAll();
                    html.open(htmlPaths.element("ol"));
                    var notes = noteReferences.map(function(noteReference) {
                        return document.notes.resolve(noteReference);
                    });
                    convertElements(notes, html, messages, function(error) {
                        if (error) {
                            callback(error);
                        } else {
                            html.close();
                            callback();
                        }
                    });
                }
            });
        },
        "paragraph": convertParagraph,
        "run": convertRun,
        "text": function(element, html, messages, callback) {
            html.text(element.value);
            callback();
        },
        "tab": function(element, html, messages, callback) {
            html.text("\t");
            callback();
        },
        "hyperlink": function(element, html, messages, callback) {
            html.open(htmlPaths.element("a", {href: element.href}));
            convertElements(element.children, html, messages, function(err) {
                if (err) {
                    callback(err);
                } else {
                    html.close();
                    callback();
                }
            });
        },
        "noteReference": function(element, html, messages, callback) {
            html.open(htmlPaths.element("sup"));
            html.open(htmlPaths.element("a", {
                href: "#" + noteHtmlId(element),
                id: noteRefHtmlId(element)
            }));
            noteReferences.push(element);
            html.text("[" + (noteNumber++) + "]");
            html.close();
            html.close();
            callback();
        },
        "note": function(element, html, messages, callback) {
            html.open(htmlPaths.element("li", {id: noteHtmlId(element)}));
            var footnoteHtml = new HtmlGenerator({ markdown: options.markdown });
            convertElements(element.body, footnoteHtml, messages, function(err) {
                if (err) {
                    callback(err);
                } else {
                    footnoteHtml.text(" ");
                    footnoteHtml.open(htmlPaths.element("a", {href: "#" + noteRefHtmlId(element)}));
                    footnoteHtml.text("↑");
                    footnoteHtml.closeAll();
                    html.append(footnoteHtml);
                    html.close();
                    callback();
                }
            });
        },
        "image": options.convertImage || defaultConvertImage,
        "table": convertTable,
        "tableRow": convertTableRow,
        "tableCell": convertTableCell,
        "lineBreak": function(element, html, messages, callback) {
            html.selfClosing(htmlPaths.element("br"));
            callback();
        }
    };
    return {
        convertToHtml: convertToHtml
    };
}

function unrecognisedStyleWarning(type, element) {
    return results.warning(
        "Unrecognised " + type + " style: '" + element.styleName + "'" +
        " (Style ID: " + element.styleId + ")"
    );
}
<commit_msg>Create a single function for instantiating HtmlGenerator<commit_after>var promises = require("./promises");
var async = require("async");

var documents = require("./documents");
var htmlPaths = require("./html-paths");
var HtmlGenerator = require("./html-generator").HtmlGenerator;
var results = require("./results");
var images = require("./images");

exports.DocumentConverter = DocumentConverter;

// TODO: investigate tidier solutions
if (typeof setImmediate === "undefined") {
    var setImmediate = function(callback) {
        setTimeout(callback, 0);
    };
}

function DocumentConverter(options) {
    return {
        convertToHtml: function() {
            var conversion = new DocumentConversion(options);
            return conversion.convertToHtml.apply(this, arguments);
        }
    };
}

function DocumentConversion(options) {
    var noteNumber = 1;

    var noteReferences = [];

    options = options || {};
    var idPrefix = options.idPrefix === undefined ?
        Math.floor(Math.random() * 1000000000000000) :
        options.idPrefix;

    var defaultParagraphStyle = htmlPaths.topLevelElement("p");

    var styleMap = options.styleMap || [];

    function createHtmlGenerator() {
        return new HtmlGenerator({
            prettyPrint: options.prettyPrint,
            markdown: options.markdown
        });
    }

    function convertToHtml(document) {
        var messages = [];
        var html = createHtmlGenerator();

        return promises.nfcall(elementToHtml, document, html, messages).then(function() {
            html.closeAll();

            return new results.Result(html.asString(), messages);
        });
    }

    function convertElements(elements, html, messages, callback) {
        async.eachSeries(elements, function(element, callback) {
            return elementToHtml(element, html, messages, callback);
        }, callback);
    }

    function elementToHtml(element, html, messages, callback) {
        var handler = elementConverters[element.type];
        if (handler) {
            handler(element, html, messages, callback);
        } else {
            callback();
        }
    }

    function convertParagraph(element, html, messages, callback) {
        html.satisfyPath(styleForParagraph(element, messages));
        setImmediate(function() {
            convertElements(element.children, html, messages, callback);
        });
    }

    function styleForParagraph(element, messages) {
        var style = findStyle(element);

        if (style) {
            return style.to;
        } else {
            if (element.styleId) {
                messages.push(unrecognisedStyleWarning("paragraph", element));
            }
            return defaultParagraphStyle;
        }
    }

    function convertRun(run, html, messages, callback) {
        var runHtml = createHtmlGenerator();
        if (run.styleId) {
            var style = findStyle(run);
            if (style) {
                runHtml.satisfyPath(style.to);
            } else {
                messages.push(unrecognisedStyleWarning("run", run));
            }
        }
        if (run.isBold) {
            runHtml.open(htmlPaths.element("strong"));
        }
        if (run.isItalic) {
            runHtml.open(htmlPaths.element("em"));
        }
        if (run.verticalAlignment === documents.verticalAlignment.superscript) {
            runHtml.open(htmlPaths.element("sup"));
        }
        if (run.verticalAlignment === documents.verticalAlignment.subscript) {
            runHtml.open(htmlPaths.element("sub"));
        }
        if (run.isUnderline && options.convertUnderline) {
            options.convertUnderline(runHtml);
        }
        convertElements(run.children, runHtml, messages, function(err) {
            if (err) {
                callback(err);
            } else {
                runHtml.closeAll();
                html.append(runHtml);
                callback();
            }
        });
    }

    function findStyle(element) {
        for (var i = 0; i < styleMap.length; i++) {
            if (styleMap[i].from.matches(element)) {
                return styleMap[i];
            }
        }
    }

    var defaultConvertImage = images.inline(function(element) {
        return element.read("base64").then(function(imageBuffer) {
            return {
                src: "data:" + element.contentType + ";base64," + imageBuffer
            };
        });
    });

    function noteHtmlId(note) {
        return idPrefix + "-" + note.noteType + "-" + note.noteId;
    }

    function noteRefHtmlId(note) {
        return idPrefix + "-" + note.noteType + "-ref-" + note.noteId;
    }

    function convertTable(element, html, messages, callback) {
        html.closeAll();
        wrapChildren(element, "table", html, messages, callback);
    }

    function convertTableRow(element, html, messages, callback) {
        wrapChildren(element, "tr", html, messages, callback);
    }

    function convertTableCell(element, html, messages, callback) {
        html.open(htmlPaths.element("td"), {alwaysWrite: true});

        async.map(element.children, function(child, callback) {
            var cellHtml = createHtmlGenerator();
            elementToHtml(child, cellHtml, messages, function(error) {
                if (error) {
                    callback(error);
                } else {
                    cellHtml.closeAll();
                    callback(null, cellHtml);
                }
            });
        }, function(error, childrenHtml) {
            if (error) {
                callback(error);
            } else {
                childrenHtml.forEach(function(childHtml) {
                    html.append(childHtml);
                });
                html.close();
                callback();
            }
        });
    }

    function wrapChildren(element, wrapElementName, html, messages, callback) {
        html.open(htmlPaths.element(wrapElementName));
        convertElements(element.children, html, messages, function(err) {
            if (err) {
                callback(err);
            } else {
                html.close();
                callback();
            }
        });
    }

    var elementConverters = {
        "document": function(document, html, messages, callback) {
            convertElements(document.children, html, messages, function(error) {
                if (error) {
                    callback(error);
                } else {
                    html.closeAll();
                    html.open(htmlPaths.element("ol"));
                    var notes = noteReferences.map(function(noteReference) {
                        return document.notes.resolve(noteReference);
                    });
                    convertElements(notes, html, messages, function(error) {
                        if (error) {
                            callback(error);
                        } else {
                            html.close();
                            callback();
                        }
                    });
                }
            });
        },
        "paragraph": convertParagraph,
        "run": convertRun,
        "text": function(element, html, messages, callback) {
            html.text(element.value);
            callback();
        },
        "tab": function(element, html, messages, callback) {
            html.text("\t");
            callback();
        },
        "hyperlink": function(element, html, messages, callback) {
            html.open(htmlPaths.element("a", {href: element.href}));
            convertElements(element.children, html, messages, function(err) {
                if (err) {
                    callback(err);
                } else {
                    html.close();
                    callback();
                }
            });
        },
        "noteReference": function(element, html, messages, callback) {
            html.open(htmlPaths.element("sup"));
            html.open(htmlPaths.element("a", {
                href: "#" + noteHtmlId(element),
                id: noteRefHtmlId(element)
            }));
            noteReferences.push(element);
            html.text("[" + (noteNumber++) + "]");
            html.close();
            html.close();
            callback();
        },
        "note": function(element, html, messages, callback) {
            html.open(htmlPaths.element("li", {id: noteHtmlId(element)}));
            var footnoteHtml = createHtmlGenerator();
            convertElements(element.body, footnoteHtml, messages, function(err) {
                if (err) {
                    callback(err);
                } else {
                    footnoteHtml.text(" ");
                    footnoteHtml.open(htmlPaths.element("a", {href: "#" + noteRefHtmlId(element)}));
                    footnoteHtml.text("↑");
                    footnoteHtml.closeAll();
                    html.append(footnoteHtml);
                    html.close();
                    callback();
                }
            });
        },
        "image": options.convertImage || defaultConvertImage,
        "table": convertTable,
        "tableRow": convertTableRow,
        "tableCell": convertTableCell,
        "lineBreak": function(element, html, messages, callback) {
            html.selfClosing(htmlPaths.element("br"));
            callback();
        }
    };
    return {
        convertToHtml: convertToHtml
    };
}

function unrecognisedStyleWarning(type, element) {
    return results.warning(
        "Unrecognised " + type + " style: '" + element.styleName + "'" +
        " (Style ID: " + element.styleId + ")"
    );
}
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "experimental"
};

var self = require("sdk/self");
var main = require("../main.js");

const { Cu, Ci } = require("chrome");
const { BaseSidePanel } = require("../chrome/baseSidePanel");
const { Class } = require("sdk/core/heritage");
const { Trace, TraceError } = require("../core/trace.js").get(module.id);
const { Tool } = require("dev/toolbox");
const { Locale } = require("../core/locale.js");
const { DomTree } = require("../dom/domTree.js");
const { DomProvider } = require("../dom/domProvider.js");
const { DomCache } = require("../dom/domCache.js");
const { loadSheet } = require("sdk/stylesheet/utils");
const { defer } = require("sdk/core/promise");

/**
 * @panel This object implements a main DOM panel. It displays structure
 * of the current document. The panel might be converted into DevTools SDK
 * example showing how to render remote objects (grips) by using repository
 * of registered templates {@Reps}.
 */
const DomSidePanel = Class(
/** @lends DomSidePanel */
{
  extends: BaseSidePanel,

  id: "domSidePanel",
  label: Locale.$STR("domPanelTitle"),
  tooltip: "DOM panel example",
  icon: "./icon-16.png",
  url: "./dom.html",

  setup: function({frame, toolbox}) {
    BaseSidePanel.prototype.setup.apply(this, arguments);

    Trace.sysout("domSidePanel.setup;", arguments);
  },

  onReady: function(options) {
    BaseSidePanel.prototype.onReady.apply(this, arguments);

    Trace.sysout("domSidePanel.onReady;");
  },

  supportsObject: function() {
    // xxxHonza: FIX ME
    return true;
  },

  refresh: function(actor) {
    Trace.sysout("domSidePanel.refresh; actor:", actor);

    let target = this.toolbox.target;
    target.activeTab.attachThread({}, (response, threadClient) => {
      Trace.sysout("domSidePanel.refresh; threadClient:", threadClient);

      // xxxHonza: theme applying and switch must be centralized
      // (see also theme-switcher)
      let win = this.panelFrame.contentWindow;
      loadSheet(win, "chrome://firebug/skin/domTree.css", "author");

      var panelNode = win.document.body;

      // xxxHonza: there should be just one instance of the cache
      // (see also ConsoleOverlay).
      // The initialization should happen just once.
      let cache = new DomCache(threadClient);
      let provider = new DomProvider(cache);
      this.tree = new DomTree(provider);
      this.tree.replace(panelNode, {object: actor});
    });
  },
});

// Exports from this module
exports.DomSidePanel = DomSidePanel;
<commit_msg>Fix localization<commit_after>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "experimental"
};

var self = require("sdk/self");
var main = require("../main.js");

const { Cu, Ci } = require("chrome");
const { BaseSidePanel } = require("../chrome/baseSidePanel");
const { Class } = require("sdk/core/heritage");
const { Trace, TraceError } = require("../core/trace.js").get(module.id);
const { Tool } = require("dev/toolbox");
const { Locale } = require("../core/locale.js");
const { DomTree } = require("../dom/domTree.js");
const { DomProvider } = require("../dom/domProvider.js");
const { DomCache } = require("../dom/domCache.js");
const { loadSheet } = require("sdk/stylesheet/utils");
const { defer } = require("sdk/core/promise");

/**
 * @panel This object implements a main DOM panel. It displays structure
 * of the current document. The panel might be converted into DevTools SDK
 * example showing how to render remote objects (grips) by using repository
 * of registered templates {@Reps}.
 */
const DomSidePanel = Class(
/** @lends DomSidePanel */
{
  extends: BaseSidePanel,

  id: "domSidePanel",
  label: Locale.$STR("dom.panel.title"),
  tooltip: Locale.$STR("dom.panel.tip"),
  icon: "./icon-16.png",
  url: "./dom.html",

  setup: function({frame, toolbox}) {
    BaseSidePanel.prototype.setup.apply(this, arguments);

    Trace.sysout("domSidePanel.setup;", arguments);
  },

  onReady: function(options) {
    BaseSidePanel.prototype.onReady.apply(this, arguments);

    Trace.sysout("domSidePanel.onReady;");
  },

  supportsObject: function() {
    // xxxHonza: FIX ME
    return true;
  },

  refresh: function(actor) {
    Trace.sysout("domSidePanel.refresh; actor:", actor);

    let target = this.toolbox.target;
    target.activeTab.attachThread({}, (response, threadClient) => {
      Trace.sysout("domSidePanel.refresh; threadClient:", threadClient);

      // xxxHonza: theme applying and switch must be centralized
      // (see also theme-switcher)
      let win = this.panelFrame.contentWindow;
      loadSheet(win, "chrome://firebug/skin/domTree.css", "author");

      var panelNode = win.document.body;

      // xxxHonza: there should be just one instance of the cache
      // (see also ConsoleOverlay).
      // The initialization should happen just once.
      let cache = new DomCache(threadClient);
      let provider = new DomProvider(cache);
      this.tree = new DomTree(provider);
      this.tree.replace(panelNode, {object: actor});
    });
  },
});

// Exports from this module
exports.DomSidePanel = DomSidePanel;
<|endoftext|>