Skip to content

Commit bbbf416

Browse files
BooleanTypeBooleanType
authored andcommitted
Initial commit
0 parents  commit bbbf416

File tree

5 files changed

+370
-0
lines changed

5 files changed

+370
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nbproject/

README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# php-jquery-ajax-handling-conception
2+
3+
#### This functionality is advice on making requests to the server through the AJAX. This is just a *template* that can be copied from project to project and which allows you to process all possible server responses, adhering to the same style throughout the project.
4+
5+
- The technology bundle used is PHP and JavaScript, but since this is only a concept, a similar approach can be used in other programming languages.
6+
7+
### Server side
8+
#### Requirements
9+
1. Encapsulate all code in `try… catch` statement.
10+
2. Every condition, which suggests a failure, should throw PHP Exception, for example:
11+
12+
```php
13+
if ($reg_id == '') {
14+
throw new Exception(json_encode([500, 'Error description.']));
15+
}
16+
if(!$someThing->save()) {
17+
throw new Exception('Something is not saved');
18+
}
19+
```
20+
#### Example of pseudo-code processing an AJAX request on the server
21+
```php
22+
try {
23+
if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
24+
throw new Exception(json_encode([403, 'Forbidden']));
25+
}
26+
27+
if (array_key_exists('id', $_POST)) {
28+
$id = (int) $_POST['id'];
29+
$news = News::findOne($id);
30+
31+
if (!is_object($news)) {
32+
throw new Exception(json_encode([404, 'No news']);
33+
}
34+
// Update found record.
35+
$news->author = 'Stephen King';
36+
if (!$news->save()) {
37+
throw new Exception(json_encode([404, 'Updating error']);
38+
}
39+
// Success (return status and encoded data).
40+
die(json_encode([200, json_encode($news)]));
41+
42+
} else { // Wrong data from user.
43+
throw new Exception(json_encode([400, 'Bad request']));
44+
}
45+
} catch (Exception $e) {
46+
die($e->getMessage());
47+
}
48+
```
49+
Exception argument can be JSON-parsable string, which **must** contain status code, (**Case 1**) or just
50+
error message (**Case 2**).
51+
52+
##### Case 1
53+
54+
```php
55+
throw new Exception(json_encode([404, 'No news']);
56+
```
57+
58+
- `404` is a status code for appropriate handling on client side. Something like HTTP status codes
59+
(https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). You can choose any code you want;
60+
- `'No news'` is an example error message, which will be rendered for end user.
61+
62+
##### Case 2 (without status code):
63+
64+
```php
65+
throw new Exception('Some error description');
66+
```
67+
68+
In this case data parsing will fail on client side, so `catch` block in `done()` method will triggered. `msgText` variable will contain 'Some error description' string (see [Client Side](#client-side) section).
69+
70+
> **WARNING**: it is necessary to understand, if all operations really should throw Exceptions. Exception throwing stops code execution in `try {}` block, and further control passes to `catch()` block. That's why such operations as logging shouldn't stop all process, if writing to log table is failed.
71+
72+
#### Success answer format
73+
74+
```php
75+
die(
76+
json_encode([ // *3*
77+
200, // *1*
78+
json_encode($news) // *2*
79+
])
80+
);
81+
```
82+
83+
- \*1\* is **successful** status code, **required**;
84+
- \*2\* is **optional**. Data, returned by AJAX. **If data is array, it should be encoded by `json_encode()`**;
85+
- \*1\* and \*2\* arguments should be elements of encoded array (\*3\*), which is passed to `die()`.
86+
87+
##### Examples:
88+
```php
89+
// Only status, without data.
90+
die(json_encode([200]));
91+
// Successful status and $news array.
92+
die(json_encode([200, json_encode($news)]));
93+
// Successful status and appropriate message.
94+
die(json_encode([200, 'Articles were updated successfully!']));
95+
```
96+
97+
----
98+
### Client side
99+
100+
#### Example of handling AJAX request on client side
101+
Code below is a template of how AJAX requests handling on the client side can be organized.
102+
103+
```javascript
104+
let msgText;
105+
$.ajax({
106+
'type': 'POST',
107+
'url': url,
108+
'data': { ... }
109+
110+
}).done(function (res) {
111+
let data, statusCode;
112+
try {
113+
data = JSON.parse(res);
114+
if ($.isPlainObject(data) || $.isArray(data)) { // Should be object (or arr).
115+
statusCode = data[0];
116+
if (statusCode == 200) { // If success.
117+
118+
// DO YOUR SUCCESS LOGIC HERE!!!
119+
120+
} else { // Status code != 200.
121+
throw new TypeError(data[1]);
122+
}
123+
124+
// Data is parsable, but it is not an obj/arr, as planned (for ex., '"foo"', 'true', 'null').
125+
} else {
126+
throw new TypeError(data);
127+
}
128+
} catch (e) {
129+
// SyntaxError exc throws, if res is unparsable (JSON.parse() failed):
130+
// unhandled exception is thrown, which isn't related to validation (for ex., UnknownPropertyException).
131+
msgText = (e.name == 'SyntaxError') ? res : e.message;
132+
133+
// DO ERROR LOGIC HERE (IF NEEDED).
134+
135+
}
136+
}).fail(function (jqXHR, textStatus, errorThrown) {
137+
msgText = errorThrown;
138+
139+
// DO ERROR LOGIC OR COPY IT FROM PREVIOUS CATCH STATEMENT (IF NEEDED).
140+
141+
}).always(function () {
142+
// Always triggers - in done() and fail() case as well.
143+
144+
// DO LOGIC, WHICH SHOULD BE PRESENT ANYWAY - IF AJAX IS SUCCESSFUL OR FAILED (IF NEEDED).
145+
146+
});
147+
```
148+
149+
There is AJAX handling template in example above, which takes into account all possible errors
150+
that have occurred on the server.
151+
152+
In code...
153+
```javascript
154+
if (statusCode == 200) { // If success.
155+
// DO YOUR SUCCESS LOGIC HERE!!!
156+
```
157+
... you can add *additional logic*, related to successful query, for example:
158+
```javascript
159+
if (statusCode == 200) { // If success.
160+
msgText = 'Saving was successful!';
161+
// Work with data, retrieved from server in success case.
162+
let requestedData = JSON.parse(data[1]);
163+
```
164+
You can handle any status code, which was returned from server, for example:
165+
```javascript
166+
// If server returns a lot of statuses, you can use switch() statement.
167+
if (statusCode == 200) { // If success.
168+
msgText = 'Saving was successful!';
169+
msgContainerBgColor = 'green';
170+
171+
} else if (statusCode == 404) { // Not an error, just information.
172+
msgText = 'No news';
173+
msgContainerBgColor = 'blue';
174+
175+
} else { // Status code = 500 or another.
176+
throw new TypeError(data[1]);
177+
// Don't do error logic here
178+
// (defining msgText and msgContainerBgColor variables, etc.);
179+
// do it in catch() block instead (like in full example above).
180+
}
181+
```
182+
`always()` method of the jqXHR object allows to implement logic, which always should be present.
183+
184+
#### Demo
185+
Working demo can be found in `demo` directory of current project. Code in that project is just an example, so, you can modify it according to your needs.
186+
Entrance file is `view.html`.

demo/helper.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/**
4+
* Generates specific string: encoded array with status code and error message.
5+
* This string is AJAX response message in case, when exception is thrown.
6+
* Usage:
7+
* throw new Exception(errMsg(403, 'AccessRestricted'));
8+
* @param int $statusCode
9+
* @param string|arr $err Error message
10+
* @return string Err message with specific structure for AJAX response in exception throwing case
11+
*/
12+
function errMsg($statusCode = 500, $err = null)
13+
{
14+
$statusCode = (int) $statusCode;
15+
16+
// By default.
17+
$errMsg = 'Error';
18+
19+
if (is_scalar($err) && !is_bool($err)) { // Integer, float, string.
20+
$errMsg = $err;
21+
} elseif (is_array($err)) {
22+
$errMsg = multiImplode($err);
23+
}
24+
25+
return json_encode([$statusCode, $errMsg]);
26+
}
27+
28+
/**
29+
* Recursively implodes multi-dimensional array $array.
30+
* For ex.:
31+
* $arr = [
32+
* 'field_1' => [
33+
* 0 => 'field_1 description.',
34+
* 1 => 'Another field_1 description.',
35+
* ],
36+
* 'field_2' => [
37+
* 0 => 'field_2 description.',
38+
* 1 => 'Another field_2 description.',
39+
* ]
40+
* ];
41+
*
42+
* // "field_1 description., Another field_1 description., field_2 description., Another field_2 description."
43+
* $imploded = multiImplode($arr);
44+
* @param array $array The array of strings to implode
45+
* @param string $glue The glue to join the pieces of the array
46+
*/
47+
function multiImplode($array, $glue = ', ')
48+
{
49+
$ret = '';
50+
51+
foreach ($array as $item) {
52+
if (is_array($item)) {
53+
$ret .= multiImplode($item, $glue) . $glue;
54+
} else {
55+
$ret .= $item . $glue;
56+
}
57+
}
58+
59+
// If $ret = '', substr(...) returns FALSE.
60+
$ret = ($ret !== '') ? substr($ret, 0, 0 - strlen($glue)) : $ret;
61+
62+
return $ret;
63+
}

demo/index.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php// Include some functions for handling possible errors.include_once 'helper.php';try { if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') { throw new Exception(json_encode([403, 'Forbidden'])); } // Here is your custom code. // $client_data processing below is just for example. if (!isset($_POST['client_data'])) { throw new Exception(json_encode([400, 'Bad request'])); } $clientData = (int) $_POST['client_data']; if ($clientData == 0) { $errData = [ 'client_data' => [ 0 => 'Error description.', 1 => 'Another error description.', ] ]; throw new Exception(errMsg(500, $errData)); } else { $requestedData = [ 'Requested Data', 'More Requested Data' ]; die(json_encode([200, json_encode($requestedData)])); }} catch (Exception $e) { die($e->getMessage());}

demo/view.html

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width">
6+
<title>php-jquery-ajax-handling-conception</title>
7+
<style>
8+
#actions {
9+
width:100%;
10+
text-align: center;
11+
}
12+
.inner {
13+
display: inline-block;
14+
}
15+
#request-msg-container {
16+
position: absolute;
17+
left: 50%;
18+
top: 20%;
19+
transform: translate(-50%, -50%);
20+
-ms-transform: translate(-50%, -50%); /* for IE 9 */
21+
-webkit-transform: translate(-50%, -50%); /* for Safari */
22+
}
23+
</style>
24+
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
25+
</head>
26+
<body>
27+
28+
<div id="actions">
29+
<div>Click on buttons below to trigger AJAX call.</div>
30+
<br>
31+
<div class="inner"><button data-id="0">Server Exception</button></div>
32+
<div class="inner"><button data-id="1">AJAX Failure</button></div>
33+
<div class="inner"><button data-id="2">Success</button></div>
34+
</div>
35+
<div id="request-msg-container"><pre></pre></div>
36+
<script>
37+
!function () {
38+
$('.inner').click(function (e) {
39+
triggerAjaxCall($(e.target).attr('data-id'));
40+
});
41+
42+
function triggerAjaxCall (client_data) {
43+
let msgText,
44+
msgContainer = $('#request-msg-container pre'),
45+
// URL is changed to 'wrong-url' only to show .fail() handler working.
46+
url = (client_data == 1)
47+
? 'wrong-url'
48+
: 'index.php';
49+
50+
msgContainer.html('');
51+
52+
$.ajax({
53+
'type': 'POST',
54+
'url': url,
55+
'data': {
56+
'client_data' : client_data,
57+
},
58+
}).done(function (res) {
59+
let data, statusCode;
60+
61+
try {
62+
data = JSON.parse(res);
63+
64+
if ($.isPlainObject(data) || $.isArray(data)) { // Should be object (or arr).
65+
statusCode = data[0];
66+
67+
if (statusCode == 200) { // If success.
68+
69+
// DO YOUR SUCCESS LOGIC HERE!!!
70+
71+
let requestedData = JSON.parse(data[1]);
72+
for (let keys in requestedData){
73+
msgContainer
74+
.css('color', 'green')
75+
.append('<div>' + requestedData[keys] + '</div>');
76+
}
77+
78+
} else { // Status code != 200.
79+
throw new TypeError(data[1]);
80+
}
81+
82+
// Data is parsable, but it is not an obj/arr, as planned (for ex., '"foo"', 'true', 'null').
83+
} else {
84+
throw new TypeError(data);
85+
}
86+
87+
} catch (e) {
88+
// SyntaxError exc throws, if res is unparsable (JSON.parse() failed):
89+
// unhandled exception is thrown, which isn't related to validation (for ex., UnknownPropertyException).
90+
msgText = (e.name == 'SyntaxError') ? res : e.message;
91+
92+
// DO ERROR LOGIC HERE (IF NEEDED).
93+
94+
msgContainer
95+
.css('color', 'red')
96+
.html(msgText);
97+
}
98+
99+
}).fail(function (jqXHR, textStatus, errorThrown) {
100+
msgText = errorThrown;
101+
102+
// DO ERROR LOGIC OR COPY IT FROM PREVIOUS CATCH STATEMENT (IF NEEDED).
103+
104+
msgContainer
105+
.css('color', 'red')
106+
.html(msgText);
107+
108+
}).always(function () { // Always triggers - in done() and fail() case as well.
109+
110+
// DO LOGIC, WHICH SHOULD BE PRESENT ANYWAY - IF AJAX IS SUCCESSFUL OR FAILED (IF NEEDED).
111+
112+
msgContainer
113+
.append('<div style="color: blue;">I always trigger.</div>');
114+
});
115+
}
116+
}();
117+
</script>
118+
</body>
119+
</html>

0 commit comments

Comments
 (0)