Handling ajax post data in Zend Framework 2 - php

So i've got some information I send with a Post ajax request like this:
$.ajax("<?php echo $this->url('panel/academy/locals/editlocal')?>", {
method: 'POST',
data: {
id: id, // number
distrito: newDistrito, // string
direccion: newDireccion, // string
telefono1: newTelf1, // string
telefono2: newTelf2, // string
efectivo: $('#local_'+id).find('.efectivo').prop('checked'),
visa: $('#local_'+id).find('.visa').prop('checked'),
mastercard: $('#local_'+id).find('.mastercard').prop('checked'),
american: $('#local_'+id).find('.american').prop('checked'),
deposito: $('#local_'+id).find('.deposito').prop('checked'),
central: newCentral,
}
})
efectivo, visa, american, mastercard and central are all booleans.
In the server I do this:
$prg = $this->prg();
if($prg instanceof \Zend\Http\PhpEnvironment\Response)
return $prg;
elseif ($prg === false)
return new JsonModel(array(
'msg' =>'error prg false',
'success' => false
));
$localForm = new LocalForm();
$localForm->setData($prg);
if(!$localForm->isValid()){
return new JsonModel(array(
'success ' => false,
'error' => 'Invalid',
));
}
$id = (int) $localForm->get('id')->getValue();
$local = (new LocalMapper())->byId($id);
//change local model
$local->setDistrictId($localForm->get('distrito')->getValue());
$local->setAddress($localForm->get('direccion')->getValue());
$local->setPhone1($localForm->get('telefono1')->getValue());
$local->setPhone2($localForm->get('telefono2')->getValue());
$local->setCentral($localForm->get('central')->getValue());
$localpayments = (new LocalPaymentMapper())->byLocalId($id);
// I will have to fix what I'm about to do as soon as possible
foreach($localpayments as $payment){
// Efectivo 1
// Visa 2
// Mastercard 3
// American Express 4
switch($payment->getPaymentId()){
case 1:
if(!$prg['efectivo']){
$payment->delete();
}
break;
case 2:
if(!$prg['visa']){
$payment->delete();
}
break;
case 3:
if(!$prg['mastercard']){
$payment->delete();
}
break;
case 4:
if(!$prg['american']){
$payment->delete();
}
break;
default:
break;
}
}
the problem is that when i try to add an element to the localForm that holds a boolean value like a checkbox the form is always invalid and so I never get to the part where I acccess the db and save the changes the user made. I tried using the $prg array to acces the info but had no luck either. How can I acomplish this? am I trying the wrong approach?
Thanks in advance
here is the full form
<?php
namespace GrouPanel\Form\Locals;
use GrouCore\Form\Form;
use GrouCore\Form\Element\DistrictSelector;
use GrouCore\Form\Element\UserPhone;
use GrouCore\Form\Element\LocalAddress;
use Zend\Form\Element\Checkbox;
class LocalForm extends Form {
public function __construct($name = "localForm") {
parent::__construct ( $name );
$this->setAttribute ( 'novalidate', 'novalidate' );
$localDistrict = new DistrictSelector ( 'distrito' );
$localAddress = new LocalAddress ( 'direccion' );
$localPhone1 = new UserPhone ( 'telefono1' );
$localPhone2 = new UserPhone ( 'telefono2' );
$localCentral = new Checkbox ( 'central' );
$this->add ( array (
'name' => 'id',
'type' => 'text'
) );
$this->add( $localDistrict )
->add( $localAddress )
->add ( $localPhone1 )
->add ( $localPhone2 )
->add ( $localCentral );
$this->getInputFilter ();
}
DistrictSelector, UserPhone and LocalAddress all work as expected, the checkbox seems to be the problem somehow

You use the syntax jQuery.ajax( url [, settings ] ). Try adding key :
dataType: 'json',
in your setting structure.

Http stringified every thing before send.
so you can use 1 or 0 instead of true or false in js.
efectivo: ($('#local_'+id).find('.efectivo').prop('checked')) ? 1 : 0,
add to ajax
dataType: 'json',

Related

Laravel: php match does not seem to be working

I'm working on a Laravel app. I've created an enum like this:
<?php
namespace Domain\Order\Enums;
use Domain\Order\Models\Order;
enum OrderStatuses : string
{
case New = 'new';
case Pending = 'pending';
case Canceled = 'canceled';
case Paid = 'paid';
case PaymentFailed = 'payment-failed';
public function createOrderStatus(Order $order) : OrderStatus
{
return match($this) {
OrderStatuses::Pending => new PendingOrderStatus($order),
OrderStatuses::Canceled => new CanceledOrderStatus($order),
OrderStatuses::Paid => new PaidOrderStatus($order),
OrderStatuses::PaymentFailed => new PaymentFailedOrderStatus($order),
default => new NewOrderStatus($order)
};
}
}
In my order model I've got the following attribute:
protected function status(): Attribute
{
return new Attribute(
get: fn(string $value) =>
OrderStatuses::from($value)->createOrderStatus($this),
);
}
which as you can see receives some data and returns an Order status.
Now, I've got the following piece of code:
$order = Order::find($orderID);
$newOrder = match ($order->status) {
OrderStatuses::New => (new NewToPaidTransition)->execute($order),
NewOrderStatus::class => (new NewToPaidTransition)->execute($order),
'new' => (new NewToPaidTransition)->execute($order),
default => null,
};
but the value of $newOrder is always null, meaning the status is not being matched to any of the elements. There should be one single element there: NewOrderStatus::class, I just added the others for debugging purposes.
If I inspect the value of $order->status while running the debugger I'm getting that it is of type Domain\Order\Enums\NewOrderStatus so why it is not being matched?
Thanks
It looks like you are testing for equality between an instance of a class and a string of the class name.
Try
$newOrder = match (get_class($order->status)) {
OrderStatuses::New => (new NewToPaidTransition)->execute($order),
NewOrderStatus::class => (new NewToPaidTransition)->execute($order),
'new' => (new NewToPaidTransition)->execute($order),
default => null,
};

remove & take out from the null entry

I have a project & branches model in Laravel, each project has many branches.
this function is work very well but it saved Empty records from $request->branches
$data = $request->except('branches');
$branches = collect($request->branches)->transform(function($branch) {
$branch['name'] = $branch['name'];
return new Branch($branch);
});
$data = $request->except('branches');
$data['user_id'] = $user->id;
$project = Project::create($data);
$project->branches()->saveMany($branches);
return response()->json(['created' => true,]);
I want to remove empty record from branches request.
this is the log of array:
$request->branches:
local.INFO: array (
0 =>
array (
'name' => NULL,
),
)
$branches (after collect) :
local.INFO: [{"name":null}]
You can use the filter function
$filteredBranches = $branches->filter();
See Documention.
I use the reject function
->reject(function ($branch) {return empty($branch['name']);})

Dynamic Calculations based on underlying data

The story.
I'm trying to calculate product cost's based on multiple varying factors. My system currently works perfectly using PHP functions however I would like to add some Ajax in order to produce a more user friendly experience.
How I currently do it.
Entities
//entity/ProductRecipe.php
public function productcost2amountcost() {
$this->productcost = null;
$am = $this->amount;
$cu = $this->product->getCostunit();
$productcost = $am * $cu;
$this->productcost = $productcost;
$this->recipe->fixRecipecost();
$this->recipe->fixCostperyield();
}
//entity/Recipe.php
public function fixRecipecost() {
$this->recipecost = 0;
foreach ($this->product AS $pc) {
$this->recipecost += $pc->getProductcost();
$this->setRecipecost($this->recipecost);
}
}
public function fixCostperyield(){
$this->costperyield = null;
$cy = $this->recipeyield;
$rc = $this->recipecost;
$this->costperyield = $rc / $cy;
}
Forms
//Form/RecipeType.php
$builder
->add('recipename', 'text', array(
'label' => 'Recipe Name'))
->add('recipeyield', 'number', array(
'label' => 'Recipe Yield'))
->add('product', 'collection', array(
'label' => 'Ingredients',
'type' => new ProductRecipeType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
//Form/ProductRecipeType.php
$builder
->add('product', 'entity', array(
'class' => 'BCInventoryBundle:Product',
'property' => 'prodlist',
))
->add('amount', 'number', array(
'label'=>'Quantity',
))
->add('measure', 'entity', array(
'class' => 'BCInventoryBundle:Measures',
'property' => 'unit',
))
->add('productcost' ,'money', array(
'currency' => false,
'read_only' => 'true',
))
;
As I stated before this all works fine, albeit a bit boring and static.
Issue
As you can see from the picture. The ProductRecipe is used as a collection of form's from within the Recipe form. What I want is once the User has selected a product from the Database (Butter) and stated a quantity (1) and measure (kg) I need Ajax to first get the UnitCost (all Units get converted to Grams and update a filed called Unitcost)
1kg convert to g = 1000, 1000 * unitcost (0.0079600000) = £7.96 <- This needs to be put into the ProductCost field of the form.
Any help would greatly be appreciated even a point in the right direction would be amazing. I've spent hours Google-ing but the stuff that comes up never quite seems to be what I need especially when it comes to Symfony2 related.
How do I run the productcost2amountcost() function using Ajax in order to fill the ProductCost field without a page refresh.
Thank-you in advance. Doug.
The Answer
Thank's to Santiag00 after much trial and error for us both we got it working. He's updated his part but I'd like to elaborate a bit.
Javascript
//Calc.js
$(document).on('change', '.products, .amounts, .unit', function(event) {
var amount = $(this).parent().parent().parent().find('.amounts').val();
var productId = $(this).parent().parent().parent().find('.products').val();
var unit = $(this).parent().parent().parent().find('.unit').val();
var productCostField = $(this).parent().parent().parent().find('.product-costs');
//The above assign a Var to each of the field's needed for the JS
console.log(productCostField);
console.log("Amount: " + amount + " - ProductID: " + productId + " - unit: " + unit);
if (amount == "" || productId == "" || unit == "") {
// Don't make the Ajax call if you are missing one of the two values
return false;
}
// This will be triggered every time a product or amount input field is changed
$.post(
Routing.generate('calculate_cost'),
//This line is what connects to the Function in the controller and defined in routing.yml. Made easier by
//https://github.com/FriendsOfSymfony/FOSJsRoutingBundle/blob/master/Resources/doc/index.md
{
// Use the corresponding amount and product ID
product: productId, amount: amount, unit: unit,
},
function(data) {
data = JSON.parse(data);
if (!data.success) {
// An error was thrown in the controller
alert(data.message);
}
else {
// Update the corresponding productCost field using the data from the controller
console.log("Product cost: " + data.productCost);
productCostField.val(data.productCost);
}
}
);
}
);
The Route Called from the above JS.
//routing.yml
calculate_cost:
pattern: /productcost
defaults: { _controller: "BCInventoryBundle:ProductRecipe:getProductCost" }
options:
expose: true
Last of all, the function called from the above JS.
//ProductRecipeController.php
public function getProductCostAction(Request $request) {
$amount = $request->request->get('amount', null);
$productId = $request->request->get('product', null);
$unit = $request->request->get('unit', null);
if (empty($amount) || empty($productId)) {
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => false, 'message' => 'Bad input')));
}
$em = $this->getDoctrine()->getManager();
$product = $em->getRepository('MyBundle:Product')->find($productId);
$u = $em->getRepository('MyBundle:Measures')->find($unit);
$mass = new Mass($amount, $u->getUnit());
$fam = $mass->toUnit('g');
if (empty($product)) {
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => false, 'message' => 'Invalid product')));
}
$productCost = $product->getCostunit() * $fam;
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => true, 'productCost' => $productCost)));
}
I really hope this can be helpful to other people out there. If you find it useful please up-vote Santiag00 we spent ages trying to figure this out. The main part to notice is how we had to select the field's because of how nested they were when using embedded forms in Symfony.
One solution would be to install "FOSJsRoutingBundle" (https://github.com/FriendsOfSymfony/FOSJsRoutingBundle) to expose routes in Javascript. That way you would be able to create a new action in a Controller that would calculate a product cost and return it as a JSON to the HTML.
The action in the controller could look something like this:
/**
* #Route("/productcost", name="calculate_cost", options={"expose"=true})
* #Method("POST")
*/
public function getProductCostAction(Request $request) {
$amount = $request->request->get('amount', null);
$productId = $request->request->get('product', null);
if (empty($amount) || empty($productId)) {
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => false, 'message' => 'Bad input')));
}
$product = $this->getDoctrine()->getManager()->getRepository('ProductBundle:ProductRecipe')->findOneBy(array('id' => $productId));
if (empty($product)) {
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => false, 'message' => 'Invalid product')));
}
$productCost = $product->getCostunit() * $amount;
return new \Symfony\Component\HttpFoundation\Response(json_encode(array('success' => true, 'productCost' => $productCost)));
}
And then the AJAX call could look like this:
<script>
$(document).on('change', '.products, .amounts', function(event) {
var amount = $(this).parent().children('.amounts').val();
var productId = $(this).parent().children('.products').val();
if (amount == "" || productId == "") {
// Don't make the Ajax call if you are missing one of the two values
return false;
}
// This will be triggered every time a product or amount input field is changed
$.post(
Routing.generate('calculate_cost'),
{
// Use the corresponding amount and product ID
amount: amount,
product: productId
},
function(data) {
data = JSON.parse(data);
if (!data.success) {
// An error was thrown in the controller
alert(data.message);
}
else {
// Update the corresponding productCost field using the data from the controller
$(this).parent().children('.product-costs').val(data.productCost);
}
}
);
});
</script>
productcost2amountcost seems too complex and it has strong relation to state of the model.
If you want to update DB (or some store) each time client sends ajax request, you can use productcost2amountcost. But it is expensive and risky. (You must control the order of requests) [Solution1]
If you want to treat requests more simply, I think you should convert productcost2amountcost to stateless (as a procedure) and some small logic. (State managed by client) [Solution2]
New procedures receive some parameter (e.g. product, quantity, measure) through ajax, and send response (e.g. productcost).
(If you use temporary models (non-stored), you can use productcost2amountcost. But you should remove reference to Recipe from productcost2amountcost)
But in this case, I think you don't have to use ajax.
You can calculate all costs by javascript with the form data (include hidden unit cost and measure scale) for usability, and re-calculate in server side for update at last. [Solution3]
Logic duplication and calculation of different architecture are damn, but it is maybe simple solution.

