gitana / alpaca

Alpaca provides the easiest way to generate interactive HTML5 forms for web and mobile applications. It uses JSON Schema and simple Handlebars templates to generate great looking, dynamic user interfaces on top of Twitter Bootstrap, jQuery UI, jQuery Mobile and HTML5.
http://www.alpacajs.org
Other
1.29k stars 371 forks source link

No option to set the Content-Type? #569

Closed ssgtcookie closed 6 years ago

ssgtcookie commented 6 years ago

It seems to be that there is no option to specify the content-type header of the submitted alpaca form. All forms are submitted as "application/x-www-form-urlencoded" which is a little strange to my taste since Alpaca is all about json. It's also not listed in the documentation.

Does anyone know why this is and can this option be added?

Laravel states the following in it's documentation "When sending JSON requests to your application, you may access the JSON data via the input method as long as the Content-Type header of the request is properly set to application/json".

ambischof commented 6 years ago

As far as I know, alpaca only handles options for native form submission, for which 'application/json' is not valid

If you want to use json, you'll need an ajax request.

ssgtcookie commented 6 years ago

Yea technicly it can be done with ajax, but that's patching something that isnt there in the first place. The serialisation page shows that alpaca can generate perfect json, but than you can't send it in that format to the server. It feels a little bit like having a car with some many fancy feautures, that you can't use while driving it. :)

@ambischof do you know if it's documentated somewhere how to use alpaca in combination with the json ajax header? I know how to make an ajax call with the json content-type, but not how to gather the form input and send that as ajax call with json content-type headers.

ambischof commented 6 years ago

Something like:

var data = $('#my-form').alpaca().getValue();

$.ajax('/endpoint', {
  contentType: 'application/json',
  data: JSON.stringify(data)
})

alternatively:

$('#my-form').alpaca({
  schema: {..},
  options: {
    form: {
      buttons: {
        submit: {
          type: 'button',
          click: function(){
            var val = this.getValue();
            // Repeat ajax call from above.
          }
        }
      }
    }
  }
})

Now, this is from memory, so it may not be 100% accurate, but I have done this many times and this is more or less how you do it.

ssgtcookie commented 6 years ago

@ambischof thank you very much for your reply, this works. I still think that tho that creating a "content-type" json property might be a good thing to do as an extra optional option to the JSON. Using some javascript script in the JSON itself (alternative solution you gave), breaks the JSON valid status.

I have to dig into the alpaca JS lib to research how everything works to create a merge request for this. I'm okay doing that but I'm not sure if that merge request will be accepted by CloudCMS.

ambischof commented 6 years ago

There already exists the ability to set the tire header as shown here http://www.alpacajs.org/docs/api/forms.html but I think you're not realizing that that option is only for the native hml form submission, not an ajax request, for which 'application/json' won't work. Alpaca doesn't handle ajax submission.

ambischof commented 6 years ago

Sorry, won't let me fix my typos. I meant to say 'type header' and 'native html form submission'

seanvree commented 6 years ago

@ambischof I seond @ssgtcookie 's thoughts.

Where in the alpaca script does that first code snippet go?

I'm simply trying to send the data from the fields onsubmit, serialize it, and write to a backend .json file. This is ALL working except the output format in the .json file isn't in array format.

"John""McClane"

Here's my field:

<div id="field1"></div>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#field1").alpaca({
                "data": {
                    "firstName": "John",
                    "lastName": "McClane"
                },
                "schema": {
                    "title": "converting data to JSON:",
                    "type": "object",
                    "properties": {
                        "firstName": {
                            "type": "string",
                            "title": "First Name"
                        },
                        "lastName": {
                            "type": "string",
                            "title": "Last Name"
                        }
                    }
                },
                "options": {
                    "form": {

                        "attributes": {
                            "method": "post",
                            "action": "post_receiver.php"
                        },

                        "buttons": {
                            "submit": {
                                "title": "Serialize",
                                "click": function(e) {

                                    var promise = this.ajaxSubmit();
                                    var value = this.getValue();

                                    promise.done(function() {
                                        alert("Success" );
                                    });
                                    promise.fail(function() {
                                        alert("Error");
                                    });
                                    promise.always(function() {
                                        alert(JSON.stringify(value, null, " "));
                                    });

                                }
                            }
                        }
                    }
                }
            });

        });
    </script>

Now obviously I had to add a backend PHP ajax processor to catch the ajax request and write the data to the .json file, so that looks like this:

post_receiver.php:

<?php

    $fp = fopen('ajax.json', 'w');
        fwrite($fp, json_encode($_POST['firstName']));
        fwrite($fp, json_encode($_POST['lastName']));
    fclose($fp);

