card icon indicating copy to clipboard operation
card copied to clipboard

Card Sizing Issue(s) ... Multiple Cards on a Page

Open BallisticPain opened this issue 8 years ago • 5 comments

There are times I have multiple credit card inputs as you'll see in the screenshot(s) below. I don't think it's pertinent based on how I'm using card, but this is in an Angular app. I initialize the card as follows...

    const cardContainer = '.card-container-'+this.roomIndex;
    const ccNumber = 'input#td-cc-number-'+this.roomIndex;
    const ccName = 'input#td-cc-first-name-'+this.roomIndex+', input#td-cc-last-name-'+this.roomIndex;
    const ccExpiry = 'input#td-cc-expiry-'+this.roomIndex;
    const ccCvc = 'input#td-cc-cvc-'+this.roomIndex;

    // Initialize Card
    $(this.element.nativeElement).card({
      // a selector or DOM element for the container
      // where you want the card to appear
      container: cardContainer, // *required*
      width: 250,
      formSelectors: {
        numberInput: ccNumber, // optional — default input[name="number"]
        nameInput: ccName,
        expiryInput: ccExpiry, // optional — default input[name="expiry"]
        cvcInput: ccCvc // optional — default input[name="cvc"]
      },
      formatting: true, // optional - default true
      // Strings for translation - optional
      messages: {
        validDate: 'valid\ndate', // optional - default 'valid\nthru'
        monthYear: 'mm-yyyy' // optional - default 'month/year'
      },
      // Default placeholders for rendered fields - optional
      placeholders: {
        number: '•••• •••• •••• ••••',
        name: 'Full Name',
        expiry: '••-••••',
        cvc: '•••'
      },
      // if true, will log helpful messages for setting up Card
      debug: true // optional - default false
    });

As you can see there, I have specified the width. This works for the first card in the loop of initializations... Below is the screenshot showing the first one and another one with varying size.

screen shot 2017-02-18 at 12 04 36 am

I started trying to specify the size of the card when the cards were not appropriately re-sizing as they do on the demo page. So I could be doing something wrong. I'll show my relevant template code for completeness...

<div class="col-sm-6" *ngFor="let traveler of hotelReservation?.travelers; let i = index; let isFirst = first">
    <div class="panel panel-primary">
      <div class="pull-right text-primary m-r-10 m-t-5" *ngIf="isFirst">Primary</div>
      <div class="panel-body">
        <div class="row">
          <div class="col-xs-12">
            <div>
              <span class="room-title">Room {{ i + 1 }}</span>
              <div *ngIf="isFirst || !traveler.sameName">
                <input name="firstName" type="text" placeholder="First" [(ngModel)]="traveler.firstName" required>
                <input name="lastName" type="text" placeholder="Last" [(ngModel)]="traveler.lastName" required>
                <input name="loyalty" type="text" placeholder="Loyalty" [(ngModel)]="traveler.loyalty">
              </div>
            </div>
            <div class="row" *ngIf="isFirst || !traveler.samePayment">
              <div id="form-{{i}}" piCreditCard [roomIndex]="i">
                <div class="well m-t-15" id="td-card-details-{{i}}">
                  <div class="well-title">Payment Details</div>
                  <div class="row">
                    <div class="col-sm-6">
                      <input [(ngModel)]="traveler.creditCard.firstName" type="text" required class="form-control" id="td-cc-first-name-{{i}}" placeholder="First Name" name="cc-first-name" autocomplete="cc-given-name">
                    </div>
                    <div class="col-sm-6">
                      <input [(ngModel)]="traveler.creditCard.lastName" type="text" required class="form-control" id="td-cc-last-name-{{i}}" placeholder="Last Name" name="cc-last-name" autocomplete="cc-family-name">
                    </div>
                  </div>
                  <div class="row">
                    <div class="col-sm-6">
                      <input [(ngModel)]="traveler.phone" type="tel" alt="Phone Number" required class="form-control" placeholder="Phone" name="phoneNumber" id="td-phoneNumber-{{i}}" autocomplete="tel">
                    </div>
                    <div class="col-sm-6">
                      <input [(ngModel)]="traveler.email" type="email" alt="Email Address" required class="form-control" placeholder="Email" name="email" id="td-email-{{i}}" autocomplete="email">
                    </div>
                  </div>
                  <div class="row m-b-10 m-t-10">
                    <div class="col-sm-12">
                      <div class="card-container-{{i}}"></div>
                    </div>
                  </div>
                  <div class="row">
                    <div class="col-sm-12">
                      <input [(ngModel)]="traveler.creditCard.ccNumber" type="tel" required class="form-control" id="td-cc-number-{{i}}" placeholder="Card Number" name="cc-number" autocomplete="cc-number">
                    </div>
                  </div>
                  <div class="row">
                    <div class="col-xs-12 col-sm-8">
                      <input [(ngModel)]="traveler.creditCard.ccExpiry" type="tel" required class="form-control" id="td-cc-expiry-{{i}}" placeholder="MM-YYYY" name="cc-expiry" autocomplete="cc-exp">
                    </div>
                    <div class="col-xs-12 col-sm-4">
                      <input [(ngModel)]="traveler.creditCard.ccCvc" type="tel" required class="form-control" id="td-cc-cvc-{{i}}" placeholder="CVC" name="cc-cvc" autocomplete="cc-csc">
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div *ngIf="!isFirst">
              <label>
                <input type="checkbox" name="sameName" [(ngModel)]="traveler.sameName"/>
                Use Primary Name
              </label>
              &nbsp;&nbsp;
              <label>
                <input type="checkbox" name="samePayment" [(ngModel)]="traveler.samePayment"/>
                Use Primary Payment
              </label>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

