On Jan 12, 4:20 pm, Adam Warski <a...@warski.org> wrote:
> Hello,
>
> this *almost* works :).
>
> I modified your code a bit and now I have:
>
> def ajaxButton(text: NodeSeq, formId: String, func: () => JsCmd, attrs: 
> (String, String)*): Elem = {
>     attrs.foldLeft(fmapFunc(contextFuncBuilder(func))(name =>
>             <button onclick={makeAjaxCall(JsRaw(
>               LiftRules.jsArtifacts.serialize(formId).toJsCmd + " + " + 
> Str("&" + name + "=true").toJsCmd)).toJsCmd +
>                     "; return false;"}>{text}</button>))(_ % _)
>   }
>
> Now the form submits and the right function is executed on the server, and 
> the form is redrawn in the browser.
>
> However, the problem is in the ordering of operations.
> The sequence basically is:
> (1) update some elements of the form
> (2) execute the function
> (3) update the rest of the elements of the form
>
> The problem of course is that (2) returns the new content of the form (a 
> SetHtml JsCmd), generated basing on state without all fields updated.
> I don't quite yet get the rule deciding which fields get updated before 
> calling the function, and which after.
> One thing I noticed is that if I move the field that is bound first (in 
> bind(...)) to be the last field, it gets moved from group (1) to (3).
>
> Also, I thought that maybe the ordering of POST values matters, but swapping 
> Str("&" + name + "=true").toJsCmd and 
> LiftRules.jsArtifacts.serialize(formId).toJsCmd doesn't have any effect.
>
> I tried the form many times and always get the same behaviour, so the (1) vs. 
> (3) division seems to be deterministic :)

I think there is a sorting involved when execution the user function
but I'm not sure why. I need to give a second look into Lift's guts. I
agree ordering is important here as you want your function executed
after all form functions have been executed.

