Skip to content

Commit

Permalink
Merge pull request #62 from 40ants/feat/form-submitter
Browse files Browse the repository at this point in the history
Feat/form submitter
  • Loading branch information
svetlyak40wt authored Jul 28, 2024
2 parents c4e86df + a3041d6 commit deed717
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 34 deletions.
144 changes: 144 additions & 0 deletions examples/simple-form.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
(uiop:define-package #:reblocks-examples/simple-form
(:use #:cl)
(:import-from #:reblocks/app
#:defapp)
(:import-from #:reblocks/server)
(:import-from #:reblocks/html
#:with-html)
(:import-from #:reblocks/widget
#:update
#:defwidget)
(:import-from #:reblocks-ui/form
#:render-form-and-button)
(:documentation "This example demonstrating a simple web-page with a form having multiple fields and submit button.
When user pushes the submit button, entered data are processed on the backend and are rendered in read-only mode."))
(in-package #:reblocks-examples/simple-form)


(defapp simple-form
:prefix "/")


(defun start (&key (port 8080) (interface "localhost") )
(reblocks/server:stop interface
port)
(reblocks/server:start :apps 'simple-form
:port port
:interface interface))


(defwidget form-page (reblocks-ui:ui-widget)
((edit-mode :initform t
:accessor edit-mode-p)
(name :initform ""
:accessor name)
(name-error :initform nil
:accessor name-error)
(profession :initform ""
:accessor profession)
(bio :initform ""
:accessor bio)))


(defmethod reblocks/page:init-page ((app simple-form) (url-path string) expire-at)
(check-type expire-at (or null local-time::timestamp))
(make-instance 'form-page))


(defmethod reblocks/widget:render ((widget form-page))
(labels ((to-edit-mode (&rest args)
(log:info "Switching to edit mode" args)
(setf (edit-mode-p widget)
t)
(update widget))
(valid-p (data)
(let ((has-errors nil))
(setf (name-error widget)
nil)

(when (or (null (getf data :name))
(string= "" (getf data :name)))
(setf has-errors t)
(setf (name-error widget)
"Name is required field"))
(values (not has-errors))))
(handle-submission (&rest args &key submit-button &allow-other-keys)
(log:info "Form was submitted" args)
(cond
((and (not (null submit-button))
(string-equal submit-button
"cancel"))
(setf (edit-mode-p widget)
nil))
;; Saving
(t
(when (valid-p args)
(setf (name widget)
(getf args :name))
(setf (profession widget)
(getf args :profession))
(setf (bio widget)
(getf args :bio))
(setf (edit-mode-p widget)
nil))))
(update widget)))

(with-html
(:div :style "width: 50%; margin: 4rem auto"
(cond
((edit-mode-p widget)
(reblocks-ui/form:with-html-form (:post #'handle-submission)
(when (name-error widget)
(:div :class "label alert"
(name-error widget)))
(:input :type "text"
:name "name"
:placeholder "Enter your name"
:value (name widget))

(:select :name "profession"
(loop for profession in '("Nothing special"
"Software Developer"
"Designer"
"Product Manager")
for selected = (string= (profession widget)
profession)
do (:option :selected selected
profession)))
(:textarea :name "bio"
(bio widget))

;; This works:
(:button :type "submit"
:class "button"
:name "submit-button"
:value "submit"
"Submit")
(:button :class "button secondary"
:name "submit-button"
:value "cancel"
"Cancel")


;; And this should work too:
;; (:input :class "button"
;; :name "submit-button"
;; :type "submit"
;; :value "submit")
;; (:input :class "button secondary"
;; :type "submit"
;; :name "submit-button"
;; :value "cancel")
))
(t
(:dl
(:dt "Name")
(:dd (name widget))
(:dt "Profession")
(:dd (profession widget))
(:dt "Bio")
(:dd (bio widget)))

(render-form-and-button "Edit"
#'to-edit-mode)))))))
11 changes: 6 additions & 5 deletions src/actions.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@

(in-readtable pythonic-string-syntax)

(defvar *js-default-action* "return initiateAction(\"~A\"~@[, ~A~])")
(defvar *js-default-form-action* "return initiateFormAction(\"~A\", event, this)")


(defgeneric on-missing-action (app action-name)
(:documentation "Must be overridden by application to prevent default
Expand Down Expand Up @@ -227,13 +230,12 @@ situation (e.g. redirect, signal an error, etc.)."))
(cond
(args
(let ((options (dict "args" args)))
(format nil "initiateAction(\"~A\", ~A); return false;"
(format nil *js-default-action*
action-code
(yason:with-output-to-string* ()
(yason:encode options)))))
(t
(format nil "initiateAction(\"~A\"); return false;"
action-code)))))
(format nil *js-default-action* action-code)))))


(defun make-js-form-action (action)
Expand All @@ -244,8 +246,7 @@ situation (e.g. redirect, signal an error, etc.)."))
On form submit given action will be executed and all input values
will be passed as arguments."
(let* ((action-code (make-action action)))
(format nil "initiateFormAction(\"~A\", $(this)); return false;"
action-code)))
(format nil *js-default-form-action* action-code)))


(defun get-session-action (action-name)
Expand Down
17 changes: 17 additions & 0 deletions src/doc/changelog.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ASDF"
"MOP"
"JS"
"UI"
"GET"
"POST"
"LOADED"
Expand All @@ -34,6 +35,22 @@
"REBLOCKS/SESSION:INIT")
:external-links (("Ultralisp" . "https://ultralisp.org"))
:external-docs ("https://40ants.com/log4cl-extras/"))
(0.60.0 2024-07-28
"""
Incompatible Changed
====================
* JS function initiateFormAction now accepts four arguments instead of three.
Second argument should be an event, third - the form object and fourth an options object.
This change allows to submit form using different buttons having their values passed to a
lisp callback.
An `examples/simple-form.lisp` file was added to demonstrate how this feature works when
form has Submit and Cancel buttons.
**Note:** if you've used Reblocks-UI system, then you'll need to update it to the newest version.
""")
(0.59.0 2024-01-26
"""
Changed
Expand Down
41 changes: 12 additions & 29 deletions src/js/jquery/jquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,6 @@ jQuery.fn.focusFirstElement = function(){
}
};

jQuery.fn.serializeObjectWithSubmit = function(){
var ret = this.serializeObject();
var submitElement = jQuery(this).find('input[type=submit][clicked=true]');
ret[submitElement.attr('name')] = submitElement.val();
submitElement.attr('clicked', null);

return ret;
};

// Utilities
function updateElementBody(element, newBody) {
element.update(newBody);
Expand All @@ -59,15 +50,6 @@ function updateElement(element, newElement) {
element.replaceWith($newElement);
}

function applySubmitClickEvent() {
jQuery("form input[type=submit]")
.unbind('click.reblocks-submit-event')
.bind('click.reblocks-submit-event', function() {
$("input[type=submit]", $(this).parents("form")).removeAttr("clicked");
$(this).attr("clicked", "true");
});
}

function selectionEmpty() {
if(document.getSelection) {
return document.getSelection() == "";
Expand Down Expand Up @@ -295,7 +277,6 @@ function onActionSuccess(json){
commands.forEach(processCommand);

execJsonCalls(json['on-load']);
applySubmitClickEvent();
}

function execJsonCalls (calls) {
Expand Down Expand Up @@ -344,13 +325,13 @@ function initiateAction(actionCode, options) {
var url = options.url || getActionUrl(actionCode);
var on_success = options.on_success;
var on_failure = options.on_failure || onActionFailure;

args.action = actionCode;

// This logging is for debug only
// log('Fireing action', actionCode);
// log('with options', options);

var ajax_options = {
type: method,
success: function(first, second, third) {
Expand All @@ -366,17 +347,24 @@ function initiateAction(actionCode, options) {
ajax_options.data = JSON.stringify(args)
}
jQuery.ajax(url, ajax_options);

return false;
}

function initiateFormAction(actionCode, form, options) {
// Hidden "action" field should not be serialized on AJAX
function initiateFormAction(actionCode, event, eventThis, options) {
const form = $(eventThis);
var options = options || {};
var action_arguments = form.serializeObjectWithSubmit();
var action_arguments = form.serializeObject();
if (["BUTTON", "INPUT"].includes(event.submitter && event.submitter.tagName)) {
action_arguments[event.submitter.name] = event.submitter.value;
}
delete(action_arguments['action']);

options['args'] = Object.assign({}, options.args || {}, action_arguments);
options['method'] = options['method'] || form.attr('method');
initiateAction(actionCode, options);

return false;
}

function disableIrrelevantButtons(currentButton) {
Expand Down Expand Up @@ -512,11 +500,6 @@ $ = function(id){
}
};

jQuery(function(){
applySubmitClickEvent();
});


window.Event.observe = function(obj, evtType, func){
if(obj == window && evtType == 'load'){
jQuery(func);
Expand Down

0 comments on commit deed717

Please sign in to comment.