Skip to content

Commit

Permalink
Add short syntax: @ and : && fix class binding to string output
Browse files Browse the repository at this point in the history
  • Loading branch information
calebporzio committed Jan 7, 2020
1 parent 4b49315 commit 4062724
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 8 deletions.
2 changes: 1 addition & 1 deletion dist/alpine.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/alpine.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"main": "dist/alpine.js",
"name": "alpinejs",
"version": "1.4.0",
"version": "1.5.0",
"repository": {
"type": "git",
"url": "git://github.com/alpinejs/alpine.git"
Expand Down
4 changes: 3 additions & 1 deletion src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,9 @@ export default class Component {
el.value = value
}
} else if (attrName === 'class') {
if (Array.isArray(value)) {
if (typeof value === 'string') {
el.setAttribute('class', value)
} else if (Array.isArray(value)) {
el.setAttribute('class', value.join(' '))
} else {
// Use the class object syntax that vue uses to toggle them.
Expand Down
22 changes: 18 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,22 @@ export function saferEvalNoReturn(expression, dataContext, additionalHelperVaria
}

export function isXAttr(attr) {
const name = replaceAtAndColonWithStandardSyntax(attr.name)

const xAttrRE = /x-(on|bind|data|text|html|model|if|show|cloak|transition|ref)/

return xAttrRE.test(attr.name)
return xAttrRE.test(name)
}

export function getXAttrs(el, type) {
return Array.from(el.attributes)
.filter(isXAttr)
.map(attr => {
const typeMatch = attr.name.match(/x-(on|bind|data|text|html|model|if|show|cloak|transition|ref)/)
const valueMatch = attr.name.match(/:([a-zA-Z\-]+)/)
const modifiers = attr.name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
const name = replaceAtAndColonWithStandardSyntax(attr.name)

const typeMatch = name.match(/x-(on|bind|data|text|html|model|if|show|cloak|transition|ref)/)
const valueMatch = name.match(/:([a-zA-Z\-]+)/)
const modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []

return {
type: typeMatch ? typeMatch[1] : null,
Expand All @@ -94,6 +98,16 @@ export function getXAttrs(el, type) {
})
}

export function replaceAtAndColonWithStandardSyntax(name) {
if (name.startsWith('@')) {
return name.replace('@', 'x-on:')
} else if (name.startsWith(':')) {
return name.replace(':', 'x-bind:')
}

return name
}

export function transitionIn(el, callback, forceSkip = false) {
if (forceSkip) callback()

Expand Down
26 changes: 26 additions & 0 deletions test/bind.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Alpine from 'alpinejs'
import { wait } from '@testing-library/dom'

global.MutationObserver = class {
observe() {}
Expand Down Expand Up @@ -102,6 +103,19 @@ test('class attribute bindings are added by array syntax', async () => {
expect(document.querySelector('span').classList.contains('foo')).toBeTruthy
})

test('class attribute bindings are synced by string syntax', async () => {
document.body.innerHTML = `
<div x-data="{foo: 'bar baz'}">
<span class="" x-bind:class="foo"></span>
</div>
`

Alpine.start()

expect(document.querySelector('span').classList.contains('bar')).toBeTruthy
expect(document.querySelector('span').classList.contains('baz')).toBeTruthy
})

test('boolean attributes set to false are removed from element', async () => {
document.body.innerHTML = `
<div x-data="{ isSet: false }">
Expand Down Expand Up @@ -139,3 +153,15 @@ test('boolean attributes set to true are added to element', async () => {
expect(document.querySelectorAll('input')[2].required).toBeTruthy()
expect(document.querySelectorAll('input')[3].readOnly).toBeTruthy()
})

test('binding supports short syntax', async () => {
document.body.innerHTML = `
<div x-data="{ foo: 'bar' }">
<span :class="foo"></span>
</div>
`

Alpine.start()

expect(document.querySelector('span').classList.contains('bar')).toBeTruthy()
})
18 changes: 18 additions & 0 deletions test/on.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,21 @@ test('click away', async () => {

await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
})

test('supports short syntax', async () => {
document.body.innerHTML = `
<div x-data="{ foo: 'bar' }">
<button @click="foo = 'baz'"></button>
<span x-bind:foo="foo"></span>
</div>
`

Alpine.start()

expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')

document.querySelector('button').click()

await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
})

0 comments on commit 4062724

Please sign in to comment.