Skip to content

Commit

Permalink
Add the possibility of updating html through morphs
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-s committed Nov 22, 2020
1 parent 4e63613 commit 9cfcff7
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 13 deletions.
8 changes: 8 additions & 0 deletions cypress/integration/websocket_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@ describe("Integration tests", () => {

cy.get('#decrementor').click()
cy.get('#counter-2').should('have.text', '-1')
}),

it("can send a morph in a reflex", () => {
cy.visit('/test')
cy.wait(200)
cy.get('#morph-button').click()

cy.get('#morph').should('have.text', 'I got morphed!')
})
})
29 changes: 24 additions & 5 deletions sockpuppet/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,28 @@ def reflex_message(self, data, **kwargs):
url = data['url']
selectors = data['selectors'] if data['selectors'] else ['body']
target = data['target']
identifier = data['identifier']
reflex_name, method_name = target.split('#')
reflex_name = classify(reflex_name)
arguments = data['args'] if data.get('args') else []
element = Element(data['attrs'])

# TODO can be removed once stimulus-reflex has increased a couple of versions
permanent_attribute_name = data.get('permanent_attribute_name')
if not permanent_attribute_name:
# Used in stimulus-reflex >= 3.4
permanent_attribute_name = data['permanentAttributeName']

try:
ReflexClass = self.reflexes.get(reflex_name)
reflex = ReflexClass(self, url=url, element=element, selectors=selectors)
reflex = ReflexClass(
self, url=url,
element=element,
selectors=selectors,
identifier=identifier,
reflex_id=data['reflexId'],
permanent_attribute_name=permanent_attribute_name
)
self.delegate_call_to_reflex(reflex, method_name, arguments)
except Exception as e:
error = '{}: {}'.format(e.__class__.__name__, str(e))
Expand All @@ -173,7 +188,7 @@ def reflex_message(self, data, **kwargs):
)
self.broadcast_error(message, data, reflex)
_, _, traceback = sys.exc_info()
exc = SockpuppetError(msg)
exc = SockpuppetError(message)
raise exc.with_traceback(traceback)

logger.debug('Reflex took %6.2fms', (time.perf_counter() - start) * 1000)
Expand Down Expand Up @@ -214,6 +229,10 @@ def receive_json(self, data, **kwargs):
print('Unsupported')

def render_page_and_broadcast_morph(self, reflex, selectors, data):
if reflex.is_morph:
# The reflex has already sent a message so consumer doesn't need to.
return

html = self.render_page(reflex)
if html:
self.broadcast_morphs(selectors, data, html, reflex)
Expand Down Expand Up @@ -245,7 +264,7 @@ def broadcast_morphs(self, selectors, data, html, reflex):
document = BeautifulSoup(html)
selectors = [selector for selector in selectors if document.select(selector)]

channel = Channel(reflex.get_channel_id(), identifier=data['identifier'])
broadcaster = Channel(reflex.get_channel_id(), identifier=data['identifier'])
logger.debug('Broadcasting to %s', reflex.get_channel_id())

# TODO can be removed once stimulus-reflex has increased a couple of versions
Expand All @@ -255,14 +274,14 @@ def broadcast_morphs(self, selectors, data, html, reflex):
permanent_attribute_name = data['permanentAttributeName']

for selector in selectors:
channel.morph({
broadcaster.morph({
'selector': selector,
'html': ''.join([e.decode_contents() for e in document.select(selector)]),
'children_only': True,
'permanent_attribute_name': permanent_attribute_name,
'stimulus_reflex': {**data}
})
channel.broadcast()
broadcaster.broadcast()

def delegate_call_to_reflex(self, reflex, method_name, arguments):
method = getattr(reflex, method_name)
Expand Down
59 changes: 51 additions & 8 deletions sockpuppet/reflex.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
from django.template.loader import render_to_string
from django.template.backends.django import Template
from django.test import RequestFactory

from .channel import Channel

PROTECTED_VARIABLES = [
'consumer',
'element',
'is_morph',
'selectors',
'session',
'url',
]


class Reflex:
def __init__(self, consumer, url, element, selectors):
def __init__(
self, consumer, url, element, selectors, identifier='', permanent_attribute_name=None, reflex_id=None
):
self.consumer = consumer
self.url = url
self.element = element
self.selectors = selectors
self.session = consumer.scope['session']

def get_channel_id(self):
'''
Override this to make the reflex send to a different channel
other than the session_key of the user
'''
return self.session.session_key
self.identifier = identifier
self.is_morph = False
self.reflex_id = reflex_id
self.permanent_attribute_name = permanent_attribute_name

@property
def request(self):
Expand All @@ -31,3 +35,42 @@ def request(self):
request.session = self.consumer.scope['session']
request.user = self.consumer.scope['user']
return request

def get_channel_id(self):
'''
Override this to make the reflex send to a different channel
other than the session_key of the user
'''
return self.session.session_key

def morph(self, selector='', html='', template='', context={}):
"""
If a morph is executed without any arguments, nothing is executed
and the reflex won't send over any data to the frontend.
"""
self.is_morph = True
no_arguments = [not selector, not html, (not template and not context)]
if all(no_arguments) and not selector:
# an empty morph, nothing is sent ever.
return

if html:
html = html
elif isinstance(template, Template):
html = template.render(context)
else:
html = render_to_string(template, context)

broadcaster = Channel(self.get_channel_id(), identifier=self.identifier)
broadcaster.morph({
'selector': selector,
'html': html,
'children_only': True,
'permanent_attribute_name': self.permanent_attribute_name,
'stimulus_reflex': {
'morph': 'selector',
'reflexId': self.reflex_id,
'url': self.url
}
})
broadcaster.broadcast()
5 changes: 5 additions & 0 deletions tests/example/reflexes/example_reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ def increment(self, step=1):
class DecrementReflex(Reflex):
def decrement(self, step=1):
self.session['otherCount'] = int(self.element.dataset['count']) - step


class MorphReflex(Reflex):
def morph_me(self):
self.morph('#morph', 'I got morphed!')
4 changes: 4 additions & 0 deletions tests/example/templates/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@
>Decrement</button>
<div id="counter-2">{{ otherCount }}</div>
</div>

<button id="morph-button" data-reflex="click->MorphReflex#morph_me">Morph me</button>
<span id="morph"></span>

</body>

0 comments on commit 9cfcff7

Please sign in to comment.