Skip to content

Commit

Permalink
Merge pull request #420 from cssinjs/dynamic-ss
Browse files Browse the repository at this point in the history
implement sheet.update() #356
  • Loading branch information
kof authored Mar 14, 2017
2 parents 9999e5b + bfa418b commit 969fe0b
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 3 deletions.
34 changes: 34 additions & 0 deletions docs/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ sheet.addRules({
})
```

## Update function values

`sheet.update(data)`

If you use [function values](./json-api.md#function-values), you will want to update them with new data. This method will call all your function values, pass the `data` param and update the CSS Rule if needed.

```javascript
sheet.update({
// Any data here.
})
```

## Create a rule without a Style Sheet

`jss.createRule([name], style, [options])`
Expand Down Expand Up @@ -297,6 +309,28 @@ console.log(sheet.toString())
}
```

## Extract dynamic styles

`getDynamicStyles(styles)`

Extracts a styles object with only rules that contain function values. Useful when you want to share a static part between different elements and render only the dynamic styles separate for each element.

```js
const dynamicStyles = getDynamicStyles({
button: {
fontSize: 12,
color: data => data.color
}
})

// Returns only styles with dynamic values.
{
button: {
color: data => data.color
}
}
```

## Plugins

See [plugins](./plugins.md) documentation.
12 changes: 12 additions & 0 deletions docs/json-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ Compiles to:
}
```

## Function values

If you want dynamic behavior for your Style Sheet, you can use functions as a value which return the actual value. Use [sheet.update(data)](./js-api.md#update-function-values) in order to pass the data object.

```javascript
const styles = {
button: {
color: data => data.color
}
}
```

## Media Queries

```javascript
Expand Down
19 changes: 19 additions & 0 deletions src/RulesContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@ export default class RulesContainer {
delete this.classes[rule.name]
}

/**
* Update the function values with a new data.
*/
update(data: Object): void {
this.index.forEach((rule) => {
const style = rule.originalStyle
for (const prop in style) {
const value = style[prop]
if (typeof value === 'function') {
const computedValue = value(data)
rule.prop(prop, computedValue)
}
}
if (rule.rules instanceof RulesContainer) {
rule.rules.update(data)
}
})
}

/**
* Convert rules to a CSS string.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/StyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ export default class StyleSheet {
return this
}

/**
* Update the function values with a new data.
*/
update(data: Object): this {
this.rules.update(data)
return this
}

/**
* Convert rules to a CSS string.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import SheetsRegistry from './SheetsRegistry'
import RulesContainer from './RulesContainer'
import sheets from './sheets'
import type {JssOptions} from './types'
import getDynamicStyles from './utils/getDynamicStyles'

/**
* Extracts a styles object with only rules that contain function values.
*/
export {getDynamicStyles}

/**
* SheetsRegistry for SSR.
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/RegularRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,12 @@ export default class RegularRule {
prop(name: string, value?: string): RegularRule|string {
// Its a setter.
if (value != null) {
this.style[name] = value
// Only defined if option linked is true.
if (this.renderable) this.renderer.setStyle(this.renderable, name, value)
// Don't do anything if the value has not changed.
if (this.style[name] !== value) {
this.style[name] = value
// Only defined if option linked is true.
if (this.renderable) this.renderer.setStyle(this.renderable, name, value)
}
return this
}
// Its a getter, read the value from the DOM if its not cached.
Expand Down
32 changes: 32 additions & 0 deletions src/utils/getDynamicStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Extracts a styles object with only rules that contain function values.
*/
export default (styles: Object): Object|null => {
let fnValuesCounter = 0

// eslint-disable-next-line no-shadow
function extract(styles: Object): Object {
let to

for (const key in styles) {
const value = styles[key]
const type = typeof value

if (type === 'function') {
if (!to) to = {}
to[key] = value
fnValuesCounter++
}
else if (type === 'object' && value !== null && !Array.isArray(value)) {
if (!to) to = {}
const extracted = extract(value)
if (extracted) to[key] = extracted
}
}

return to
}

const extracted = extract(styles)
return fnValuesCounter ? extracted : null
}
46 changes: 46 additions & 0 deletions tests/functional/sheet.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-underscore-dangle */

import {stripIndent} from 'common-tags'
import expect from 'expect.js'
import {create} from '../../src'
import DomRenderer from '../../src/backends/DomRenderer'
Expand Down Expand Up @@ -401,4 +402,49 @@ describe('Functional: sheet', () => {
expect(sheet.classes.a, 'test')
})
})

describe('sheet.update()', () => {
let sheet

beforeEach(() => {
sheet = jss.createStyleSheet({
a: {
color: theme => theme.color
},
'@media all': {
b: {
color: theme => theme.color
}
}
}, {link: true})
})

afterEach(() => {
sheet.detach()
})

it('should return correct .toString()', () => {
expect(sheet.toString()).to.be('')

sheet.update({
color: 'green'
})

expect(sheet.toString()).to.be(stripIndent`
.a-id {
color: green;
}
@media all {
.b-id {
color: green;
}
}
`)
})

it('should render updated props', () => {
sheet.update({color: 'green'}).attach()
expect(getCss(getStyle())).to.be(removeWhitespace(sheet.toString()))
})
})
})
1 change: 1 addition & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ import './integration/sheetsRegistry'
import './functional/rules'
import './functional/sheet'
import './functional/priority'
import './unit/utils'
68 changes: 68 additions & 0 deletions tests/unit/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import expect from 'expect.js'
import getDynamicStyles from '../../src/utils/getDynamicStyles'

describe('Unit: jss', () => {
describe('getDynamicStyles', () => {
it('should extract dynamic styles', () => {
const color = data => data.color
const styles = {
button: {
float: 'left',
margin: [5, 10],
color,
'@media screen': {
width: null,
},
'@media print': {
width: undefined,
color
},
'& a': {
color: 'red',
'& b': {
color
}
},
},
'@media': {
button: {
width: 2,
color
}
}
}
expect(getDynamicStyles(styles)).to.eql({
button: {
color,
'@media print': {
color
},
'& a': {
'& b': {
color
}
}
},
'@media': {
button: {
color
}
}
})
})

it('should return null if there are no function values', () => {
const styles = {
button: {
float: 'left',
},
'@media': {
button: {
width: 2
}
}
}
expect(getDynamicStyles(styles)).to.be(null)
})
})
})

0 comments on commit 969fe0b

Please sign in to comment.