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 Jul 18, 2021
1 parent 455c71a commit 8177333
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 2 deletions.
8 changes: 8 additions & 0 deletions cypress/integration/websocket_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,12 @@ describe("Integration tests", () => {

cy.get('#user-reflex').should('have.text', 'test_user')
})

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!')
})
})
19 changes: 18 additions & 1 deletion sockpuppet/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,30 @@ def reflex_message(self, data, **kwargs):
url = data["url"]
selectors = data["selectors"] if data["selectors"] else ["body"]
target = data["target"]
identifier = data["identifier"]
reflex_class_name, method_name = target.split("#")
arguments = data["args"] if data.get("args") else []
params = dict(parse_qsl(data["formData"]))
element = Element(data["attrs"])
if not self.reflexes:
self.load_reflexes()

# 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_class_name)
reflex = ReflexClass(
self, url=url, element=element, selectors=selectors, params=params
self, url=url,
element=element,
selectors=selectors,
identifier=identifier,
params=params,
reflex_id=data['reflexId'],
permanent_attribute_name=permanent_attribute_name
)
self.delegate_call_to_reflex(reflex, method_name, arguments)
except TypeError as exc:
Expand Down Expand Up @@ -218,6 +231,10 @@ def reflex_message(self, data, **kwargs):
logger.debug("Reflex took %6.2fms", (time.perf_counter() - start) * 1000)

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
46 changes: 45 additions & 1 deletion sockpuppet/reflex.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
from django.urls import resolve
from urllib.parse import urlparse

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, params):
def __init__(
self, consumer, url, element, selectors, params, 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"]
self.params = params
self.context = {}
self.identifier = identifier
self.is_morph = False
self.reflex_id = reflex_id
self.permanent_attribute_name = permanent_attribute_name

def __repr__(self):
return f"<Reflex url: {self.url}, session: {self.get_channel_id()}>"
Expand Down Expand Up @@ -68,3 +80,35 @@ def request(self):
def reload(self):
"""A default reflex to force a refresh"""
pass

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 @@ -31,3 +31,8 @@ class UserReflex(Reflex):
def get_user(self):
context = self.get_context_data()
self.user_reveal = context['object']


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 @@ -37,4 +37,8 @@
<span id="text-output">{{ text_output }}</span>

<span id="stimulus-reflex">{{ stimulus_reflex }}</span>

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

</body>

0 comments on commit 8177333

Please sign in to comment.