For those not familiar with Angular, the piCreditCard directive is what actually initializes the card(s).

I have made a "stab" attempt at solving this, but I'm not a CoffeeScript person, and unfortunately I don't have the time at the moment to learn it as well as I would hope. So maybe someone can point me in the right direction, or fix this. It would be greatly appreciated.

The ultimate fix for me would also be knowing how to make it responsive. That would be much more impressive for my client as they would like it larger on the larger screens, but I must be thinking about their users more than themselves.

Thanks! Jarvis

BallisticPain avatar Feb 18 '17 06:02 BallisticPain

Ah! I understand what the issue is here — it looks like you're using one selector for multiple cards. Instead, you should instantiate a new card instance for every card! Is that possible with the angular library?

jessepollak avatar Mar 01 '17 19:03 jessepollak

@jessepollak I am initializing using the first code block for each payment on the page. So I have two issues... the primary would eliminate the need of the secondary...

  1. I'm unable to get my cards to be responsive. How do you do this on the demo page? (Maybe I should just update and see if this is fixed for me)
  2. If I cannot get the cards to be responsive, I need to specify that width which makes it look good for the majority of screens, but right now only the first instance gets the width.

Thoughts?

Thanks, Jarvis

BallisticPain avatar Mar 01 '17 20:03 BallisticPain

I had the same issue and just solved by using different indexes to each card. Very ugly solution but it works... I guess somewhere in the library prevents attaching new instance to same container or form selector.

`let ccBoxes = $(".credit-card"); ccBoxes.each((index, element) => {

$(element).card({
    // a selector or DOM element for the container
    // where you want the card to appear
    container: '.card-wrapper' + index, // *required*
    formSelectors: {
        nameInput: ['input[name="creditCard'+ index +'[name]"]'],
        numberInput: ['input[name="creditCard'+index+'[number]"]'],
    },
    placeholders: {
        number: '',
        name: '',
        expiry: '',
        cvc: ''
    },
    masks: {
        cardNumber: '•' // optional - mask card number.
    },
    formatting: true, // optional - default true
    debug:true
});

}); `

keremcankaya0 avatar Jan 22 '18 23:01 keremcankaya0

In addition to @ keremcankaya0's answer, I also had to make the form: document.querySelector('form') unique by adding an id attribute, with an index in the id value, to for the form element, just like what is required for container.

So it looks like:

<div class="card-wrapper-1"></div>
<form id="some-id-1">
    ...
</form>

<div class="card-wrapper-2"></div>
<form id="some-id-2">
    ...
</form>

Then...

new Card({
    form: document.querySelector('#some-id-' + index),
    container: ('.card-wrapper-' + index),
    ...
})

hvaughan3 avatar Mar 13 '18 21:03 hvaughan3

Eu tive o mesmo problema e acabei de resolver usando índices diferentes para cada cartão. Solução muito feia, mas funciona ... Acho que em algum lugar da biblioteca impede a anexação de uma nova instância ao mesmo contêiner ou seletor de formulário.

`let ccBoxes = $(".cartão de crédito"); ccBoxes.each((índice, elemento) => {

$(element).card({
    // a selector or DOM element for the container
    // where you want the card to appear
    container: '.card-wrapper' + index, // *required*
    formSelectors: {
        nameInput: ['input[name="creditCard'+ index +'[name]"]'],
        numberInput: ['input[name="creditCard'+index+'[number]"]'],
    },
    placeholders: {
        number: '',
        name: '',
        expiry: '',
        cvc: ''
    },
    masks: {
        cardNumber: '•' // optional - mask card number.
    },
    formatting: true, // optional - default true
    debug:true
});

}); `

I had the same issue and just solved by using different indexes to each card. Very ugly solution but it works... I guess somewhere in the library prevents attaching new instance to same container or form selector.

`let ccBoxes = $(".credit-card"); ccBoxes.each((index, element) => {

$(element).card({
    // a selector or DOM element for the container
    // where you want the card to appear
    container: '.card-wrapper' + index, // *required*
    formSelectors: {
        nameInput: ['input[name="creditCard'+ index +'[name]"]'],
        numberInput: ['input[name="creditCard'+index+'[number]"]'],
    },
    placeholders: {
        number: '',
        name: '',
        expiry: '',
        cvc: ''
    },
    masks: {
        cardNumber: '•' // optional - mask card number.
    },
    formatting: true, // optional - default true
    debug:true
});

}); `

thanks bro. it's work

alexandremsouza1 avatar Apr 12 '23 13:04 alexandremsouza1