cakephp with extjs through insert data in database

I creter js file and add tbar add button when click one blnak row add in grid
in movies controller file i write
function ext_item($id = null) {
if(!empty($this->data)) {
if($this->Movie->save($this->data))
{
$this->set('success','true');
$this->data = array();
return;
}
else {
$this->set('success',"false");
return;
}
}
}
how to pass this js data ?
how to insert data in database?
in controller file
function create() {
$newData = json_decode($this->params['form'], true); // turn the incomin json into an array
$this->data = array(
'Movie' => array(
'date_' => $newData['date_'],
'notes' => $newData['notes'],
'asset_id' => $newData['asset_id'],
'maint_picture' => $newData['maint_picture'],
'maint_condition1' => $newData['maint_condition1'],
'maint_condition2' => $newData['maint_condition2'],
'maint_condition3' => $newData['maint_condition3'],
'maint_condition4' => $newData['maint_condition4'],
)
);
if ($this->Movie->save($this->data))
{
$data['success'] = true;
} else {
$data['success'] = false;
}
$this->set('data', $data);
//$this->layout = 'ajax';
return $this->render(null, null, '/movies/ext_item');
}
then in js file
var proxy = new Ext.data.HttpProxy({
api: {
// these will map to cakephp controller actions
create: { url: 'movies_controller/create', method: 'POST' },
// read: { url: '/movies_controller/index', method: 'POST' },
//update: { url: '/movies_controller/update', method: 'POST' },
destroy: { url: 'movies_controller/destroy', method: 'POST' }
}
});
and for add row in grid
tbar: [{
text: 'Add Movie',
icon: 'images/table_add.png',
cls: 'x-btn-text-icon',
handler: function() {
grid.getStore().insert(0, new Movie({
id: 0,
notes: 'New Movie',
asset: ''
}));
rowEditor.startEditing(0, true);
}
}]
What wrong with this. it's not insert data in database.
What you want to do is add to the grid using ExtJS. The store that is attached to your grid (if you follow my answer to your last question) will handle talking to the server.
In ExtJS, the button in your toolbar to add a row to your grid should have a handler.
var toolbar = Ext.Toolbar({
// config options
handler: function() {
// in your handler you need to create a new record and insert it into your store
// if you followed my answer to your last question, you'll have setup a store with proxy, jsonreader, and jsonwriter.
// get the store component from Ext
var store = Ext.getCmp('idOfYourStore'),
NewRecord = Ext.data.Record.create(['name', 'genre', 'length']); // this array of column names should match the fields you specified for your JsonReader's fields
// now that you have your store component, and your new blank record. you can fill it in and add it to the store
var record = new NewRecord({
name: 'Name of Movie',
genre: 'Genre of Movie',
length: '1:25:22'
});
store.add(record);
store.commitChanges();
}
});
After calling add (if autosave is set to true on your store) it will automatically call the url to your cakephp application that you setup in your proxy's api under 'create'. It will send the data of this new record to that action.
So if you set up you're create proxy to point to /movies/create than inside of your MoviesController you want to setup a create() action.
Inside of the create action, you'll want to check $this->params['form'] for the incoming data from ExtJS.
function create() {
$newData = json_decode($this->params['form'], true); // turn the incomin json into an array
$this->data = array(
'Movie' => array(
'name' => $newData['name'],
'genre' => $newData['genre'],
'length' => $newData['length']
)
);
if ($this->Movie->save($this->data)) {
$data['success'] = true;
} else {
$data['success'] = false;
}
return json_encode($data);
}
After ExtJs makes the post to PHP it expects a json object back with a 'success' key in the root of the object with true, or false. You need this in json, so you can't simply just use $this->set and send it to your view. In this case I'm returning the json_encoding string.
In reality what you should do, is include the Js helper in your app_controller. Then create an element named ajaxreturn. /views/elements/ajaxreturn.ctp would contain one line.
<?php echo $this->Js->object($data) ?>
Object is responsible for turn $data into a json object. It's used instead of json_encode because PHP4 didn't have support for json_encode.
now that you have this element, in your controller you can rewrite it like so...
function create() {
$newData = json_decode($this->params['form'], true); // turn the incomin json into an array
$this->data = array(
'Movie' => array(
'name' => $newData['name'],
'genre' => $newData['genre'],
'length' => $newData['length']
)
);
if ($this->Movie->save($this->data)) {
$data['success'] = true;
} else {
$data['success'] = false;
}
$this->set('data', $data);
$this->layout = 'ajax';
return $this->render(null, null, '/elements/ajaxreturn');
}
You want to return the json string and ONLY the json string. No layout, no html, nothing but the string it will throw an error.
Once you do this, your store will know whether the call was successful, if so it will stick a row in your grid. If not, it will delete the temp. row it put in your grid.
I'm not sure I understanding what you're asking for.
RequestHandler is how cake enables handling javascript/ajax requests:
http://book.cakephp.org/view/1291/Request-Handling