?>

And here's the output. You'll see the alert is in the proper format, but the ajax.json file is not:

image

BTW, this is a great product! First data tables project i found that is VERY informative and in plan HTML.

ambischof commented 6 years ago

Umm if I understand your question correctly, you'd want something like:

$('#my-form').alpaca({
  schema: {..},
  options: {
    form: {
      buttons: {
        submit: {
          type: 'button',
          click: function(){
            var val = this.getValue();
            var promise = $.ajax('/endpoint', {
              contentType: 'application/json',
              data: JSON.stringify(val)
            });

            promise.then( /* do something */)
          }
        }
      }
    }
  }
})

As for the issue with writing, I'm not exactly sure since it's been a while since I've done anything in PHP, but the issue is that you're encoding the first and last name separately when you really want to encode the object that holds them.

You probably want to do something more along the lines of: fwrite($fp, json_encode($_POST)); assuming that $_POST only includes those attributes.

seanvree commented 6 years ago

@ambischof

thanks for the response! You were right about the PHP, all good there.

I'm still a bit confused, does this :

              contentType: 'application/json',
              data: JSON.stringify(val)

replace the "attributes" section?

                        "attributes": {
                            "method": "post",
                            "action": "post_receiver.php"
                        }, 

also, what resources the "/endpoint" supposed to be

?  "var promise = $.ajax('/endpoint', {"

Is that the post_reecevier.php file or the json file?

ambischof commented 6 years ago

Lets's step back a bit, just to make sure we're on the same page. When you're submitting a form, there's generally two ways to do it.

First is the native html form submission which does not support json format. The attributes is a direct mapping to the <form method="post" action="post_reciever.php"> markup.

Second, which I was describing as an AJAX submission. The endpoint/url in ajax is generally the same as the action attribute used in the native form submission, so you'd use the post_receiver.php

The two are different and are mutually exclusive. There are advantages and disadvantages to each.

This is is out of the scope of Alpaca, so I'd advise you to look up some articles on this like https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

seanvree commented 6 years ago

@ambischof been messing with this for a couple hours. Yours method doesn't' work unless I"m doing something wrong, all I get is "undefined in the JSON file.

This is the best method I've found so far, as it writes perfectly to the .json file, however, if i try to define any args to the submit tag, like " e.preventDefault();" then it will go back to writing "undefined" to the json file.

Any hints? This is what I've got so far:

<div id="field7"></div>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#field7").alpaca({
                "schema": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "enum": ["Curl", "Ping"]
                            },
                            "check url": {
                                "type": "string",
                                "format": "uri"
                            },
                            "link url": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                },
                "options": {
                    "toolbarSticky": true,
                    "items": {
                        "fields": {
                            "type": {
                                "label": "Type",
                                "optionLabels": ["Curl", "Ping"]
                            },
                            "check url": {
                                "label": "Check URL"
                            },
                            "link url": {
                                "label": "Link URL"
                            }
                        }
                    },
                    "form": {
                        "attributes": {
                            "action": "post_receiver.php",
                            "method": "post",
                            "url": "settings.php",
                            "enctype": "multipart/form-data"
                        },

                        "buttons": {

                            "submit": {},

                            "view": {
                                "label": "View JSON",
                                "click": function() {

                                    var promise = this.ajaxSubmit();

                                    alert(JSON.stringify(this.getValue(), null, "  "));
                                }
                            }
                        }
                    }
                }
            });

        });
    </script>
ambischof commented 6 years ago

TBH, your issue isn't with the code itself, it's that you're having trouble coordinating the front end form submission with your server's handler.

It doesn't matter how you encode the data, or whether you send it via HTML form submission or ajax, your server just has to be set up to understand it.

Open up the developer console and look at what is actually being sent. Then on your server, log what is actually being received from the form and then log what you're trying to write to the file and see where things are breaking.

My advice: Don't waste your time just messing with the code. Form submission is complex enough to trip up even professional developers. You're much better off taking your time to read up on what all those options actually mean and debugging your process step by step.

ssgtcookie commented 6 years ago

@ambishof I don't agree on your last comment. Laravel will detect the json header and it will allow you to access the json properties natively in your code when it detects it as json. If you don't add the json header, you would have to add logic to test if the content is in json format and than convert it to an object. This is not very good looking. Using a json header for json content is best practice I eould say.

seanvree commented 6 years ago

umm, you took the words right out of my mouth @ssgtcookie .

It's really not that difficult to make a simple ajax request to post data @ambischof .

I literally used the example from the http://www.alpacajs.org/docs/fields/array.html docs and am simply trying to write the JSON data to a JSON file.

