Hello,

the problem is that some form elements are updated, then the function is 
called, and then the rest of the form elements are updated.
My code is more or less something like this:

    def editElement(elementTemplate: NodeSeq): NodeSeq = {
      container.elements.flatMap { element: Element =>
          bind("element", elementTemplate,
            "name" -> { println("bind element name " + element.name); 
SHtml.text(element.name.is,
              (s: String) => {println("set element name " + s); 
element.name(s); }) }
            )
      }
    }

    bind("cont", containerTemplate,
      "name" -> { println("bind container name " + conf.name); 
SHtml.text(cont.name.is,
              (s: String) => {println("set container name " + s); cont.name(s); 
}) },
      "elements" -> editElement _,
      "addElement" -> ajaxButton(Text("Add"), "edit_form"id, () => { 
println("executing function"); cont.addElement; reDraw })
      )

And the output, when I submit a form where 1 element is already added is:

09:04:23,450 INFO  [STDOUT] set container name a
09:04:23,474 INFO  [STDOUT] executing function
09:04:23,474 INFO  [STDOUT] bind container name a
09:04:23,475 INFO  [STDOUT] bind element name 0
09:04:23,476 INFO  [STDOUT] bind element name 1
09:04:23,478 INFO  [STDOUT] set element name 0a

So as you can see first cont.name is set, then the button function is executed, 
and later the element.names are set.

I can try creating a small lift app which would demo this if you'd like.
Also, I'm using 1.1-M8. Maybe I should try some newer version? (2.0-SNAPSHOT? 
1.1-SNAPSHOT?)

Adam

On Jan 12, 2010, at 6:03 PM, Marius wrote:

> I must have misread your post. I did test the ajaxButton above (with
> your corrections) and the behaviour is correct. Form field functions
> are invoked first and then your ajax function provided to ajaxButton.
> Thus this is a good way for adding submit functions for ajax form
> without the need of using hidden fields and I'll promote this for
> addition in Shtml (probably with slight modifications). I think the
> method name should be "ajaxSubmit"
> 
> I don't quite get why you're saying this is a problem. What does step
> 3 needs to accomplish? . all form fields functions are called (except
> if you have a Shtml.submit because form serialization does not include
> submits). Then you function is invoked and the response is sent to
> client.
> 
> You mentioned that you just need to add multiple buttons for a ajax
> form ... this version of ajaxButton does just that. Can you please
> clarify your used case for for for those 3 steps? ...
> 
> Br's,
> Marius
> 
> 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 :)
>> 
>> 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.
> 
> 

-- 
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