How do I use an Angular.JS generated form to upload a file (and other fields) through AJAX to Symfony2? - ajax

I'm trying to send a form generated by Angular.js through an AJAX call to a Symfony controller action in order to be saved. The form is generated in an ng-repeat after receiving some JSON data (including an entry ID). The code looks as follows.
<div ng-repeat="job in sc.careers">
<div>
<h2>{[{job.title}]}</h2>
<span ng-if="job.super">{[{job.super}]}</span>
<div>
<h4>Role</h4>
<span ng-bind-html="job.role"></span>
</div>
<div class="col-md-4">
<h4>Required Skills</h4>
<span ng-bind-html="job.skills"></span>
</div>
<div class="col-md-4">
<h4>Media</h4>
Some other content \ media
</div>
</div>
<hr/>
<div class="join-us">
<button ng-click="sc.joinUs[$index] = true" ng-hide="sc.joinUs[$index]">Apply for this position</button>
<div ng-show="sc.joinUs[$index]">
<h4>Join Us</h4>
<span>Some random text</span>
<form>
<input type="hidden" name="job_id" value="{[{job.id}]}" />
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" />
<label for="motivation">Cover Letter & CV link</label>
<textarea id="motivation" name="motivation"></textarea>
<label for="resume">Upload your resume</label>
<input type="file" name="resume">
<button type="submit">Send your application</button>
</form>
</div>
</div>
</div>
* most class names have been redacted to keep the code as relevant to the question as possible
I have not yet placed an ng-click -> submit directive. Also, my Angular is configured for the use of the "{[{" and "}]}" delimiters so as not to interfere with TWIG.
I have searched the internet for possible answers but they all pertain to Symfony generated forms (which include validation tokens). Other answers (such as this one) don't quite describe the Angular side of things or don't describe sending the entire form.
In the end, I'm not exactly sure how to approach this. If it's too complicated I'd even settle for not using AJAX at all and submitting directly to Symfony. (actually, the only reason I want to use AJAX is to make the site feel more "snappy").
For what it's worth I'm using the latest stable versions of PHP, Symfony and Angular.JS at the time of writing.
Update
So i managed to send the data back to the controller by using an Agular JS module that enables the use of ng-model on file inputs and by using FormData like so:
var formObject = new FormData;
formObject.append('email', self.careers[index].application.email);
formObject.append('motivation', self.careers[index].application.motivation);
formObject.append('resume', self.careers[index].application.file);
formObject.append('jobID', self.careers[index].id);
$http.post('/app_dev.php/jobs/apply', formObject, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined } // Allows angular to choose the proper Content-Type
})
The only problem left now is that Symfony does not recognize my form data as being valid. The controller action looks like this (for now).
public function applyAction(Request $request) {
$jobApplication = new JobApplications(); // This is the entity I use to store everything.
$form = $this->createFormBuilder($jobApplication)
->add('jobId')
->add('email')
->add('coverLetter')
->add('file')
->getForm();
$form->handleRequest($request);
$response = array (
'isValid' => $form->isValid(), // false
'isSubmitted' => $form->isSubmitted(), // false < that's why the form is invalid
'isErrors' => $form->getErrorsAsString() // empty array
);
if ($form->isValid()) { // is not valid so the following section is not complete
$em = $this->getDoctrine()->getManager();
$jobApplication->upload();
$em->persist($jobApplication);
$em->flush();
//return $this->redirect($this->generateUrl('idea_presentation_careers'));
}
return new JsonResponse($response);
}

Related

What is this.loading = false?

I am using Laravel and Vue.
When I was searching on the internet I saw the following code.
<template>
<div>
<h3 class="text-center">Create Movie</h3>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="createMovie">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="movie.name">
</div>
<div class="form-group">
<label>Director</label>
<input type="text" class="form-control" v-model="movie.director">
</div>
<button type="submit" class="btn btn-primary">Create movie</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
movie: {}
}
},
methods: {
createMovie() {
this.axios
.post('http://localhost:8000/api/movie/create', this.movie)
.then(response => (
this.$router.push({name: 'movie'})
))
.catch(error => console.log(error))
.finally(() => this.loading = false)
}
}
}
I am trying to find out what the last line
.finally(() => this.loading = false)
is doing. I am searching on the internet but I can't find what it does. Also, I tried running the code without the last line however, it did not make any change.
Can someone please tell me what this is doing and when it is useful?
Without seeing the associated Vue template we cannot explain what it is doing exactly, however, we can be fairly confident that the value of loading will be used to show/hide some sort of overlay or activity spinner.
The purpose of the overlay/activity spinner is to provide visual feedback to the user that something is happening. This is useful when loading large amounts of data into your page, or when you perform a long running process (such as uploading a large file for example). So rather than the user seeing nothing on first page load, or clicking a button and wondering if it worked, they are provided with something to let them know that something is happening.
A basic example of what this might look like in the Vue template could be:
// if the value of loading is true, show this
<div v-if="this.loading">Loading, please wait ...</div>
// otherwise show this
<div v-else>Other content</div>
Your example is setting the value of loading to false once a response has been received from your axios request. You would probably want to set the value of loading to true prior to making the request to show an overlay/activity spinner.
Uncaught (in promise) ReferenceError: ture is not defined
You have a typo, it should be true not ture.

