Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally provide innerHtml and textContent on StimulusReflex::Element #517

Merged
merged 15 commits into from
Jul 18, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/channels/stimulus_reflex/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def delegate_call_to_reflex(reflex)
elsif policy.arguments?
reflex.process(method_name, *arguments)
else
raise ArgumentError.new("wrong number of arguments (given #{arguments.inspect}, expected #{required_params.inspect}, optional #{optional_params.inspect})")
raise ArgumentError.new("wrong number of arguments (given #{arguments.inspect}, expected #{policy.required_params.inspect}, optional #{policy.optional_params.inspect})")
julianrubisch marked this conversation as resolved.
Show resolved Hide resolved
end
end

Expand Down
105 changes: 105 additions & 0 deletions javascript/reflex_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { extractElementAttributes, extractElementDataset } from './attributes'
import { getReflexRoots } from './reflexes'
import { uuidv4 } from './utils'
import { elementToXPath } from './utils'

export default class ReflexData {
constructor (
options,
reflexElement,
controllerElement,
reflexController,
permanentAttributeName,
target,
args,
url,
tabId
) {
this.options = options
this.reflexElement = reflexElement
this.controllerElement = controllerElement
this.reflexController = reflexController
this.permanentAttributeName = permanentAttributeName
this.target = target
this.args = args
this.url = url
this.tabId = tabId
}

get attrs () {
return this.options['attrs'] || extractElementAttributes(this.reflexElement)
}

get reflexId () {
this._reflexId = this._reflexId || this.options['reflexId'] || uuidv4()
return this._reflexId
}

get selectors () {
const selectors =
this.options['selectors'] || getReflexRoots(this.reflexElement)
if (typeof selectors === 'string') {
return [selectors]
} else {
return selectors
}
}

get resolveLate () {
return this.options['resolveLate'] || false
}

get dataset () {
return extractElementDataset(this.reflexElement)
}

get innerHTML () {
return this.includeHTML ? this.reflexElement.innerHTML : ''
}

get textContent () {
return this.includeText ? this.reflexElement.textContent : ''
}

get xpathController () {
return elementToXPath(this.controllerElement)
}

get xpathElement () {
return elementToXPath(this.reflexElement)
}

get includeHTML () {
return (
this.options['includeInnerHTML'] ||
'reflexIncludeHtml' in this.reflexElement.dataset
)
}

get includeText () {
return (
this.options['includeTextContent'] ||
'reflexIncludeText' in this.reflexElement.dataset
)
}

valueOf () {
return {
attrs: this.attrs,
dataset: this.dataset,
selectors: this.selectors,
reflexId: this.reflexId,
resolveLate: this.resolveLate,
xpathController: this.xpathController,
xpathElement: this.xpathElement,
inner_html: this.innerHTML,
text_content: this.textContent,
reflexController: this.reflexController,
permanentAttributeName: this.permanentAttributeName,
target: this.target,
args: this.args,
url: this.url,
tabId: this.tabId
}
}
}
56 changes: 21 additions & 35 deletions javascript/stimulus_reflex.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,13 @@ import { Controller } from 'stimulus'
import { defaultSchema } from './schema'
import { dispatchLifecycleEvent } from './lifecycle'
import { uuidv4, serializeForm } from './utils'
import { elementToXPath } from './utils'
import { beforeDOMUpdate, afterDOMUpdate, serverMessage } from './callbacks'
import {
registerReflex,
getReflexRoots,
setupDeclarativeReflexes
} from './reflexes'
import {
attributeValues,
extractElementAttributes,
extractElementDataset
} from './attributes'
import { registerReflex, setupDeclarativeReflexes } from './reflexes'
import { attributeValues } from './attributes'
import Log from './log'
import Debug from './debug'
import Deprecate from './deprecate'
import ReflexData from './reflex_data'
import reflexes from './reflexes'
import isolationMode from './isolation_mode'
import actionCable from './transports/action_cable'
Expand Down Expand Up @@ -136,36 +128,30 @@ const register = (controller, options = {}) => {
'selectors',
'reflexId',
'resolveLate',
'serializeForm'
'serializeForm',
'includeInnerHTML',
'includeTextContent'
].includes(key)
).length
) {
const opts = args.shift()
Object.keys(opts).forEach(o => (options[o] = opts[o]))
}
const attrs = options['attrs'] || extractElementAttributes(reflexElement)
const reflexId = options['reflexId'] || uuidv4()
let selectors = options['selectors'] || getReflexRoots(reflexElement)
if (typeof selectors === 'string') selectors = [selectors]
const resolveLate = options['resolveLate'] || false
const dataset = extractElementDataset(reflexElement)
const xpathController = elementToXPath(controllerElement)
const xpathElement = elementToXPath(reflexElement)
const data = {

leastbad marked this conversation as resolved.
Show resolved Hide resolved
const reflexData = new ReflexData(
options,
reflexElement,
controllerElement,
this.identifier,
reflexes.app.schema.reflexPermanentAttribute,
target,
args,
url,
tabId,
attrs,
dataset,
selectors,
reflexId,
resolveLate,
xpathController,
xpathElement,
reflexController: this.identifier,
permanentAttributeName: reflexes.app.schema.reflexPermanentAttribute
}
tabId
)