PHP OO: Best strategy for dealing with form posts?

We have built custom forms and at this stage are built very well with a lot of thought. Each class has its own responsibility and so far the basic principle of OOP concepts have been used i.e. inheritance, polymorphism, encapsulation, etc. Each element of the form is an object and all objects are collected and initiated on the fly.
Now weve got to the processing part of the forms we at a loss for a strategy to deal with this and would like to ask if anyone has any pointers please?
Is there an alternative, say for instance the creation of a class that could be responsible for checking if the form has been submit and methods to gather post data, validate, etc or do people still do the old way like a quick conditional in the client to check if form submit:
if(isset($_POST["var"])
{
//process form
} else {
//show form
}
And would it be best to use a separate action page to process?
Bascially what I dont want to do is have to write some awful code thats not reusable or make use of OOP concepts. We would like to achieve something WITHOUT USING ANY FRAMEWORKS.
I would try to go with structure like this :
// public function __construct( ValidatorInterface $validator )
$form = new Form( new Validator );
// public function add_field( $name , array $rules = array() )
$form->add_field(
'name',
// that would be $_POST['name'] , and in template file <?php echo $name ?>
array(
'required' => 'Full Name is required'
// for validator to execute is_required()
));
$form->add_field(
'email' ,
array(
'required' => 'Email Address is required',
'email' => 'A valid email address is required'
// for validator to execute is_email()
));
$form->add_field( 'country' );
//name of submit button
$for->add_submitter( 'submit' );
// etc
$page = new Template();
$page->use_file( 'contact.php' );
if( $form->is_submitted() )
{
// gathers all the $_POST's from registered fields
$form->collect();
if ($form->is_valid() )
{
$page->use_file( 'done.html' );
// do some stuff
}
else
{
// public function populate( TemplateInterface $template )
// assigns field values and error messages to the template
$form->populate( $page );
}
}
echo $page->render();
And the Template class based upon this code : http://codeangel.org/articles/simple-php-template-engine.html
update
Implementation for method that registers new fields
public function add_field( $name , array $rules = array() )
{
if ( !array_key_exists( $name , $this->_fields ))
{
$this->_fields[ $name ] = array();
}
$this->_fields[ $name ]['rules'] = $rules;
}
Implementation for method that finds values of all the registered fields
public function collect()
{
$field_names = array_keys( $this->_fields );
foreach ( $field_names as $name )
{
$this->_fields[ $name ]['value'] = $this->_collect_value( $name );
}
}
protected function _collect_value($name)
{
$value = null;
if ( isset( $_POST[ $name ] ) )
{
$value = $_POST[$name];
}
$value = trim( $value );
if ( empty( $value ) ){
$value = null;
}
return $value;
}
The data collection is pretty simple process.
And on the is_valid() method call if get the Validator instance from local variable and performs actions on each registered form field ( mostly with helpfule php filter_var() function.
You're all missing very important concept. You don't want to show form, you want to ask user for some input. You actually want something like this:
$rules = array(
"name" => array("not_empty"),
"email" => array("not_empty", "email")
);
$callback = array($this, "handle_valid_post_data");
$ui->request_input($rules, $callback);
Sadly, no framework does this so you have to implement it yourself.

Categories