How to pass variable to view without using url in Laravel?

guys. I'm a newbie of Laravel.
I'm just wondering that if I need to pass some sensitive info, which is been wrote on a form, to controller, then to another view.
How could I pass the info to the view without using URL?
I have made some small test, but the result is all the same (url would include the data).
Here is the code.
Html(a):
<form action="/data" method="GET" class="form-horizontal">
<div class="form-group">
<input type="text" class="form-control" name="Test">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">test</button>
</div>
web.php:
Route::get('/data', 'DataController#show');
DataController.php:
public function show(Request $request) {
return view('testShow');
}
Html(b):
<div class="ShowDataHere">
#if(!empty(request()->all()))
{{ request()->Test }}
#endif
Simply change your form to use POST instead of GET.
On the form...
<form action="/data" method="POST" class="form-horizontal">
In your route ...
Route::post('/data', 'DataController#show');
This will send all of the form fields to the server in a way that is less visable (not in the URL) but to make the form secure, you'll want to ensure it's served using HTTPS as well.
In Laravel when you use Route::post('/data', 'DataController#show') the Laravel expect a GET, because of the show function.
So you can use Route::post('/data', 'DataController#someFunction') and in your laravel controller get with:
public function someFunction(Request $request)
{
$array = $request->all(); //your fields
}
In this function you can get the attributes with $array = $request->all();

Showing a system error on a form after submitting via AJAX

I'm trying to find the best way in Angular to invalidate a form not due to a specific element, but due to a system-level error AFTER submission via AJAX. For example, you could put in a valid email and password (both good strings), press submit, and find out there is a system error that should trigger a generic error message on the form. Since this isn't tied to anything in the data model, what is the best way I can generically call the form 'invalid'?
<form name="loginForm" class="loginForm" ng-submit="loginSubmit(loginForm)">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email" ng-model="login.email" required>
<span class="error" ng-show="loginSubmit.email.$error">Required!</span><br>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" ng-model="login.password" minlength="8" required>
</div>
<button type="submit" class="btn btn-default">Log In</button>
</form>
and...
ngModule.controller('LoginController', function($scope, $location) {
$scope['login'] = {};
$scope['loginSubmit'] = function(form) {
var loginPromise = myAsyncLoginFuncYouCanAssumeWorks();
loginPromise.done(function(){
$location.path('/');
});
loginPromise.fail(function() {
//how best to trigger a generic error in the form here?
});
};
});
As you can see, I'd like to trigger some form-wide error state after submission. It really could be as simple as adding an invalid form class to the form, but again, I'd like to know the purest Angular way to do this.
Add a label to your form with your generic error which shows upon a scope variable being true when an error occurs:
<div class="alert alert-danger" role="alert" ng-show="loginError">There was an error with your login details. Please check them and try again</div>
then when your promise fails:
loginPromise.fail(function () {
$scope.loginError = true;
});
maybe also could be nice if you have many system messages to abstract them all out into a separate service so you can inject the systemmessages service into your controller and then simply bind:
<div class="alert alert-danger" role="alert" ng-show="loginError">{{ systemMessages.loginError }}</div>
Alternatively as you use Bootstrap maybe inject the $modal service and show the error message inside a popup.
It is also important to make sure you try to use a bearer token stored in localatorage as oppose to cookies for persistence, so it doesn't get sent to the server on each request.
Anti forgery token would also be very beneficial for SPAs.
Your server could return some sort or error pay load with error key or error code.
{errors:[{key:"invalid.password"}]}
Assign the error response to your scope:
loginPromise.fail(function(response) {
$scope.errors = response.data;
});
Next, add filter to translate error key/code into error messages:
angular.module('mtApp').filter('errorFilter', function() {
return function(e) {
if (e === 'invalid.password') {
return 'Invalid password, please try again.';
}
};
});
Finally, display the appropriated errors as a list:
<div ng-show="errors">
<ul>
<li ng-repeat="e in errors">{{e.key | errorFilter}}</li>
</ul>
</div>
Optionally, you can reuse this same "$scope.erros" object combined to ng-class and control the CSS of each field with error.

Hot Towel Angular data-ng-show not working during validation

