Typel
Typel

Reputation: 1139

How to submit Polymer forms to PHP and display response

Using Polymer 1.0, I set up an iron-form to submit a simple contact form. The idea is to submit the form to a database table using PHP and then display a response from the PHP side into the browser without refreshing - typical AJAX. I'm getting hung up on the Polymer environment though - it seems like there should be a correct way to do this, but hours of searching and tinkering hasn't been fruitful.

I did start this project using the Polymer Starter Kit (lite), which uses a script (app.js) to add event listeners and such. So far I haven't broken that functionality, though all of the examples in documentation do NOT do it this way, so it makes things a little more complicated since I'm still getting used to Polymer in general.

Here's what I've got so far. Thanks so much for any advice you can offer.

index.html

<!-- this is where the output should be displayed -->
<div id="output"></div>

<!-- this is the web form -->
<form is="iron-form" id="contactus-form" method="post" action="/">
<input type="hidden" name="action" value="contactus-form">
<paper-input id="contactus-field-Name" name="Name" label="Name" value="test"></paper-input>
<paper-button onclick="submitHandler(event)">Send</paper-button>
</form>

...

<script src="script/app.js"></script>
<script>
function submitHandler(event) {
    Polymer.dom(event).localTarget.parentElement.submit();
    }
</script>

app.js

(function(document) {
  'use strict';

  addEventListener('iron-form-submit', function(e) {

    // this works and displays the POSTed values in the browser
    document.querySelector('#output').innerHTML = JSON.stringify(e.detail);

    // I'm looking for a way to first submit this data through PHP and 
    // display the output of that process in the #output div rather than the 
    // raw form input itself.

    }
})(document);

FAILED METHOD 1

I tried adding an iron-ajax element into index.html and referencing it from app.js as shown below. Unfortunately, when it tries to add the event listener, the entire app crashes. It seems strange because there are many other pieces in app.js which add event listeners in the same way.

index.html

<iron-ajax id="contactus-output" url="/form/contact.php" params="" handle-as="json"></iron-ajax>
<!-- same form as before goes here -->

app.js

var coutput = document.querySelector('#contactus-output');
coutput.addEventListener('response', function() {
    // nothing fancy here yet, just trying to see if I can do this
    document.querySelector('#output').innerHTML = 'hello world';
    }

FAILED METHOD 2

I found This Answer on SO and decided to try the iron-form-response event. The output I receive now is [object HTMLElement], which is at least something, although I'm not sure if it is actually working or not.

Everything else staying the same, I changed the target of my form to point to my php script and then replaced what I had in app.js with the following:

app.js

addEventListener('iron-form-response', function(e) {
  document.querySelector('#output').innerHTML = e.detail;
});

Am I getting any closer?

NOT GIVING UP

Using my second failed method above, the iron-form appears to be making a request, because when I listen for the 'iron-form-response' event, it does fire.

However, the only thing that gets returned is [object HTMLElement] - no idea what to do with that. I tried spitting out some of its properties (as documented on developer.mozilla.org - .title, .properties, .style, etc) but they appear to be empty. Does iron-form really return an HTMLElement object or is this a mistake? I had figured it would return the results from the PHP script I'm submitting the form to just like a normal XMLHttpRequest. If iron-form somehow compresses this into an object, is there a way to pull it out again?

TL;DR

I think what this entire thing boils down to is this: HOW can I properly add an Event Listener (for iron-form-request) when my iron-form is in index.html and index.html is bootstrapped by app.js as happens by default in the Polymer 1.0 Starter Kit?

Further Simplified: How do I add event listeners properly to Polymer's shadow DOM when I'm NOT creating an element (just using it)?

BUG?

With the help of user2422321's wonderful answer below, the iron-request is being performed and a successful iron-request response is received. However, its "response" property returns NULL even though "succeeded" returns true, there were no errors, and the XHR resolved completely. Both "get" and "post" methods were tested with the same NULL result.

I see that there was a bug which matches these symptoms precisely logged in GitHub 10 days ago, though it hasn't seen much attention: Issue 83. This is unfortunate, but it appears to be a bug. I'm not convinced there will be any way to get this working until the element itself is repaired.

WHAT DOES IRON-REQUEST WANT TO SEE?!

As I explore this further, I see that even the XHR directly is returning "null" even though it has the responseURL correct and a statusText of "OK". I'm beginning to wonder if the actual PHP script I'm trying to run - which currently just outputs "Hello World" - is at fault.

Does iron-form-request expect a certain format or data type in the results? I tried adding header('Content-Type: text/plain'); to my PHP file, then I tried formatting it as a verified JSON string, but response is still null. Seems that nothing works.

Old school direct XMLHttpRequests work normally... is iron-form malforming something before the request even gets submitted?

I did set up a handler to catch iron-form-error but none are received. According to every single piece of information in the response, everything is perfect in the world. Just... null response. Over and over and over again... this is so incredibly frustrating.

SOLUTION! (sort of)

Okay, I got desperate enough that I started thumbing through the iron source code itself. It appears that iron-form is still somewhat glitchy at the moment and only returns content if the response is properly formatted json. In iron-request.html, it appears to allow the following types, but don't be fooled. I could only get json to work - I assume the rest will eventually fall into line.

Therefore, for the time being, we need to format our response as JSON and include a DOCTYPE declaration to match.

In my case, this looks like so (thanks goes out to user2422321 for helping me so much):

index.php

<div id="output">{{myOutput}}</div>

<form is="iron-form" id="contactUsForm" method="get" action="/contactus.php" on-iron-form-response="_onResponseRetrieved">
<paper-input id="Name" name="Name" value="text" label="Name"></paper-input>
<paper-button id="contactSubmitButton" on-tap="_submitHandler">Submit</paper-button>
</form>

app.js

(function(document) {
...
app._onResponseRetrieved = function(e) {
  this.myOutput = e.detail;
  console.log(e);
};
app._submitHandler = function(e) {
  this.$.contactUsForm.submit();
});
...
})(document);

Then finally, and this was the important last piece of the puzzle. I hadn't previously considered that the contents this file outputs would be very important since straight up XMLHttpRequests return whatever the file spits out.

contactus.php

<?php
// This is the line I added
header('Content-Type: application/json');
// Actual Code goes here
// Then make sure to wrap your final output in JSON
echo '{"test":"this is some test json to try"}';

With all those pieces in place, it works and e.detail.response contains the JSON response we echoed from contactus.php.

Upvotes: 0

Views: 1872

Answers (2)

Ankita Gavali
Ankita Gavali

Reputation: 81

I am using Polymer 2 and I had a similar problem like this.

Here is my element file :

<template is="dom-bind">

    <iron-ajax
            auto
            id="ajax"
            url="test.php"
            handle-as="json"
            method="POST"
            body='{"email":"[email protected]", "lastlogin":"Feb 21st 2016", "notifications":6}'
            content-type = "application/json"
            last-response="{{responseObject}}" >
    </iron-ajax>

    <login-element details="[[responseObject]]"></login-element>
</template>

And the login-element.html looks like this:

<dom-module id="login-element" >
<template>

    <!--<form action="test1.php" method="post"  enctype='application/json'>-->
        <!--Name: <input type="text" name="name"><br>-->
        <!--E-mail: <input type="text" name="email"><br>-->
        <!--<input type="submit" onclick="submitForm()">-->
    <!--</form>-->

    <h2>{{details.email}}</h2>

</template>

<script>
    Polymer({
        is:"login-element",
        properties:{
            details:Object
        }
    });

</script>

And test.php

<?php
$data = json_decode(file_get_contents('php://input'), true);
echo json_encode($data);
exit;

Upvotes: 1

Whyser
Whyser

Reputation: 2247

Not quite sure I understand what is it that doesn't work, but this is how I think it should be done "pure" Polymer way (by that I mean as little Javascript as possible).

<div id="output">{{myOutput}}</div>

<!-- this is the web form -->
<form is="iron-form" id="contactUsForm" method="post" action="/" on-iron-form-response="_onResponseRetrieved">
   <input type="hidden" name="action" value="contactUsForm">
   <paper-input id="contactus-field-Name" name="Name" label="Name" value="test"></paper-input>
   <paper-button on-tap="_submitHandler">Send</paper-button>
</form>


_onResponseRetrieved: function(e)
{
    //I'm not 100% sure what e.detail actually contain, but the value your looking for should be inside there somewhere
    this.myOutput = e.detail; 
}
_submitHandler: function(e)
{
    //Note that I renamed the id of your form :)
    this.$.contactUsForm.submit();
}

I've also seen recommendation that onclick should be on-tap so that it works properly on mobile devices, therefore I changed it.

The UI should now update as soon as you receive a response from the server!

-

EDIT:

Because you are using Polymer Starter Kit which is doing some of main logics inside the index.html some things are a bit different. In the Polymer documentation one is usually given examples where they show some piece of code inside an element, while the index.html does not look or function exactly as an element, which indeed can be confusing. In my own project I actually skipped all logics inside the index.html because I thought it seemed messy and complicated, but when looking back it's not all that weird (still not pretty in my opinion). I'm not sure about this, but the way index.html is setup might be the way to setup custom-elements if you want to separate the code (javascript) and the look (html/css).

So to get your code working: In app.js you see this line:

var app = document.querySelector('#app');

You can think of the app variable as a custom-element. In index.html you can see this line, which kinda says: "if you click on me I will call the method onDataRouteClick in the element app":

<a data-route="home" href="/" on-click="onDataRouteClick">

So, why will it call the method on the element app? That's because the line above is a child of: <template is="dom-bind" id="app"> (note the id has nothing to do with this, other than that we located it in Javascript by that id, so when I talk about the app object I'm talking about the one in Javascript).