>
> Adam
>
> On Jan 11, 2010, at 10:58 PM, Marius wrote:
>
> > Adam I was thinking of a slightly different approach that does not
> > involve hidden fields:
>
> > Say you have your current form with SHtml.text, checkboxes or whatever
> > have you:
>
> > then your ajax buttons (outside the form) like:
>
> >  def ajaxButton(text: NodeSeq, formId: String, func: () => JsCmd,
> > attrs: (String, String)*): Elem = {
> >    attrs.foldLeft(fmapFunc(contextFuncBuilder(SFuncHolder(func)))
> > (name =>
> >            <button onclick={makeAjaxCall(JsRaw
> > (LiftRules.jsArtifacts.serialize(formId) + "&" + name.encJs +
> > "=_")).toJsCmd +
> >                    "; return false;"}>{text}</button>))(_ % _)
> >  }
>
> > I haven't tested though but you get the idea ... When we do the ajax
> > call, we serialize the form and add the name parameter as well. This
> > will cause your field functions to be called, and at the end you
> > ajaxButton function to be called. Inside func function your RequestVar
> > should be preserved due to contextFuncBuilde call.
>
> > Please let me know if this works. If it does we should probably add it
> > to SHtml.
>
> > Br's,
> > Marius
>
> > On Jan 11, 10:54 pm, Adam Warski <a...@warski.org> wrote:
> >> Hello,
>
> >> trying the solution a bit more I came into another problem which I can't 
> >> solve elegantly.
>
> >> The solution below works nicely for an "add" button, but a "delete" button 
> >> causes more problems: the problem is that with "delete", you must know 
> >> which element should get deleted.
>
> >> In a no-ajax solution, it is enough to do:
>
> >> elements.flatMap { element: Element =>
> >>    bind("element", element Template,
> >>       "name" -> element.name.toForm,
> >>       "delete" -> submit("Delete", () => { elements -= element })
> >>    )
>
> >> }
>
> >> which is very nice and easy, as the element to delete gets captured in a 
> >> closure.
> >> But with ajax, and a hidden field used to hold the name of the operation 
> >> to dispatch, this gets pretty complex: I now need to somehow encode the 
> >> element to delete (or create a map from some unique identifier to closures 
> >> which hold the delete methods), so that I can set this as a value of the 
> >> hidden field. Then in the function passed to SHtml.hidden, I need to 
> >> decode it back to find the right element. Isn't it a bit of what Lift 
> >> already does when creating forms?
>
> >> But I still have the feeling that maybe I'm approaching the whole problem 
> >> from the wrong end, after all, I just want to create an ajax-enabled list 
> >> of input fields with add and delete operations :)
>
> >> Adam
>
> >>> On Jan 11, 1:09 pm, Adam Warski <a...@warski.org> wrote:
> >>>> Hello,
>
> >>>> this almost works :).
>
> >>>> Right now in my form I have a hidden element where the type of the 
> >>>> operation to execute will be set:
> >>>> <input type="hidden" id="operation_id" name="operation_id" value="" />
> >>>> (the name is needed for jquery to set the value, and the id so that I 
> >>>> can later read the value using S).
>
> >>>> I bind the button as following:
>
> >>>> "addElement" -> <button onclick={((JqId(Str("operation_id")) >> 
> >>>> JqAttr("value", Str("add")))
> >>>>               & SHtml.submitAjaxForm("elements_edit")).toJsCmd+" return 
> >>>> false;"}>{Text("Add element")}</button>,
>
> >>>> and add a hidden field to the whole form to do the processing:
>
> >>>> bind(
> >>>> ...
> >>>> )  ++ SHtml.hidden(() => {
> >>>>       val operationId = S.param("operation_id")
> >>>>       operationId.map { opId => opId match {
> >>>>         case "add" => elements += new Element
> >>>>         case _ => println("Unknown operation: " + opId)
> >>>>       } }
> >>>>       reDraw
> >>>>     })
>
> >>>> where elements is a RequestVar object.
>
> >>>> However for some reason, when I click the button, in the callback I get 
> >>>> a new elements RequestVar (so it's initialized to an initial value) and 
> >>>> moreover, nothing gets redrawn on the page. What is also quite weird is 
> >>>> that the RequestVar is re-initialized, but the snippet instance stays 
> >>>> the same. Any ideas? :)
>
> >>> Yes I think so. You have a regular form with fields like: (SHtml.text.
> >>> SHtml.hidden etc. right? RequestVars are kept around into a "snapshot-
> >>> restorer" only for ajax functions (like for ajaxText etc) and not for
> >>> regular fields forms. Thus in the Scala function of a SHtml.ajaxText
> >>> you'll see the RequestVar set when the page was rendered because its
> >>> state is preserved on purpose by lift. However for regular fields this
> >>> is not happening. So there is a difference between an ajax-form and
> >>> ajax fields. Even your form is sent via ajax, the fields are regular
> >>> fields not ajax fields (... ajax fields do not even need to live
> >>> inside a form). Therefore for your case the RequestVar's are not
> >>> preserved and they are renewed when the request comes in. You can keep
> >>> around that state using a SessionVar.
>
> >>>> I thought that my use-case would be quite common in lift: I just want to 
> >>>> add a couple of buttons to my form which execute different actions.
>
> >>> Which is very easy to do. But your problem not is how you maintain
> >>> your specific state (the elements)
>
> >>>> Thanks for the help!
> >>>> Adam
>
> >>>> On Jan 10, 2010, at 6:03 PM, Marius wrote:
>
> >>>>> Sorry I think the syntax would be (I haven't tested it though):
>
> >>>>> <button onclick={((JqId("hidden_field_id") >> JqAttr("value", "add"))
> >>>>> & SHtml.submitAjaxForm(form_ID)).toJsCmd}>add</button>
> >>>>> <button onclick={((JqId("hidden_field_id") >> JqAttr("value",
> >>>>> "edit")) & SHtml.submitAjaxForm(form_ID)).toJsCmd}>edit</button>
> >>>>> <button onclick={((JqId("hidden_field_id") >> JqAttr("value",
> >>>>> "delete")) & SHtml.submitAjaxForm(form_ID)).toJsCmd}>delete</button>
>
> >>>>> Br's,
> >>>>> Marius
>
> >>>>> On Jan 10, 6:58 pm, Marius <marius.dan...@gmail.com> wrote:
> >>>>>> On Jan 10, 5:20 pm, Adam Warski <a...@warski.org> wrote:
>
> >>>>>>> Hello,
>
> >>>>>>>> ajaxButton("Press me would ya'?", SHtml.submitAjaxForm
> >>>>>>>> (form_ID).toJsCmd, (some) => {
>
> >>>>>>>> do your stuff here
>
> >>>>>>>> })
>
> >>>>>>> Looking at the source code I think this might work, but I'm having 
> >>>>>>> trouble constructing the correct expression to pass to ajaxButton. 
> >>>>>>> The method signature requires a Call instance, and 
> >>>>>>> SHtml.submitAjaxForm results in a command (JsCmd). Is it possible to 
> >>>>>>> somehow combine the two?
>
> >>>>>> I was referring to this signature:
>
> >>>>>> def ajaxButton(text: NodeSeq, jsExp: JsExp, func: String => JsCmd,
> >>>>>> attrs: (String, String)*): Elem
>
> >>>>>> and not
>
> >>>>>> def ajaxButton(text: NodeSeq, jsFunc: Call, func: () => JsCmd, attrs:
> >>>>>> (String, String)*): Elem
>
> >>>>>> jsExp will be called before sending the actual ajax. But this may be a
> >>>>>> bit problematic if your jsExp sends the ajaxForm the ajax request is
> >>>>>> processed asynchronously at js level meaning the the button ajax
> >>>>>> request may be send before the actual ajax form processing is done.
> >>>>>> I'm not sure if this fits your needs
>
> >>>>>>> Or maybe there is some other, lift-idomatic way to solve my problem?
> >>>>>>> I want to create a form with a list of elements, with three ajax 
> >>>>>>> buttons: add, remove and save (adding/removing a row and persisting 
> >>>>>>> the list)
>
> >>>>>> The first solution I described involving hidden fields will definitely
> >>>>>> work. I don't think you need to do 2 ajax calls (one for the form and
> >>>>>> one for the button) but piggy back the button information into a
> >>>>>> hidden field and only submit the form:
>
> >>>>>> Perhaps something like:
>
> >>>>>> <button onclick={(JqId("hidden_field_id") >> JqAttr("value", "add")) +
> >>>>>> + SHtml.submitAjaxForm(form_ID).toJsCmd}>blah</button>
>
> >>>>>>> By the way, I think there's a small inconsistency; there are 7 
> >>>>>>> overloaded variants for ajaxButton:
>
> >>>>>>> ajaxButton(text: NodeSeq, func: () => JsCmd, attrs: (String, 
> >>>>>>> String)*): Elem
> >>>>>>> ajaxButton(text: String, func: () => JsCmd, attrs: (String, 
> >>>>>>> String)*): Elem
>
> >>>>>>> ajaxButton(text: NodeSeq, jsFunc: Call, func: () => JsCmd, attrs: 
> >>>>>>> (String, String)*): Elem
> >>>>>>> ajaxButton(text: String, jsFunc: Call, func: () => JsCmd, attrs: 
> >>>>>>> (String, String)*): Elem
>
> >>>>>>> ajaxButton(text: NodeSeq, jsExp: JsExp, func: String => JsCmd, attrs: 
> >>>>>>> (String, String)*): Elem
>
> >>>>>>> and the last one doesn't have a text: String counterpart. Also the 
> >>>>>>> javadoc for the last variant is missing information on what's "jsExp" 
> >>>>>>> and what's the argument passed to "func". My guess would be that 
> >>>>>>> jsExp is evaluated and the result passed to func on the server?
>
> >>>>>> Yes jsExp is being
>
> ...
>
> read more »
-- 
You received this message because you are subscribed to the Google Groups 
"Lift" group.
To post to this group, send email to lift...@googlegroups.com.
To unsubscribe from this group, send email to 
liftweb+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/liftweb?hl=en.


Reply via email to