-
Notifications
You must be signed in to change notification settings - Fork 0
/
readme.html
309 lines (244 loc) · 14.3 KB
/
readme.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>ReadMe for PkExtensions for Laravel >= 5.1</title>
<style>
h1 {
color: red;
}
</style>
</head>
<body>
<!--
15-Feb-16 16:40
@Author: Paul Kirkaas
Extensions for Laravel applications.
-->
<h1>What are PkExtensions?</h1>
<p>PkExtensions consist of multiple components, some very Laravel specific, others more generally applicable with PHP/JavaScript/CSS.
<p>The guiding principal is to provide a lot of reusable functionality for things developers do all the time - so you don't have to re-implement similar functionality every time. For example, enhanced form inputs (like, multi-select check boxes), bound by specific CSS class names to JavaScript/jQuery event handlers. Which support binding complex, nested, one to many to many forms with hidden templates and deletable data sets - so almost the only code you need to write to implement complex form to abstract DB data structures are the PHP/HTML templates.
<h2>What do PkExtensions consist of?</h2>
<p>They consist of:
<ul>
<li><b>Laravel Extensions</b>: Classes that extend existing Laravel Classes, like PkModel, PkController, etc.
<li><b>New Utility Classes</b>: Stand-alone classes that I find really useful, that don't depend on Laravel - my favorite is "PartialSet" - an extension of ArrayObject that can contain almost anything and implements <tt>__toString()</tt> so you can just print / echo it out.
<li><b>Supporting PHP libraries</b>: PHP functions I find really useful. There can be some naming conflicts with other libraries.
<li><b>Supporting JavaScript, jQuery, CSS and SCSS Libraries</b>: I do lots of things where PHP and JS have to co-operate. These libraries help in a very generic way - like automatically building dialog boxes from PHP, just based on CSS class names and 'data-xxx' attribute names and values. Assumes jQuery & jQuery UI.
</li><b>Artisan helper scripts</b>: Just a couple of commands to:
<ul>
<li>Generate migration files from PkModel class definitions
<li>Clear all caches, autoloads, everything in a single command
</ul>
</ul>
<h2>What is the current status/version?</h2>
<p>Very much Alpha - continually evolving, quite a bit of commented out code and experiments - but what works works pretty well and hopefully could be very useful to other developers.
<p>See below for highlights of some of the more interesting features.
<h1>Extensions to existing Laravel Classes</h1>
<h2>PkModel</h2>
<p>This extends Eloquent Model and fills in what seemed to be some very obvious gaps.
<h3>public function saveRelations(Array $array = [])</h3>
<p>For one-to-many Objects, takes the multi-dimensional array of data and updates the Model instance and all its possessions/collections.
<p>For example, if you have:
<pre>
Class Cart extends PkModel {
static $table_field_defs = [
'id'=> 'increments',
'cart_name'=>'string',
'owner_id' => 'integer'
];
public function items() {
return hasMany('Models\\Item');
}
static $load_relations [ #Tells the class what one-to-many relationships it has
'items' => 'Models\\Item',
];
#...
}
</pre>
<p>... and ...
<pre>
Class Item extends PkModel {
static $table_field_defs = [
'id'=> 'increments',
'item_name'=>'string',
'cart_id' => 'integer'
'price' => 'integer',
];
#...
}
</pre>
<p>If you have an array of data representing the cart and its items, like:
<pre>
$data = [
'id'=>7, #The cart ID - ignored because we know the cart
'cart_name' => 'Happy Cart'
'items' => [
[
'id'=>234,
'item_name'=>'Fishing Line',
'cart_id' => 7, #Again ignored, we still know the cart
'price' => 5,
],
[
#No item ID - this is new!
'item_name'=>'Reel',
'cart_id' => 7, #Again ignored, we still know the cart
'price' => 22,
],
],
];
</pre>
<p>If you call:
<pre>
$myCart->updateRelations($data);
</pre>
<p>This method will:
<ul>
<li>Update any of the changed cart fields to the values of the corresponding array keys - like 'cart_name'
<li>Any key field in the data array that's not an attribute of $myCart is ignored (<tt>_token</tt>, for example.
<li>Any attribute name that's part of the Cart definition but absent as a key in the data array is ignored.
<li>Any Cart attribute name that is present as a key in the data array with a value of null is reset/nulled in the <tt>$myCart->$attributeName = null</tt>
<li>When we get to a $key that is not an attribute but a key in the model's <tt>static $load_relations[]</tt> array, ('items'), iterate through any "relation" value arrays (items) and:
<ul>
<li>If the 'items' key doesn't exist, don't do anything to the cart's items.
<li>If the 'items' key exists but has a value of null, empty string or [], delete all 'Item' Model/instances with <tt>'cart_id'==7</tt> (This cart has been emptied)
<li>If the 'item' key value is an indexed array of associative arrays, go through and:
<ul>
<li>For any item data arrays that don't have an 'id' key - this is a new item added to the cart - create it with the associated data and 'cart_id'
<li>If an item data array has an id, instantiate the corresponding 'Item' model/object, and if any of it's attributes are different from the corresponding attribute in the item value array, update it.
<li>If any item id's from the cart's original collection are missing from the arrays of items data, they have been removed and are deleted from the DB and the Cart.
</ul>
<li>This is recursive, so if the "Item" model definition has collections/one-to-many relationships of its own, these are processed and persisted as well.
</ul>
</ul>
<p>What is the value of that? In conjunction with <tt>PkController</tt>, (described below), it makes processing / editing of complex forms that map to a single model object with an arbitrary number and nesting of one-to-many collections/relations a snap. The Controller method to fully edit a Cart form, with adding/deleting/modifying its items, etc is:
<pre>
Class CartController extends PkController {
pubic function edit(Cart $cart) {
$this->processSubmit($cart);
return view('cart.edit', ['cart'=>$cart]);
}
}
</pre>
<h3>public function saveM2MRelations(Array $array = [])</h3>
<p>Conceptually similar to <tt>saveRelations()</tt>, but for many-to-many relationships, through a pivot table. Say, a Person following a Person through the Follow pivot class.
<p>For this, we define a static array variable on the class like:
<pre>
Class Person extends PkModel {
public static $load_many_to_many = [
'friends' =>
[
'other_model' => 'Models\Person',
'pivot_model' => 'Models\Friend',
#OR
'pivot_table' => 'friends',
'my_key' => 'following_id', #Key for me (Follower) In the pivot table/model
'other_key' => 'followed_id', #Key for who I'm following n the pivot table/model
],
];
</pre>
<p>Here we obviously don't edit or delete the other side of the relationship (Followed Person), we edit/create/delete entries in the Pivot Model/Table.
<h3>DB Table Definition to generate Migration Files, etc</h3>
<p>It seems ridiculous that Eloquent Model classes don't know what attributes they have until they read the DB. Also, if you have a bunch of Migration files, it's hard to get an overview of all the DB table attributes by inspecting the code.
<h3>PkModel::$table_field_defs, PkModel::buildMigrationFieldDefs(), etc</h3>
<p>If you extend PkModel and include a static array member: $table_field_defs as follows (Example):
<pre>
class QProfile extends PkModel {
public static $table_field_defs = [
'id' => 'increments',
'user_id' => ['type' => 'integer', 'methods' => 'index'],
'viewableby_id' => ['type' => 'integer', 'methods' => ['default' => 10]],
'privatedescription' => ['type' => 'string', 'methods' => 'nullable'],
'publicdescription' => ['type' => 'string', 'methods' => 'nullable'],
'name' => ['type' => 'string', 'methods' => 'nullable'],
'location' => ['type' => 'string', 'methods' => 'nullable'],
'searchable' => ['type' => 'boolean', 'methods' => ['default' => false]],
'about' => ['type' => 'text', 'methods' => 'nullable'],
];
#...
}
</pre>
<p>You can generate Migration files by running:
<pre>
php artisan make:migration QProfile
</pre>
<p>This will create the initial migration file to create the table <tt>q_profiles</tt>, and if you change/add/remove the Model field definitions in <tt>static $table_field_defs</tt>, the <tt>artisan make:migration QProfile</tt> command will do a reasonable job of creating the Migration Update scripts - though you might have to modify them by hand for complicated changes.
<h3>static::getStaticAttributeNames()</h3>
<p>Returns all the DB field names from the underlying table.
<h1>Eliminate 80% of the JavaScript you write</h1>
<p><b>Managing Dialogs, AJAX calls, Form & DB handling, etc.</b>
<p>You've already written almost the same code dozens of times with just minor variations. Just follow a few conventions with CSS class names and <tt>data-XXX</tt> attributes in your HTML, and it's all done. If you use Laravel, <tt>PkFormBuilder</tt> extends Laravel Collective <tt>FormBuilder</tt> with methods to create the HTML components that work with the jQuery libraries.
<h2>AJAX handling on click</h2>
<ul>
<li><b>Create a DOM node you want to trigger / process the AJAX</b>: Could be just a button or link, or a complex nested collection of HTML elements. Add an attribute called <tt>data-pk-ajax-element</tt>. It doesn't need a value, jQuery will attach to it. No problem if you have many unrelated AJAX handler components on the same page.
<p>If you are using Laravel, <tt>PkForm::ajaxElement($options)</tt> builds the HTML component for you.
<li><b>Add a few more <tt>data-XXX</tt> attributes</b>:
<ul>
<li><tt>data-ajax-url</tt>: The URL for the AJAX call (the only other required attribute - the rest are optional)
<li><tt>data-ajax-params</tt>: An encoded query string of parameters and values. You don't have to encode them, though. Just set <tt>$options['params'] = ['param1'=>$val1, 'param1'=>$val2, ...]</tt> and leave it to <tt>PkForm::ajaxElement()</tt>.
<li>Specify the other behavior you want in the $options values. What you want to do with the AJAX response, etc.
<li>See the documented source codes for all the details and options.
</ul>
<li><tt>$('body').on('click', '[data-pk-ajax-element]', function (event) {...</tt> attaches to every element on the page with the 'data-pk-ajax-element' attribute, and submits the AJAX call based on the attributes defined in the target element.
<li>The event handler also processes the AJAX result based on the DOM element attributes.
</ul>
<h2>jQuery UI Dialogs</h2>
<p>A typical dialog process involves a user clicking on something, which pops up a dialog to take some action.
<ul>
<li><b>On all elements the user clicks to initiate a dialog</b>: Add the CSS class <tt>js-dialog-button</tt>. The JS event handler will attach to that/them.
<li><b>Add the attribute <tt>data-dialog</tt></b>: to each, set the value to name of the hidden dialog template the click should activate.
<li><b>Create the dialog template(s)</b>: Assign them each the CSS class <tt>js-dialog-content</tt>. The included CSS sets <tt>display: none;<tt>.
<li><b>To each template, add the attribute <tt>data-dialog</tt></b>: Set it to the value of the corresponding attribute in the clickable element that should launch the dialog.
<li><b>But Wait!</b>: Maybe you have 50 identical buttons on a page, that should pop up the same dialog, only with some different parameters - like a table of users, each with an "Edit" button to bring up a "User Edit" form. But different <tt>user_id</tt>. So each button can have several <tt>data-param</tt> attributes, with value of the corresponding user_id or whatever. The JS event handler substitutes the value from the button to the popped up dialog form. No extra JS required.
</ul>
<h2>Binding one->many ORM objects, DB tables and forms</h2>
<p>Edit/create/delete most data and relationships with almost no code.
<h1>More Features/Extensions</h1>
<h2>PkSearchModel</h2>
<p>Supports building persistent complex user created searches/queries.
<p>PkSearchModel extends PkModel and works with PkController and <tt>BuildQueryTrait</b> to build, execute and persist complex user created searches. Like for a dating site use case:
<p><tt>Find all <b>women</b> WHERE (<b>age BETWEEN 27, 45</b>) AND <b>Zodiac Sign in one of ('aries', 'gemini', 'capricorn')</b> AND <b>religion = "don't care"</b></tt>
<p>The abstract class PkSearchModel extends <b>PkModel</b> and uses <b>BuildQueryTrait</b>
<p>Any Controller that wants to manage searches should also use <b>BuildQueryTrait</b>
<p>For example, to create a search for Person objects, first define the Person model/class :
<pre>
class Person extends PkModel {
public static $table_field_defs = [
'id' => 'increments',
'name' => 'string',
'age' => 'integer',
'religion_id' => 'integer', #Assumes reference map ID's => "Religion" strings
'height' => 'integer',
'zip' => 'integer',
#...
];
#...
}
</pre>
<p>Note the static array <tt>$table_field_defs</tt>
<p>A QueryModel to save searches for a Person would look like:
<pre>
Class PersonSearch extends PkSearchModel {
use PersonSearchTrait; #Trait because also used in PersonController::search() action
public $targetModel = Person::class; #The model to perform the search on
public static $table_field_defs = [
'id' => 'increments',
#Simple scalar comparison - greater than, less than, etc.
'age_val' => 'integer', #What age is the comparison against?
'age_crit' => 'string', #What is the criterion? Greater than age_val? Less than?
# Pick multiple acceptable values
'religion_id_val' => 'string', #JSON encoded array of acceptable values
'religion_id_crit' => 'string', #IN? NOT IN?
#A Range / 'BETWEEN' query:
'height_crit' => 'string', # 'BETWEEN' or '0'/Don't Care
'height_minval' => 'integer',
'height_maxval' => 'integer',
#Uses a custom method to calculate "within X Miles of ZIP"
'withinzip_val' => 'integer',
'withinzip_crit' => 'string',
#...
}
</pre>
</body>
</html>