Inside app.js we can then define what will happen when onDataRouteClick is called by doing the following:

   app.onDataRouteClick = function() {
      var drawerPanel = document.querySelector('#paperDrawerPanel');
      if (drawerPanel.narrow) {
         drawerPanel.closeDrawer();
      }
   };

I don't know why, but they keep using this line to find objects:

var drawerPanel = document.querySelector('#paperDrawerPanel');

But when you are in the scope of app you can actually use this instead, which I think is more Polymerish:

var drawerPanel = this.$.paperDrawerPanel;

-

Sorry if you knew all this already, now how do we get your code working?

Inside app.js you add this:

app._onResponseRetrieved = function(e)
{
    //I'm not 100% sure what e.detail actually contain, but the value your looking for should be inside there somewhere
    this.myOutput = e.detail;
    console.log(e); //Check the console to see exactly where the data is
};
app._submitHandler = function(e)
{
    //Note that I renamed the id of your form :)
    //We are in the scope of 'app' therefore we can write this
    this.$.contactUsForm.submit();
};

And in index.html you would have something similar to this (obviously it should be within the template element):

<div id="output">{{myOutput}}</div>

<!-- this is the web form -->
<form is="iron-form" id="contactUsForm" method="post" action="/" on-iron-form-response="_onResponseRetrieved">
   <input type="hidden" name="action" value="contactUsForm">
   <paper-input id="contactus-field-Name" name="Name" label="Name" value="test"></paper-input>
   <paper-button on-tap="_submitHandler">Send</paper-button>
</form>

Upvotes: 1

Related Questions