I am building an application with symfony2 on the backend. I am thinking about using AngularJS on the frontend, but I would still like to use symfony forms. I have setup the form with the correct modules and everything, but the problem occurs when the data is actually submitted.
Problem
When Symfony renders the form, it sets the inputs 'name' attribute to an array, like user[username], and this is the format that it expects to receive the data once it is submitted. I can't figure out how to get Angular to submit the data in this format. This is what I have:
<body ng-app="formApp" ng-controller="formController">
{{ form_start(form, {'attr':{'id': 'registerForm', 'ng-submit':'processForm()'}}) }}
{{ form_row(form.username, {'attr':{'ng-model':'formData.username'}}) }}
{{ form_row(form.password.first, {'attr':{'ng-model':'formData.password'}}) }}
{{ form_row(form.password.second) }}
{% for address in form.userAddresses %}
{{ form_row(address.postalCode, {'attr':{'ng-model':'formData.postalCode'}}) }}
{% endfor %}
<div><input type="submit" value="Save" /></div>
{{ form_end(form) }}
</body>
and the controller:
function formController($scope, $http) {
$scope.formData = {};
$scope.processForm = function() {
$http({
method : 'POST',
url : submit,
data : $.param($scope.formData),
headers : { 'Content-Type': 'application/x-www-form-urlencoded' }
})
.success(function(data) {
alert(data.message);
});
};
}
When I submit, obviously the name-value pair uses the formData variables, so username=testuser instead of user[username]=testuser.
I tried to set the formData variable like formData.user[username], but that didn't work at all.
So is there a way to either use the inputs 'name' when submitting, or do something so that the form is submitted in the correct format? Any help would be great!
You can build the object symfony expects in a $scope function that you would bind to your form's submit with ng-click. So:
<input type="submit" value="Save" /></div>
would become
<input type="submit" value="Save" ng-click="submitForm()" /></div>
and in your controller you would add a function on the $scope object
$scope.submitForm = function() {
var user = [];
user['username'] = $scope.formData.username;
//etc
};
I think you should keep Angular form logic. You should use ng-model to bind data. To do this, you can create a Twig Form Theme to add ng-model attributes to your inputs, take a look at this gist.
Then you could pass input values to a $scope.data var in your controller like this:
$scope.var = {};
You should serialize data with the function provided in this article at line 109 or use jQuery.param() if you use jQuery.
Then create a submit method like this:
$scope.submit = function(){
$http.post(
'http://www.myurl.com',
this.serializeData($scope.data),
{ headers: {
'Content-Type': 'application/x-www-form-urlencoded' //Fix for Symfony
}
})
.success(function(data) {
//
})
.error(function (data){
$log.error(data);
});
}
I solve it using server side code. before handling of the request and validation i call the following function i created to change the format:
/**
* Format the request content with the form name
* #param Request $request
* #param $formName
* #param null $content
* #return Request
*/
public function formatRequestWithForm(Request $request, $formName, $content = null)
{
if ($content === null) {
$content = $request->getContent();
}
$contentArray = array($formName => json_decode($content, true));
$request->attributes->add($contentArray);
$request->request->add($contentArray);
return $request;
}
You could use {'data-ng-model': 'formData["user[username]"]'}.
To post formData you should pass it through jQuery.param().
Related
I need to distinguish among multiple AJAX functions in a django view's POST method, to handle multiple forms.
The background:
I'd previously written this view without AJAX and got it working just fine. Before Ajax, I was able to distinguish the POST method for each form by adding name="some_button_name", like so:
if request.method == 'POST' and 'some_button_name' in request.POST: #check which form button submitted
This was great, but AJAX can make it better if I get it working. Now I'm not sure how to distinguish the ajax functions on the view side.
Here's how I think it should work (theoretical view):
if request.method == 'POST' and request.POST['some identifier_A from ajax function here']:
# do stuff
# save form_A
if request.method == 'POST' and request.POST['some identifier_B from ajax function here']:
# do stuff
# save form_B
if request.method == 'POST' and request.POST['some identifier_C from ajax function here']:
# do stuff
# save form_C
... but I'm stumped. Below is my (simplified but structurally accurate) code. It will of course want to call every model's save method regardless of which form/ajax function was called, so saving form C will screw up form B since the B's ajax handler didn't do anything or pass any JSON.
I've looked through the list of ajax parameters and it doesn't appear that I can use one to add some identifying variable to catch on the view side in POST... or is there?
If someone could shed some light a way to solve this I'd be hugely appreciative. Also, I'm not sure if it's relevant to this question, but I also want to eventually add a "save all" button that fires all the ajax functions.
Views.py
def update_view(request, slug):
mymodel = Model.objects.get(slug=slug)
form_A = Model_A_Form(instance=mymodel.model_a)
form_B = Model_B_Form(instance=mymodel.model_b)
form_C = Model_C_Form(instance=mymodel.model_c)
if request.method == 'POST': # using request.is_ajax(): here causes the same problem
form_A = Model_A_Form(request.POST, instance=mymodel.model_a)
if form_A.is_valid():
form_A.save()
return JsonResponse
form_B = Model_B_Form(request.POST, instance=mymodel.model_b)
if form_B.is_valid():
form_B.save()
return JsonResponse
form_C = Model_C_Form(request.POST, instance=mymodel.model_c)
if form_C.is_valid():
form_C.save()
return JsonResponse
context = {
'form_A': form_A,
'form_B': form_B,
'form_C': form_C,
'obj': mymodel,
}
return render(request, "products/update_form.html", context)
.JS
<script>
$(() => {
// Form A handler
$(function () {
$('#mybtn-a').click(function () {
var formA = $(".form-a-ajax")
var formAMethod = formA.attr("method");
var formAEndpoint = formA.attr("action");
formA.submit(function (event) {
event.preventDefault();
var formAData = formA.serialize()
var thisForm = $(this)
$.ajax({
method: formAMethod,
url: formAEndpoint,
data: formAData,
success: function (data) {
$.alter({
title: "Success!",
})
},
error: function (error) {
}
}) //end ajax
});//end click
})
})
// Form B handler
$(function () {
$('#mybtn-b').click(function () {
var formB = $(".form-b-ajax")
var formBMethod = formB.attr("method");
var formBEndpoint = formB.attr("action")
formB.submit(function (event) {
event.preventDefault();
var formBData = formB.serialize()
var thisForm = $(this)
$.ajax({
method: formBMethod,
url: formBEndpoint,
data: formBData,
success: function (data) {
$.alter({
title: "Success!",
})
},
error: function (error) {
}
}) // end ajax
});//end click
})
})
// Form C handler
$(function () {
$('#mybtn-c').click(function () {
var formC = $(".form-c-ajax")
var formCMethod = formC.attr("method");
var formCEndpoint = formC.attr("action")
formC.submit(function (event) {
event.preventDefault();
var formCData = formC.serialize()
var thisForm = $(this)
$.ajax({
method: formCMethod,
url: formCEndpoint,
data: formCData,
success: function (data) {
$.alter({
title: "Success!",
})
},
error: function (error) {
}
}) //end ajax
});//end click
})
})
Template:
<form method="post" action="{{ obj.get_my_url }}" class="form-a-ajax">
{% csrf_token %} {{ form_A }}
<button type='submit' id="mybtn-a" name='save_form_a' class='btn btn-success'>Save</button>
</form>
<form method="post" action="{{ obj.get_my_url }}" class="form-b-ajax">
{% csrf_token %} {{ form_B }}
<button type='submit' id="mybtn-b" name='save_form_b' class='btn btn-success'>Save</button>
</form>
<form method="post" action="{{ obj.get_my_url }}" class="form-c-ajax">
{% csrf_token %} {{ form_C }}
<button type='submit' id="mybtn-c" name='save_form_c' class='btn btn-success'>Save</button>
</form>
Thanks for your feedback everybody! Your comments gave me an idea. I have a working solution and it seems totally simple after the fact. No need to append anything to the JSON in my case. Every model I have has some unique field names; I just need to check for the presence of one of them in the JSON object of key:value pairs. This works:
if request.method == 'POST' and 'some_unique_model_field_name' in request.POST.keys():
form_A = Model_A_Form(request.POST, instance=mymodel.model_a)
if form_A.is_valid():
form_A.save()
return JsonResponse
#rinse and repeat...
what I'm trying to do is a function in javascript that sends a simple data to a laravel controller and then upload it to the database, the problem I have is that until now I have not been able to find a way to send this data, always I get error 500.
Laravel Controller:
public function crearRegistro(Request $request){
$registro = new Registro();
$registro->indicador = $request->indicador;
$registro->save();
}
Javasript Function:
$scope.calculoIndicador = function(){
$http.post("/calculoIndicador")
.then(function(response) {
});
$scope.indicador = 5 +5;
alert('Se ha guardado correctamente');
}
view:
<input class="btn btn-success" style="" ng-click="calculoIndicador()"
type="submit" value="Enviar"/>
Route:
Route::post('/calculoIndicador', 'TecnologiaController#crearRegistro');
(First, sorry with my bad english)
I assume that you use Laravel 5.6. On controller you have to use:
$request->all() method to get the data passed to controller via post on javascript. On your code i assume yout want the "indicador", so:
$requestData = $request->all();
$registro->indicador = $requestData['indicador'];
But on your javascript you have to pass "indicador" as parameter on post request data. So, on javascript (or typescript of Angular) post do something like this:
$http.post("/calculoIndicador", {indicador: 'your_data'}).then(
function(response) {
$scope.status = response.status;
$scope.data = response.data;
}, function(response) {
$scope.data = response.data || 'Request failed';
$scope.status = response.status;
}
)
Can you update this page with stack trace of this error?
I have JS code:
var pobTeamId = document.getElementById('team_a_id').value;
var query = "<?php echo Sport::find(Team::find(pobTeamId)->sport_id)->id; ?>";
I need insert value pobTeamId in variable query.
I don't know how I can add this variable. I trying using this:
...Team::find(pobTeamId)...
...Team::find($pobTeamId)...
...Team::find(?>"pobTeamId"<?php)...
but Laravel returned only errors.
You approach is wrong! PHP won't be able to get the value of pobTeamId.
Use ajax to send the value to the Controller
var pobTeamId = document.getElementById('team_a_id').value;
// Initiate an Ajax either on page load or on button click
$.ajax({
url: '', // path you defined in your routes file
type: '' // either POST or GET
data: {
"pobTeamId": pobTeamId
},
success: function (data) {
}
});
and in the Controller you would have access to the pobTeamId
public function yourFunction(Request $request)
{
$pobTeamId = $request->input('pobTeamId');
$sport_id = Sport::find(Team::find($pobTeamId)->sport_id)->id;
}
you would need to reference the Sport Model in your controller and add an appropriate route
Do it like this:
<form method="get" action="{{ route('get_sport_id') }}">
<input id="team_a_id" value="" name="team_a_id"/>
<button type="submit"> Fetch </button>
</form>
Then in your controller:
public function getSportID()
{
$sport_id = Sport::find(Team::find(request()->get('team_a_id')->sport_id)->id;
return back()->with('sport_id', $sport_id);
}
With a corresponding route that's something like this:
Route::get('/sport-id', 'SportController#getSportID')->name('get_sport_id');
Now your view will have access to $sport_id. Of course, you should check isset($sport_id) before attempting to use it in the view.
In a Django 1.8 app I have a form that is posting using Angular.js. After the form has been submitted by Angular to Django Rest Framework I would like to move to another Django view. My question is how to move to another Django view apart from using $windows.location.href?
At the moment I'm using $windows.location.href but I would like Django(meaning not Javascript/Angular) to move to another page. Here's how I do it now - for brevity here's a small part of this form in my template:
<div ng-app="pacjent">
(...)
<div ng-controller="NewPatientCtrl">
(...)
<form name="newPatientForm" ng-submit="newPatientForm.$valid && submitNewPatientForm()" ng-init="initialize('{{user.id}}')" novalidate>
{% csrf_token %}
(...)
</form>
This form is posting all data to Django Rest Framework like this:
function submitNewPatientForm(){
/* it's prime goal is to submit name,surname, age & phone to createNewPatient API */
$scope.setBusy = true;
savePatient = $http({
method: 'POST',
url: 'http://localhost:8000/pacjent/api/createNewPatient/',
data: $scope.newPatient,
})
.then(function successCallback(response){
newPatient = response.data['id'];
createNewTherapyGroup();
url = patientDetailView + response.data.id + '/';
$window.location.href=url; # I DON"T WANT TO USE THIS, but I don't know how!
}, function errorCallback(response){
if (response['data']['name'] && response['data']['surname']) {
$scope.newPatientForm.newPatientName.$setValidity("patientExists", false);
$scope.errors.newPatientName =response.data['name'][0];
}
(...)
Is there a way to do it differently?
If you want django to control the redirect, don’t post the form using AJAX, just post it back to django with a regular form so that you can redirect the user to another view.
e.g.,
<form name="newPatientForm" method="POST" action="">
{% csrf_token %}
(...)
</form>
in your view:
def new_patient_form_submit(request):
name = request.POST['. . .']
. . .
return redirect('another_view_name')
alternatively, you can have the REST endpoint return a success JSON response with the URL to redirect to so that you don’t have to hardcode it into your JS file:
.then(function successCallback(response){
newPatient = response.data['id'];
var next = response.data['next']; // get the next URL to go to from the django REST endpoint
createNewTherapyGroup();
$window.location.href = next; // redirect to the django-provided route
<p style="padding:10px">Add your Facebook Account</p>
{!! Form::open(['route'=>array('agencyNewPlatform',$influencer->getUser()->user_type_id, '1')]) !!}
<input type="text" name="handle" placeholder="Profile Name" />
<p style="padding-top:25px;padding-bottom:5px">
<button type="submit" class="btn btn-success plat_add">Save</button>
</p>
{!! Form::close() !!}
I am trying to submit this form through AJAX, but I don't know how to define myurl. The following source code may contain other errors, too. Please help me.
$('.plat_add').click(function(event) {
event.preventDefault();
var myurl = ?????????????;
var date = new Date();
myurl = myurl+"?noche="+date.getTime();
mydata = $(this).closest('form').serialize();
var jqxhr = $.ajax({
url: myurl,
type:'GET',
dataType:'json',
data: mydata,
}).done(function() {
var response = JSON.parse(jqxhr.responseText);
$("#table3").append("<tr id=" + response.platform_id + "plat><td>" + response.plat_name + "</td><td>" + response.handle + "</td><td><a class='plat_remove' href=" + response.link + ">Remove</a></td></tr>");
}).fail(function() {
alert("Add platform fail!" + jqxhr.responseText);
});
});
This are my route and controller functions:
Route:
Route::get('influencer/update/{user_type_id}/add_plat/{platform_id}', ['as'=>'agencyNewPlatform','uses'=>'AgentController#postPlatform']);
and Controller:
public function postPlatform(InfluencerAddPlatformRequest $request, $user_type_id, $platform_id)
{
$user = Auth::user();
$agent = $user->getTypeModel();
$influencer = $this->influencer->findById($user_type_id);
$handle = $request->input('handle');
$result = DB::table('influencers_platforms')->insert(['influencer_id'=>$user_type_id, 'platform_id'=>$platform_id, 'platform_handle'=>$handle]);
$plat_name = DB::table('platforms')->where('id', $platform_id)->first()->name;
if($request->ajax())
{
return response()->json(array('responsecode'=>'1','action'=>'add', 'plat_name'=>$plat_name, 'handle'=>$handle, 'link'=>route('agencyDeletePlatform',[$influencer->getUser()->user_type_id, $platform->id]), 'result'=>$result,'platform_id'=>$platform_id));
}
}
I am really stuck at here; thank you all in advance!
First put an id to your form, thas better than try to catch the event from the submit button, remember, press enter on any field will gonna submit your form without press the submit button.
view
{!! Form::open(['route'=>array('agencyNewPlatform',$influencer->getUser()->user_type_id, '1'), 'method' => 'get' 'id' => 'form']) !!}
<input type="text" name="handle" placeholder="Profile Name" />
<p style="padding-top:25px;padding-bottom:5px">
<button type="submit" class="btn btn-success plat_add">Save</button>
</p>
{!! Form::close() !!}
After here the script i often use to send an ajax request:
javascript
$("#form").submit( function (event) {
event.preventdefault();
var url = $(this).attr('action'); //here you have to options
//get the url from the action on the form
//or declare an <a href="{{route(your.route)}}"> and get it from the href
var data = $(this).serialize();
$.get(url, data, function(result) {
//do if result is ok
}).fail(function (){
//do if fails
});;
});
Edit:
i se you have a var date = new Date(); and you want to put it on your vars, first, the url even if is a get request dont contain your data info.
You need to pass it into your data var.
lets learn
a default get url:
myurl.com?var=value&var2=value2
When you do an ajax request this url is divided in two pieces
the url and the data
var url = "myurl.com";
var data = "var=value&var2=value2";
the jquery will gonna merge that two variables after.
So, lets learn how .serialize() works, when you call this method, the result will be in the data format.
so if you want to add another variable its simple:
data+="&newvar="+var;
now data contain:
data = "var=value&var2=value2&newvar=valuefromvar"
so your code will be like:
$("#form").submit( function (event) {
event.preventdefault();
var date = new Date();
var url = $(this).attr('action'); //here you have to options
//get the url from the action on the form
//or declare an <a href="{{route(your.route)}}"> and get it from the href
var data = $(this).serialize();
data+="&noche="+date.getTime(); //here the change
$.get(url, data, function(result) {
//do if result is ok
}).fail(function (){
//do if fails
});;
});
Another recomendation if you work with route names, the correct form to put it is separating words with . not in camelcase format, and build with a subject after and action (if its necesary) like:
user.show
user.update
agency.create.platform
In the controller, I know maybe its too late to make big changes on your application, but in another projects why you dont try to use eloquent and orm relationships instead of fluent DB, this will gonna make your code more flexible, and your controller logic maybe will not take more than 10 lines.
I may have not fully understood your question so please make a comment if I didn't address something properly.
As a side note, formatting your code (indentation) and using consistency throughout your code (such as declaring an array, i.e. use array() or [] not both) will go a long way in making your code readable when you or someone else returns to it, see the changes I made in terms of formatting.
view
I have added an id myForm to the form here, see second argument of form open() function. Your route is get so I changed the form method to get also. Default for forms is post you can of course change that depending on your needs.
<p style="padding:10px">Add your Facebook Account</p>
{!! Form::open(['route' => ['agencyNewPlatform', $influencer->getUser()->user_type_id, '1'], 'method' => 'get', 'id' => 'myForm']) !!}
<input type="text" name="handle" placeholder="Profile Name" />
<!-- this looks much easier to read on three lines -->
<p style="padding-top:25px;padding-bottom:5px">
<button type="submit" class="btn btn-success plat_add">Save</button>
</p>
{!! Form::close() !!}
javascript
This listens for the form submit event and then you can get the url from the form action attribute
$('#myForm').submit(function(event) {
event.preventDefault();
var $myForm = $(this);
$.get($myForm.attr('action'),
$myForm.serialize,
function(data) {
// your success code
}
).fail(function(data) {
var errors = data.responseJSON;
// show the errors to user
});
});
routes.php
This looks much easier to read on four lines, with indentation. See controller function is getPlatform I changed that because route type is get - it doesn't HAVE to be but you should make them the same so your code is easy to understand.
Route::get('influencer/update/{user_type_id}/add_plat/{platform_id}', [
'as' =>'agencyNewPlatform',
'uses' =>'AgentController#getPlatform'
]);
controller
public function getPlatform(InfluencerAddPlatformRequest $request, $user_type_id, $platform_id)
{
$user = Auth::user();
$agent = $user->getTypeModel();
$influencer = $this->influencer->findById($user_type_id);
$handle = $request->input('handle');
$result = DB::table('influencers_platforms')
->insert([
'influencer_id'=>$user_type_id,
'platform_id'=>$platform_id,
'platform_handle'=>$handle
]);
$plat_name = DB::table('platforms')
->where('id', $platform_id)
->first()
->name;
if($request->ajax()) {
return response()
->json([
'responsecode' => '1',
'action' => 'add',
'plat_name' => $plat_name,
'handle' => $handle,
'link' => route('agencyDeletePlatform', [$influencer->getUser()->user_type_id, $platform->id]),
'result' => $result,
'platform_id' => $platform_id
]);
}
}