Hello anyone has anyone tried doing a validation using angularjs in a with Hot Towel template?
Basically i have a property in my angular controller which is binded(two way) in my view.
I just want to do a simple required validation, and then show a <span> element with the message.
here is my controller code
(function () {
'use strict';
var controllerId = 'login';
angular.module('app').controller(controllerId, ['$scope', 'common', 'userservice','$location', login]);
function login($scope, common, userservice, $location) {
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
var vm = this;
vm.title = 'Login';
//view model for credentials
vm.email = null;
activate();
function activate() {
common.activateController([], controllerId)
.then(function () { log('Activated Login View'); });
}
}
})();
and this is my view
<div data-ng-controller="login as vm">
<form name="loginform" id="loginform" novalidate data-ng-submit="loginuser()">
<fieldset>
<legend>Login</legend>
<p>
<label>Email</label>
<input type="email" data-ng-model="email" placeholder="Email" required />
<span data-ng-show="loginform.email.$error.required">*</span>
</p>
</fieldset>
</form>
</div>
i dont know what the problem is, but the <span> just wont show. am i missing something?
You have to set 'name' attribute to your input email :
<input type="email" name="email" data-ng-model="email" placeholder="Email" required />
For each field of your form, angularjs will set a value like: loginform[name-attribute].
For your information, your span will be visible only when required error is triggered (when your email is not empty, your span will be hidden).
[EDIT] See this fiddle: http://jsfiddle.net/k82at

Load Dojo form from ajax call

I am trying to implement something like this.
http://app.maqetta.org/mixloginstatic/LoginWindow.html
I want the login page to load but if you click the signup button then an ajax will replace the login form with the signup form.
I have got this to work using this code
dojo.xhrGet({
// The URL of the request
url: "'.$url.'",
// The success callback with result from server
load: function(newContent) {
dojo.byId("'.$contentNode.'").innerHTML = newContent;
},
// The error handler
error: function() {
// Do nothing -- keep old content there
}
});'
the only problem is the new form just loads up as a normal form, not a dojo form. I have tried to return some script with the phaser but it doesnt do anything.
<div id="loginBox"><div class="instructionBox">Please enter your details below and click <a><strong>signup</strong>
</a> to have an activation email sent to you.</div>
<form enctype="application/x-www-form-urlencoded" class="site-form login-form" action="/user/signup" method="post"><div>
<dt id="emailaddress-label"><label for="emailaddress" class="required">Email address</label></dt>
<dd>
<input 0="Errors" id="emailaddress" name="emailaddress" value="" type="text"></dd>
<dt id="password-label"><label for="password" class="required">Password</label></dt>
<dd>
<input 0="Errors" id="password" name="password" value="" type="password"></dd>
<dt id="captcha-input-label"><label for="captcha-input" class="required">Captcha Code</label></dt>
<dd id="captcha-element">
<img width="200" height="50" alt="" src="/captcha/d7849e6f0b95cad032db35e1a853c8f6.png">
<input type="hidden" name="captcha[id]" value="d7849e6f0b95cad032db35e1a853c8f6" id="captcha-id">
<input type="text" name="captcha[input]" id="captcha-input" value="">
<p class="description">Enter the characters shown into the field.</p></dd>
<dt id="submitButton-label"> </dt><dd id="submitButton-element">
<input id="submitButton" name="submitButton" value="Signup" type="submit"></dd>
<dt id="cancelButton-label"> </dt><dd id="cancelButton-element">
<button name="cancelButton" id="cancelButton" type="button">Cancel</button></dd>
</div></form>
<script type="text/javascript">
$(document).ready(function() {
var widget = dijit.byId("signup");
if (widget) {
widget.destroyRecursive(true);
}
dojo.parser.instantiate([dojo.byId("loginBox")]);
dojo.parser.parse(dojo.byId("loginBox"));
});
</script></div>
any advice on how i can get this to load as a dojo form. by the way i am using Zend_Dojo_Form, if i run the code directly then everything works find but through ajax it doesnt work. thanks.
update
I have discovered that if I load the form in my action and run the __toString() on it it works when i load the form from ajax. It must do preparation in __toString()
Firstly; You need to run the dojo parser on html, for it to accept the data-dojo-type (fka dojoType) attributes, like so:
dojo.parser.parse( dojo.byId("'.$contentNode.'") )
This will of course only instantiate dijits where the dojo type is set to something, for instance (for html5 1.7+ syntax) <form data-dojo-type="dijit.form.Form" action="index.php"> ... <button type="submit" data-dojo-type="dijit.form.Button">Send</button> ... </form>.
So you need to change the ajax contents which is set to innerHTML, so that the parser reckognizes the form of the type dijit.form.Form. That said, I urge people into using a complete set of dijit.form.* Elements as input fields.
In regards to:
$(document).ready(function() {});
This function will never get called. The document, youre adding innerHTML to, was ready perhaps a long time a go.
About Zend in this issue:
Youre most likely rendering the above output form from a Zend_ Dojo type form. If the renderer is set as programmatic, you will see above html a script containing a registry for ID=>dojoType mappings. The behavior when inserting <script> as an innerHTML attribute value, the script is not run under most circumstances (!).
You should try something similar to this pseudo for your form controller:
if request is ajax dojoHelper set layout declarative
else dojoHelper set layout programmatic

Resources