CakePHP 3 autocomplete AJAX responses - php

I have been trying to get cakephp to suggest input from data that is from my tables like autocomplete. I've done some reading about how some other people have done this but still can't figure it out. Currently it seems that every time my controller is waiting for an ajax request and it is always false. No errors come up from the console some i'm not sure what i'm doing wrong. I tried removing the if ($this->request->is('ajax')) statement but then I get a error about it cannot emit headers.
Here is my search function in InvoicesController which I have taken code from someone else example but failed to implement it.
public function search()
{
if ($this->request->is('ajax')) {
$this->autoRender = false;
pr('b');
$name = $this->request->query['term'];
$results = $this->Invoices->find('all', [
'conditions' => [ 'OR' => [
'id LIKE' => $id . '%',
]]
]);
$resultsArr = [];
foreach ($results as $result) {
$resultsArr[] =['label' => $result['full_name'], 'value' => $result['id']];
}
echo json_encode($resultsArr);
}
}
And here is my search.ctp
<?php use Cake\Routing\Router; ?>
<?php echo $this->Form->input('id', ['type' => 'text']);?>
<script>
jQuery('#id').autocomplete({
source:'<?php echo Router::url(array('controller' => 'Invoices', 'action' => 'search')); ?>',
minLength: 1
});
</script>
This is my invoice table and the ids are what I want to be suggested from what users type in.

I may not be seeing your exact problem but let me point out a few things I see that might help this issue.
Remove this line. It is not necessary
$this->autoRender = false;
Instead you should be doing this at the end. See using the RequestHandler
$this->set('resultsArr', $resultsArr);
// This line is what handles converting your array into json
// To get this to work you must load the request handler
$this->set('_serialize', 'resultsArr');
This will return the data without a root key
[
{"label":"Label Value"},
{"label":"Another Label Value"}
]
Or you can do it like this
$this->set('_serialize', ['resultsArr']);
This will return data like
{"resultArr":[
{"label":"Label Value"},
{"label":"Another Value"}
]}
Replace your finder query with this.
$resultArr = $this->Invoices->find('all')
->where(['id LIKE' => $id . '%'])
// If you want to remap your data use map
// All queries are collections
->map(function ($invoice) {
return ['label' => $invoice->full_name, 'id' => $invoice->id];
});
It seems to me you might want to review the new cakephp 3 orm. A lot of hard work went into writing these docs so that they could be easily read and relevant. I'm not one to push docs on people but it will save you hours of frustration.
Cakephp 3 ORM documentation
A few minor things I noticed that are also problems.
You never define $id.
You define $name but never use it.
pr is a debug statement and I am not sure why you have it.
Based on your comment, here is an update on ajax detection.
// By default the ajax detection is limited to the x-request-with header
// I didn't want to have to set that for every ajax request
// So I overrode that with the accepts header.
// Any request where Accept is application/json the system will assume it is an ajax request
$this->request->addDetector('ajax', function ($request) {
$acceptHeaders = explode(',', $request->env('HTTP_ACCEPT'));
return in_array('application/json', $acceptHeaders);
});

Related

3rd party API Response - Laravel updateOrCreate, delete the rest that previously existed?

I'm getting data from an outside API. What's the cleanest way to delete existing records that no longer appear from the API after using updateOrCreate? Right now, I have a working method which gets the collection before and forgets the collection record on the API loop. Afterwards, it deletes anything that's left.
This feels hacky and I'm sure there's a better solution. Thanks in advance!
Using: Laravel 7.x
$apiPosts = api('....')->json(); // array
$existingPosts = Post::get();
foreach ($apiPosts as $post) {
Post::updateOrCreate(['id' => $post['id']], [
'title' => $post['title'],
'body' => $post['body'],
'is_active' => $post['post_visible'],
]);
// Works but ugly
if ($existingPosts->where('id', $post['id'])->first()) {
$existingPosts->where('id', $post['id'])->first()->forget();
}
}
// Works but ugly
foreach($existingPosts as $post) {
$post->delete();
}

Update two models using yii x-editable

