Martin Stender
Martin Stender

Reputation: 11

Mixing Alpine.js with 'static' serverside markup, while getting the benefits of binding, etc

I'm new to Alpine and struggling to wrap my head around how to make a scenario like this work:

Let's say I have a serverside built page, that contains some buttons, that represent newsletters, the user can sign up to. The user might have signed up to some, and we need to indicate that as well, by adding a css-class, .i.e is-signed-up.

The initial serverside markup could be something like this:

<button id='newsletter-1' class='newsletter-signup'>Newsletter 1</button>

<div>some content here...</div>

<button id='newsletter-2' class='newsletter-signup'>Newsletter 2</button>

<div>more content here...</div>

<button id='newsletter-3' class='newsletter-signup'>Newsletter 3</button>

<div>and here...</div>

<button id='newsletter-4' class='newsletter-signup'>Newsletter 4</button>

(When all has loaded, the <button>'s should later allow the user to subscribe or unsubscribe to a newsletter directly, by clicking on one of the buttons, which should toggle the is-signed-up css-class accordingly.)

Anyway, then I fetch some json from an endpoint, that could look like this:

{"newsletters":[    
    {"newsletter":"newsletter-1"},  
    {"newsletter":"newsletter-2"},
    {"newsletter":"newsletter-4"} 
]}

I guess it could look something like this also:

{"newsletters":["newsletter-1", "newsletter-2", "newsletter-4"]}

Or some other structure, but the situation would be, that the user have signed up to newsletter 1, 2 and 4, but not newsletter 3, and we don't know that, until we get the JSON from the endpoint.

(But maybe the first variation is easier to map to a model, I guess...)

Anyway, I would like to do three things:

  1. Make Alpine get the relation between the model and the dom elements with the specific newsletter id (i.e. 'newsletter-2') - even if that exact id doesn't exist in the model.
  2. If the user has signed up to a newsletter, add the is-signed-up css-class to the corresponding <button> to show its status to the user.
  3. Bind to each newsletter-button, so all of them – not just the ones, the user has signed up to – listens for a 'click' and update the model accordingly.

I have a notion, that I might need to 'prepare' each newsletter-button beforehand with some Alpine-attributes, like 'x-model='newsletter-2', but I'm still unsure how to bind them together when Alpine has initialising, and I have the data from the endpoint,

How do I go about something like this?

Many thanks in advance! 😊

Upvotes: 0

Views: 531

Answers (1)

Dauros
Dauros

Reputation: 10502

So our basic task here is to add/remove a specific item to/from a list on a button click. Here I defined two component: the newsletter component using Alpine.data() creates the data (subs array), provides the toggling method (toggle_subscription(which)) and the checking method (is_subscribed(which)) that we can use to set the correct CSS class to a button. It also handles the data fetching in the init() method that executes automatically after the component is initialized. I have also created a save method that we can use to send the subscription list back to the backend.

The second component, subButton with Alpine.bind() is just to make the HTML code more compact and readable. (We can put each attribute from this directly to the buttons.) So on click event it calls the toggle_subscription with the current newsletter's key as the argument to add/remove it. Additionally it binds the bg-red CSS class to the button if the current newsletter is in the list. For that we use the is_subscribed method defined in our main component.

.bg-red {
    background-color: Tomato;
}
<script src="https://unpkg.com/[email protected]/dist/cdn.min.js" defer></script>
<div x-data="newsletter">
  <button x-bind="subButton('newsletter-1')">Newsletter 1</button>
  <button x-bind="subButton('newsletter-2')">Newsletter 2</button>
  <button x-bind="subButton('newsletter-3')">Newsletter 3</button>
  <button x-bind="subButton('newsletter-4')">Newsletter 4</button>
  
  <div>
    <button @click="save">Save</button>
  </div>
</div>


<script>
document.addEventListener('alpine:init', () => {
  Alpine.data('newsletter', () => ({
    subs: [],

    init() {
      // Fetch list of subscribed newsletters from backend
      this.subs = ['newsletter-1', 'newsletter-2', 'newsletter-4']
    },

    toggle_subscription(which) {
      if (this.subs.includes(which)) {
        this.subs = this.subs.filter(item => item !== which)
      }
      else {
        this.subs.push(which)
      }
    },

    is_subscribed(which) {
      return this.subs.includes(which)
    },
    
    save() {
       // Send this.sub to the backend to save active state.
    }

  }))

  Alpine.bind('subButton', (key) => ({
    '@click'() {
      this.toggle_subscription(key)
    },
    ':class'() {
      return this.is_subscribed(key) && 'bg-red'
    }
  }))
})
</script>

Upvotes: 2

Related Questions