html: Mirror gadget values in and out of the DOM

Currently only supporting text input, password input, and hidden
input, along with text areas, this mirrors the text values in
and out of the DOM, allowing JS to adjust the gadget values and
for the gadget values to be interrogated from JS.

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
This commit is contained in:
Daniel Silverstone 2019-08-04 22:07:42 +01:00
parent e78dc4f5a9
commit 05c6ee02d9
6 changed files with 169 additions and 1 deletions

View File

@ -377,4 +377,12 @@ static inline bool box_is_first_child(struct box *b)
return (b->parent == NULL || b == b->parent->children);
}
/**
* Retrieve the box for a dom node, if there is one
*
* \param node The DOM node
* \return The box if there is one
*/
struct box *box_for_node(struct dom_node *node);
#endif

View File

@ -211,7 +211,8 @@ static const box_type box_map[] = {
BOX_NONE /*CSS_DISPLAY_NONE*/
};
static inline struct box *box_for_node(dom_node *n)
/* Exported function, see box.h */
struct box *box_for_node(dom_node *n)
{
struct box *box = NULL;
dom_exception err;

View File

@ -223,6 +223,9 @@ void form_free_control(struct form_control *control)
free(control->name);
free(control->value);
free(control->initial_value);
if (control->last_synced_value != NULL) {
free(control->last_synced_value);
}
if (control->type == GADGET_SELECT) {
struct form_option *option, *next;
@ -275,6 +278,10 @@ void form_free_control(struct form_control *control)
}
}
if (control->node_value != NULL) {
dom_string_unref(control->node_value);
}
free(control);
}
@ -2235,4 +2242,108 @@ void form_gadget_update_value(struct form_control *control, char *value)
/* Do nothing */
break;
}
/* Finally, sync this with the DOM */
form_gadget_sync_with_dom(control);
}
/* Exported API, see form_internal.h */
void
form_gadget_sync_with_dom(struct form_control *control)
{
dom_exception exc;
dom_string *value = NULL;
bool changed_dom = false;
if (control->syncing ||
(control->type != GADGET_TEXTBOX &&
control->type != GADGET_PASSWORD &&
control->type != GADGET_HIDDEN &&
control->type != GADGET_TEXTAREA)) {
/* Not a control we support, or the control is already
* mid-sync so we don't want to disrupt that
*/
return;
}
control->syncing = true;
/* If we've changed value, sync that toward the DOM */
if ((control->last_synced_value == NULL && control->value[0] != '\0') ||
(control->last_synced_value != NULL && strcmp(control->value, control->last_synced_value) != 0)) {
char *dup = strdup(control->value);
if (dup == NULL) {
goto out;
}
if (control->last_synced_value != NULL) {
free(control->last_synced_value);
}
control->last_synced_value = dup;
exc = dom_string_create((uint8_t *)(control->value),
strlen(control->value), &value);
if (exc != DOM_NO_ERR) {
goto out;
}
if (control->node_value != NULL) {
dom_string_unref(control->node_value);
}
control->node_value = value;
value = NULL;
if (control->type == GADGET_TEXTAREA) {
exc = dom_html_text_area_element_set_value(control->node, control->node_value);
} else {
exc = dom_html_input_element_set_value(control->node, control->node_value);
}
if (exc != DOM_NO_ERR) {
goto out;
}
changed_dom = true;
}
/* Now check if the DOM has changed since our last go */
if (control->type == GADGET_TEXTAREA) {
exc = dom_html_text_area_element_get_value(control->node, &value);
} else {
exc = dom_html_input_element_get_value(control->node, &value);
}
if (exc != DOM_NO_ERR) {
/* Nothing much we can do here */
goto out;
}
if (!dom_string_isequal(control->node_value, value)) {
/* The DOM has changed */
if (!changed_dom) {
/* And it wasn't us */
char *value_s = strndup(
dom_string_data(value),
dom_string_byte_length(value));
char *dup = NULL;
if (value_s == NULL) {
goto out;
}
dup = strdup(value_s);
if (dup == NULL) {
free(value_s);
goto out;
}
free(control->value);
control->value = value_s;
free(control->last_synced_value);
control->last_synced_value = dup;
if (control->type != GADGET_HIDDEN &&
control->data.text.ta != NULL) {
textarea_set_text(control->data.text.ta,
value_s);
}
}
control->node_value = value;
value = NULL;
}
out:
if (value != NULL)
dom_string_unref(value);
control->syncing = false;
}

View File

@ -72,6 +72,8 @@ struct image_input_coords {
/** Form control. */
struct form_control {
void *node; /**< Corresponding DOM node */
struct dom_string *node_value; /**< The last value sync'd with the DOM */
bool syncing; /**< Set if a DOM sync is in-progress */
struct html_content *html; /**< HTML content containing control */
form_control_type type; /**< Type of control */
@ -81,6 +83,7 @@ struct form_control {
char *name; /**< Control name */
char *value; /**< Current value of control */
char *initial_value; /**< Initial value of control */
char *last_synced_value; /**< The last value sync'd to the DOM */
bool disabled; /**< Whether control is disabled */
struct box *box; /**< Box for control */
@ -261,4 +264,18 @@ void form_radio_set(struct form_control *radio);
void form_gadget_update_value(struct form_control *control, char *value);
/**
* Synchronise this gadget with its associated DOM node.
*
* If the DOM has changed and the gadget has not, the DOM's new value is
* imported into the gadget. If the gadget's value has changed and the DOM's
* has not, the gadget's value is pushed into the DOM.
* If both have changed, the gadget's value wins.
*
* \param control The form gadget to synchronise
*
* \note Currently this will only synchronise input gadgets (text/password)
*/
void form_gadget_sync_with_dom(struct form_control *control);
#endif

View File

@ -829,6 +829,23 @@ dom_default_action_DOMNodeInsertedIntoDocument_cb(struct dom_event *evt, void *p
}
}
/* Deal with input elements being modified by resyncing their gadget
* if they have one.
*/
static void html_texty_element_update(html_content *htmlc, dom_node *node)
{
struct box *box = box_for_node(node);
if (box == NULL) {
return; /* No Box (yet?) so no gadget to update */
}
if (box->gadget == NULL) {
return; /* No gadget yet (under construction perhaps?) */
}
form_gadget_sync_with_dom(box->gadget);
/* And schedule a redraw for the box */
html__redraw_a_box(htmlc, box);
}
/* callback for DOMSubtreeModified end type */
static void
dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw)
@ -861,6 +878,9 @@ dom_default_action_DOMSubtreeModified_cb(struct dom_event *evt, void *pw)
case DOM_HTML_ELEMENT_TYPE_STYLE:
html_css_update_style(htmlc, (dom_node *)node);
break;
case DOM_HTML_ELEMENT_TYPE_TEXTAREA:
case DOM_HTML_ELEMENT_TYPE_INPUT:
html_texty_element_update(htmlc, (dom_node *)node);
default:
break;
}

View File

@ -391,7 +391,18 @@ parse_input_element(struct form *forms, dom_html_input_element *input)
control = NULL;
goto out;
}
control->last_synced_value = strdup(control->value);
if (control->last_synced_value == NULL) {
form_free_control(control);
control = NULL;
goto out;
}
control->node_value = dom_string_ref(ds_value);
}
/* Force the gadget and DOM to be in sync */
form_gadget_sync_with_dom(control);
}
if (form != NULL && control != NULL)