I've done this many many times. But, like @ssgtcookie said, the format that alpaca uses is NOT standard.

"action": "post_receiver.php",
 "method": "post",
 "url": "settings.php",
 "enctype": "multipart/form-data"

The example YOU posted is for a normal jquery ajax call which we all know and love. But that is NOT working with the the alpaca script.

EDIT:

I know this is OSS, so I don't want to sound "entitled" or bitching about something.

Constructive feedback would be to include some examples of HOW to process the data server side. You guys have AWESOME examples of how to use the code client-side but I don't see one example of how to actually process the data server side using ANY framework, PHP or otherwise.

ambischof commented 6 years ago

Perhaps I wasn't clear on what I meant. I wasn't trying to say that the encoding type doesn't matter, because it does. What I meant is that for most applications, you can use a variety of encodings, as long as the server and client are in sync. Depending on the use case, some choices are better than others and many times a particular method is the only way that will work, but that is outside the scope of @seanvree's issue, since you are struggling just to get the server to do what you need it to.

My real point for @seanvree at least, is that for the kind of form you had, just about any of the encodings and submission methods will work. I can only give so much advice knowing as little as I do about your server application and use case as I do, but I felt you were working too much about the encoding when the issue was something else entirely. I think the server either wasn't interpreting the submission correctly, or wasn't writing it to file correctly.

seanvree commented 6 years ago

@ambischof I really do appriciate you taking the time to respond.

PHP is probably the most widely used back-end processing framework. as @ssgtcookie pointed out, you guys have a great product that uses simple HTML to produce json data (as apposed to datatables which is VERY complicated) but if you cant' process that data on the back-end, I don't understand how it's really useful simply sending json data back to the browser? In most of your examples you send the json to the browser or to an anon processor, which works great, so how are you doing that? Again, a simple PHP or Larvel example would at least get your users moving in the right direction.

In my examples above I'm using the basic examples from your site and a VERY simple php script to process the ajax request on the back end. And with your ajax example, it doesn't' work. Your issues are filled with people having this exact same issue - unable to get alpaca to write json data in the proper format using a number of back-end frameworks.

Again, appreciate your time responding.

ambischof commented 6 years ago

Just to be clear, I'm not actually affiliated with Alpaca. I've made a few pull requests and help people out in the issues, but that's all.

I use Alpaca at work, and as a user, I'd say the main draw of alpaca IS just getting json back to the browser easily. I started using alpaca because forms are honestly a pain if you just use vanilla html/javascript. Trying to work with validation, conditional dependencies and creating forms for certain structures like arrays or objects just take too much time without a tool like Alpaca.

I'm not sure why ajax isn't working in your case, but you also haven't posted what you've tried.

seanvree commented 6 years ago

@ambischof Right, getting the forms back to the brwoser IS great. But, what's the point of having a form on your front-end for a user to use and then not being able to use the data they submit? I mean, some calcluator or something is great, but the whole point of a form is to gather data from the user, and process it some way on the back end, right? I mean, spitting data back to the user they just submitted in their browser looks great, but I just don't understand how that's useful to anyone?

I have posted what I've tried above but I'll post below. Not complicated, again, just using the simple examples from the website and a VERY basic PHP script. I haven't even been able to integrate this into my application, as I just wanted to see if it works.

Here is the basic form using the stock options from the alpaca website:

You're welcome to test for yourself:

https://seanvree.com/dev/alpaca/settings.default.php

This WORKS, writing to the JSON file, however, it doesn't keep the user on the page as you can't specify "return: false"

<div id="field7"></div>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#field7").alpaca({
                "schema": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "enum": ["Curl", "Ping"]
                            },
                            "check url": {
                                "type": "string",
                                "format": "uri"
                            },
                            "link url": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                },
                "options": {
                    "toolbarSticky": true,
                    "items": {
                        "fields": {
                            "type": {
                                "label": "Type",
                                "optionLabels": ["Curl", "Ping"]
                            },
                            "check url": {
                                "label": "Check URL"
                            },
                            "link url": {
                                "label": "Link URL"
                            }
                        }
                    },
                    "form": {
                        "attributes": {
                            "action": "post_receiver.php",
                            "method": "post",
                            "url": "settings.php",
                            "enctype": "multipart/form-data"
                        },

                        "buttons": {
                            "submit": {
                            },

                            "view": {
                                "label": "View JSON",
                                "click": function() {
                                    alert(JSON.stringify(this.getValue(), null, "  "));
                                }
                            }
                        }
                    }
                }
            });

        });
    </script>

backend php script :