So, I'm using this extension: x-editable for yii.
And I'm currently trying to update two models in update() function.
I have two models:
Realisasi.php
RealisasiDeadline.php.
So when a cell is updated on table Realisasi.php (one value in column t1701 in this case), I want the function to update the corresponding value in column t1701 of table RealisasiDeadline, using column no as the foreign key.
Since I haven't found any example on Google, I made it up myself:
public function actionSimpanEdit($kode) {
Yii::import('editable.EditableSaver');
$es = new EditableSaver($_GET['model']); // 'modelName' is classname of model to be updated
$es->update();
$es2 = RealisasiDeadline::model()->findByPk($kode);//this is where I'm stuck
$es2['t1701'] = '1991-11-19';//this too
$es->update();//and this
}
This is the view.php:
array(
'name' => 't1701',
'value' => 'CHtml::value($data,"hubtarget.t1701")=== "0"?"Target Nol":$data->t1701',
'header' => 'Bkl Selatan',
'class' => 'editable.EditableColumn',
'editable' => array(
'url' => $this->createUrl('simpanEdit', array('model' => 'Realisasi', 'kode'=>'$data->no')),
)
),
What have I missed? Is it possible at all to do? If not, is there another solution?
UPDATE
It's not showing any error. But the value in table RealisasiDeadline doesn't change, only the one in Realisasi does.
Added some comments to original function so you can improve upon it. Biggest issue with this code is that looking at it I have no idea what it does.
public function actionSimpanEdit($kode) {
Yii::import('editable.EditableSaver'); // Should be at the top of the file
// For the love of god use descriptive variable names
$es = new EditableSaver($_GET['model']); // Would prefer to have model as actions argument
$es->update();
$es2 = RealisasiDeadline::model()->findByPk($kode); // no idea what this model is responsible for
$es2['t1701'] = '1991-11-19'; // no idea what attribute t1701 is, use descriptive names
$es->update();
}
I have refactored it a bit. Still have no idea what it does ;/
public function actionSimpanEdit($id, $model) {
$editableSaver = new EditableSaver($model);
$editableSaver->update();
$deadline = RealisasiDeadline::model()->findByPk($id);
if($deadline instanceof RealisasiDeadline) {
$deadline->t1701 = '1991-11-19';
if(!$deadline->update()) {
// something went wrong
}
} else {
// not found
}
}
Going back to your problem. It is probably caused by RealisasiDeadline model being not found or some behavior or event preventing it from update.

Laravel 4 and debugbar - show pdo queries?

I have installed: https://github.com/barryvdh/laravel-debugbar using composer.
I followed install procedures, and now done.
But how do I see my PDO mysql queries? I am building a RESTful api, without any HTML/view rendering.
Don't know if it is for any use, but heres some example of my code:
// the controller
public function feed($exclude = null) {
$feed = $this->item->feed()->with('attributes', 'images', 'user');
if($exclude)
$feed->whereNotIn('id', explode(',', $exclude));
return ['items' => $this->itemTransformer->transformCollection($feed->get()->toArray())]; // woud like to debug this query
}
// the router
Route::get('items/feed/{exclude?}', ['protected' => true, 'uses' => 'ItemsController#feed']);
The Debugbar should log all requests when enabled, even when not outputting HTML. You could try to create 1 simple HTML page so that the Debugbar gets rendered. You can click the Browse button (on the right, next to the close button) to browse previous requests. You should get a list with collected requests, which you can filter by url/ip/method. Clicking that will show that information on the Debugbar.
Easy:
public function feed($exclude = null) {
$feed = $this->item->feed()->with('attributes', 'images', 'user');
if($exclude)
$feed->whereNotIn('id', explode(',', $exclude));
$items = ['items' => $this->itemTransformer->transformCollection($feed->get()->toArray())];
echo json_encode($items); // echo instead of return. Will trigger HTML to render and
}

How to get Json input in CakePhp Restful API using PUT