const reflexId = reflexData.reflexId

const { subscription } = this.StimulusReflex

if (!this.isActionCableConnectionOpen())
Expand All @@ -181,7 +167,7 @@ const register = (controller, options = {}) => {
controllerElement.reflexError = controllerElement.reflexError || {}

controllerElement.reflexController[reflexId] = this
controllerElement.reflexData[reflexId] = data
controllerElement.reflexData[reflexId] = reflexData.valueOf()

dispatchLifecycleEvent(
'before',
Expand Down Expand Up @@ -217,15 +203,15 @@ const register = (controller, options = {}) => {
})

controllerElement.reflexData[reflexId] = {
...data,
...reflexData.valueOf(),
params,
formData
}

subscription.send(controllerElement.reflexData[reflexId])
})

const promise = registerReflex(data)
const promise = registerReflex(reflexData.valueOf())

if (Debug.enabled) {
Log.request(
Expand Down
79 changes: 79 additions & 0 deletions javascript/test/reflexData.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import assert from 'assert'
import { JSDOM } from 'jsdom'
import ReflexData from '../reflex_data'

describe('ReflexData', () => {
it('returns an array of selectors', () => {
assert.deepStrictEqual(new ReflexData({ selectors: '#an-id' }).selectors, [
'#an-id'
])

assert.deepStrictEqual(
new ReflexData({ selectors: ['.item', 'li'] }).selectors,
['.item', 'li']
)
})

it("attaches the element's innerHTML if includeInnerHTML is true", () => {
const dom = new JSDOM(
'<div><ul><li>First Item</li><li>Last Item</li></ul></div>'
)
const element = dom.window.document.querySelector('div')

assert.equal(
new ReflexData({ includeInnerHTML: true }, element, element).innerHTML,
'<ul><li>First Item</li><li>Last Item</li></ul>'
)
})

it("attaches the element's innerHTML if includeInnerHTML is declared on the reflexElement", () => {
const dom = new JSDOM(
'<div data-reflex-include-html><ul><li>First Item</li><li>Last Item</li></ul></div>'
)
const element = dom.window.document.querySelector('div')

assert.equal(
new ReflexData({}, element, element).innerHTML,
'<ul><li>First Item</li><li>Last Item</li></ul>'
)
})

it("doesn't attach the element's innerHTML if includeInnerHTML is falsey", () => {
const dom = new JSDOM(
'<div><ul><li>First Item</li><li>Last Item</li></ul></div>'
)
const element = dom.window.document.querySelector('div')

assert.equal(new ReflexData({}, element, element).innerHTML, '')
})

it("attaches the element's textContent if includeTextContent is true", () => {
const dom = new JSDOM('<div><p>Some Text <a>with a link</a></p></div>')
const element = dom.window.document.querySelector('div')

assert.equal(
new ReflexData({ includeTextContent: true }, element, element)
.textContent,
'Some Text with a link'
)
})

it("attaches the element's textContent if includeTextContent is declared on the reflex element", () => {
const dom = new JSDOM(
'<div data-reflex-include-text><p>Some Text <a>with a link</a></p></div>'
)
const element = dom.window.document.querySelector('div')

assert.equal(
new ReflexData({}, element, element).textContent,
'Some Text with a link'
)
})

it("doesn't attach the element's textContent if includeTextContent is falsey", () => {
const dom = new JSDOM('<div><p>Some Text <a>with a link</a></p></div>')
const element = dom.window.document.querySelector('div')

assert.equal(new ReflexData({}, element, element).textContent, '')
})
})
12 changes: 10 additions & 2 deletions lib/stimulus_reflex/element.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
# frozen_string_literal: true

class StimulusReflex::Element < OpenStruct
attr_reader :attrs, :data_attrs
attr_reader :attrs, :data_attrs, :inner_html, :text_content

def initialize(data = {})
@attrs = HashWithIndifferentAccess.new(data["attrs"] || {})
@inner_html = data["inner_html"]
@text_content = data["text_content"]

datasets = data["dataset"] || {}
regular_dataset = datasets["dataset"] || {}
@data_attrs = build_data_attrs(regular_dataset, datasets["datasetAll"] || {})
all_attributes = @attrs.merge(@data_attrs)

julianrubisch marked this conversation as resolved.
Show resolved Hide resolved
super build_underscored(all_attributes)

@data_attrs.transform_keys! { |key| key.delete_prefix "data-" }
end

Expand All @@ -33,6 +37,10 @@ def dataset

private

def all_attributes
julianrubisch marked this conversation as resolved.
Show resolved Hide resolved
@attrs.merge(@data_attrs)
end

def build_data_attrs(dataset, dataset_all)
dataset_all.transform_keys! { |key| "data-#{key.delete_prefix("data-").pluralize}" }

Expand Down