<?php

    $fp = fopen('ajax.json', 'w');
        fwrite($fp, json_encode($_POST));
    fclose($fp);

?>

Output to ajax.json looks like this:

{"_0_type":"Ping","_0_check_url":"test","_0_link_url":"test"}

here is what you suggested. https://seanvree.com/dev/alpaca/settings.php

This keeps the user on the page, however, writes to the ajax.json file, but either writes "undefined" or simply " [] "

<div id="field7"></div>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#field7").alpaca({
                "schema": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "enum": ["Curl", "Ping"]
                            },
                            "check url": {
                                "type": "string",
                                "format": "uri"
                            },
                            "link url": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                },
                "options": {
                    "toolbarSticky": true,
                    "items": {
                        "fields": {
                            "type": {
                                "label": "Type",
                                "optionLabels": ["Curl", "Ping"]
                            },
                            "check url": {
                                "label": "Check URL"
                            },
                            "link url": {
                                "label": "Link URL"
                            }
                        }
                    },
                    "form": {
                        "attributes": {
                            "action": "post_receiver.php",
                            "method": "post",
                            "url": "settings.php",
                            "enctype": "multipart/form-data"
                        },

                        "buttons": {
                            "submit": {
                                type: 'button',
                                "label": "submit",
                                click: function(){
                                    var val = this.getValue();
                                    var promise = $.ajax('post_receiver.php', {
                                        contentType: 'application/json',
                                        data: JSON.stringify(val)
                                    });

                                    promise.then( 
                                        alert(JSON.stringify(this.getValue(), null, "  "))
                                    ) 
                                }
                            },

                            "view": {
                                "label": "View JSON",
                                "click": function() {
                                    alert(JSON.stringify(this.getValue(), null, "  "));
                                }
                            }
                        }
                    }
                }
            });

        });
    </script>

image

ambischof commented 6 years ago

For example 1, the reason you are being redirected is because that's just how native HTML form submission works. The form sends the data to the server, and the server sends back the response as a new html page. You can control where the response page is opened using the target attribute.

For example two, I'm not quite sure what is the issue, since I don't have your server code.

I don't think that this is your issue but I will say that it is a bit atypical to submit form contents using a GET request, unless you're expecting something in return. (You're not 'getting' anything, you're 'posting' data, see this ). Also, due to the way GET encodes stuff and has a limit to the length of the body, you're typically better off using POST or PUT. Since you are writing to a file, you should be using the non-idempotent POST

I'm not exactly sure since I haven't used PHP in over 5 years, but if your server is the same for both versions of the form, it's probably failing because you're trying to get data from $_POST , but your form is sending using the GET HTTP method, so it should be using $_GET.

seanvree commented 6 years ago

I'm familiare with form submitting. What I'm not familiar with is how to set this options in the alpaca form attributes.

So, I assume something like this? Because that doesn't' work.

like this:

                        "attributes": {
                            "action": "post_receiver.php",
                            "method": "post",
                            "target": "_self",
                            "enctype": "multipart/form-data"
                        },

I'm confused, my form is NOT using a get method, it's using a POST method, see my form attributes above "method: POST"

ambischof commented 6 years ago

The attributes only apply to the native html submission. You have to specify the method in the ajax options. I checked what it was sending in the developer console and it did indeed send a get request.

seanvree commented 6 years ago

oh I see what you're saying on the second method... right should have been "$.post" that's why it was failing,

But still outputs to the json file in a wierd format:

{"data":"[{\"type\":\"Ping\",\"check url\":\"afa\",\"link url\":\"afa\"}]"}

Why the backslashes?

Still wondering about the target method and how to implement that on the first example.

pkeogan commented 6 years ago

Sorry late to the party, but late better than never right?

From Laravel Docs

Retrieving JSON Input Values When sending JSON requests to your application, you may access the JSON data via the input method as long as the Content-Type header of the request is properly set to application/json. You may even use "dot" syntax to dig into JSON arrays:

$name = $request->input('user.name');

@ssgtcookie I take it you wanted to use laravel's native request JSON parameter. (If you dump any request you will notice the #json in the Request objects paramerts list.

Html Button <button id="save" type="button" class="btn btn-success btn-lg">Save <span class="fal fa-save"></span></button>

JS

$("#save").click(function() {
    var data = $('#id_of_alpaca_form').alpaca().getValue();
    $.ajax({
        type: "POST",
        url: "{{ route('some.route') }}",
        data: JSON.stringify(data),
        contentType: "application/json",
        processData: false,
        success: function(data) {
            alert('saved');
        }
    });
});

You then can use all of laravels native functions on the json.