I am able to view and delete the data by passing ID in the URl in format of:
/apis/view/id.json
using:
public function view($id) {
$api = $this->Api->findById($id);
$this->set(array(
'api' => $api,
'_serialize' => array('api')
));
}
Similarly I want to implement add and edit, where I can pass data in Json format in the HTTP body and store/edit it in the database.
I couldn't follow the this solution:
CakePHP API PUT with JSON input
I couldn't understand how to use
$data = $this->request->input('json_decode');
to achieve it.
Add can simply be used as given in documentation by appending .json to it. The URL at which you post the data will become /apis.json. This will automatically access the add() method.
Assuming you pass json values email and password in such format: {"email":"abc#def.com","password":"123456"}
public function add(){
$data=$this->request->input('json_decode', true ); //$data stores the json
//input. Remember, if you do not add 'true', it will not store in array format.
$data = $this->Api->findByEmailAndPassword($data['email'],$data['password']);
//simple check to see if posted values match values in model "Api".
if($data) {$this->set(array(
'data' => $data,
'_serialize' => array('data')));}
else{ $this->set(array(
'data' => "sorry",
'_serialize' => array('data')));}
}// The last if else is to check if $data is true, ie. if value matches,
// it will send the input values back in JSON response. If the email pass
// is not found, it will return "Sorry" in json format only.
Hope that answers your question! Put is also very similar, except it will check if the data exists, if it doesn't it will create or else it will modify existing data. If you have any further doubts don't hesitate to ask :)
As explained in the linked documentation, CakeRequest::input() reads the raw input data, and optionally passes it through a decoding function.
So $this->request->input('json_decode') gives you the decoded JSON input, and in case it's formatting follows the Cake conventions, you can simply pass it to one of Model save methods.
Here's a very basic (untested) example:
public function add()
{
if($this->request->is('put'))
{
$data = $this->request->input('json_decode', true);
$api = $this->Api->save($data);
$validationErrors => $this->Api->validationErrors;
$this->set(array
(
'api' => $api,
'validationErrors' => $validationErrors,
'_serialize' => array('api', 'validationErrors')
));
}
}
This will try to save the data and return the save result as well as possible validation errors.
In case the formatting of the input data doesn't follow the Cake conventions, you'll have to transform it accordingly.

CodeIgniter - Variable scope

I have a controller which I use for a login form. In the view, I have a {error} variable which I want to fill in by using the parser lib, when there is an error. I have a function index() in my controller, controlled by array $init which sets some base variables and the error message to '':
function index()
{
$init = array(
'base_url' => base_url(),
'title' => 'Login',
'error' => ''
);
$this->parser->parse('include/header', $init);
$this->parser->parse('login/index', $init);
$this->parser->parse('include/footer', $init);
}
At the end of my login script, I have the following:
if { // query successful }
else
{
$init['error'] = "fail";
$this->parser->parse('login/index', $init);
}
Now, of course this doesn't work. First of all, it only loads the index view, without header and footer, and it fails at setting the original $init['error'] to (in this case) "fail". I was trying to just call $this->index() with perhaps the array as argument, but I can't seem to figure out how I can pass a new $init['error'] which overrides the original one. Actually, while typing this, it seems to impossible to do what I want to do, as the original value will always override anything new.. since I declare it as nothing ('').
So, is there a way to get my error message in there, or not? And if so, how. If not, how would I go about getting my error message in the right spot? (my view: {error}. I've tried stuff with 'global' to bypass the variable scope but alas, this failed. Thanks a lot in advance.
$init musst be modified before generating your view.
To load your header and footer you can include the following command and the footer's equivalent into your view.
<?php $this->load->view('_header'); ?>
to display errors, you can as well use validation_errors()
if you are using the codeigniter form validation.
if you are using the datamapper orm for codeigniter you can write model validations, and if a query fails due to validation rule violation, you get a proper error message in the ->error property of your model.
Code for your model:
var $validation = array(
'user_name' => array(
'rules' => array('required', 'max_length' => 120),
'label' => 'Name'
)
);
You might try this:
function index() {
$init = array(
'base_url' => base_url(),
'title' => 'Login',
'error' => ''
);
$string = $this->parser->parse('include/header', $init, TRUE);
$string .= $this->parser->parse('login/index', $init, TRUE);
$string .= $this->parser->parse('include/footer', $init, TRUE);
$this->parser->parse_string(string);
}
In parse()you can pass TRUE (boolean) to the third parameter, when you want data returned instead of being sent (immediately) to the output class. By the other hand, the method parse_string works exactly like `parse(), only accepts a string as the first parameter in place of a view file, thus it works in conjunction.

Categories