On Jan 12, 4:20 pm, Adam Warski <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> wrote:
> >>>>>> On Jan 10, 5:20 pm, Adam Warski <[email protected]> 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 [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/liftweb?hl=en.