
DataTables - Adding Buttons to a FixedHeader
Mar 1st
I was using both the FixedHeader and Buttons extensions of DataTables and I needed the buttons to remain visible when the user scrolled.
The solution I chose was to move the buttons DOM into the table head.
Since the buttons are in a div and thead is part of a table, a tr is used with a td that has a colspan of the entire table.
var dtButtons = document.querySelector(".dt-buttons");
var row, cell;
row = document.createElement("tr");
cell = document.createElement("td");
cell.id = "magic-button-row";
cell.setAttribute("colspan", /*** Put the number of columns in your table here ***/);
cell.appendChild(dtButtons);
row.appendChild(cell);
document.querySelector("#display-table thead").insertBefore(row, document.querySelector("tr:first-of-type"));
There is a tiny bit of CSS, too.
#magic-button-row {
padding: 0;
margin: 0;
text-align: left;
}
This post courtesy of Game Creek Video.

Revised Dijit/Tree Menu for Symfony
Feb 14th
This is the final implementation for the tree menu I'm using for a Symfony application.
The first approach wrote the JSON for the menu store into the template. This was a little awkward and as the application grew, the amount of content increased.
Next, I had an idea that I could create a multi-tiered tree which would have a static top tier, but include content in the lower levels. The value of this was that the user could navigate directly to content, for example the details of a client. Lazy loading was used to limit the number of requests and size of responses. It worked. But it was slow. And I realized that as the amount of data increased, it would get slower. In addition, navigating through three or more levels on a tree is inefficient. So, I wanted something that was faster and streamlined.
The first change I made was to add an autocomplete search box, which allows the user enter the name of the item they need. It could be the name of a person, a barcode or a company. The search code isn't complete.
Then I rewrote the JsonRenderer and the MenuStoreController such that there is only a static menu delivered via Ajax.
services.yml
These are the service configurations.
app.admin.menu_store_controller:
class: AppBundle\Controller\Api\Admin\Common\MenuStoreController
app.menu_renderer:
class: AppBundle\Menu\JsonRenderer
arguments: [ "@twig", "knp_menu.html.twig", "@knp_menu.matcher", {"translator": "@translator" }]
tags:
- { name: knp_menu.renderer, alias: json }
MenuStoreController
The MenuStoreController calls the KnpMenuBuilder, then the JsonRenderer and delivers the result as JSON to the client.
<?php
namespace AppBundle\Controller\Api\Admin\Common;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\View;
class MenuStoreController extends FOSRestController
{
/**
* @View()
*/
public function getAdminmenuAction( Request $request )
{
$this->denyAccessUnlessGranted( 'ROLE_ADMIN', null, 'Unable to access this page!' );
$adminMenu = $this->get( 'app.menu_builder' )->createAdminMenu( [] );
$renderer = $this->get( 'app.menu_renderer' );
return array_values( $renderer->render( $adminMenu ) );
}
}
JsonRenderer
The JsonRenderer iterates through the menu and creates the data for the dijit/Tree.
<?php
namespace AppBundle\Menu;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\Renderer\RendererInterface;
class JsonRenderer implements RendererInterface
{
/**
* @var \Twig_Environment
*/
private $environment;
private $matcher;
private $defaultOptions;
private $translator;
/**
* @param \Twig_Environment $environment
* @param string $template
* @param MatcherInterface $matcher
* @param array $defaultOptions
*/
public function __construct( \Twig_Environment $environment, $template, MatcherInterface $matcher, array $defaultOptions = array() )
{
$this->environment = $environment;
$this->matcher = $matcher;
$this->defaultOptions = array_merge( array(
'depth' => null,
'matchingDepth' => null,
'currentAsLink' => true,
'currentClass' => 'current',
'ancestorClass' => 'current_ancestor',
'firstClass' => 'first',
'lastClass' => 'last',
'template' => $template,
'compressed' => false,
'allow_safe_labels' => false,
'clear_matcher' => true,
'leaf_class' => null,
'branch_class' => null
), $defaultOptions );
}
public function render( ItemInterface $item, array $options = array() )
{
$options = array_merge( $this->defaultOptions, $options );
if( empty( $options['depth'] ) )
{
$options['depth'] = PHP_INT_MAX;
}
$this->translator = $options['translator'];
$itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator( $item );
$iterator = new \RecursiveIteratorIterator( $itemIterator, \RecursiveIteratorIterator::SELF_FIRST );
$tree = [];
$parent = null;
$levelParent[0] = null;
$lastLevel = null;
foreach( $iterator as $item )
{
$translatedLabel = $this->translator->trans( $item->getLabel() );
$id = strtolower( $item->getName() );
$level = $item->getLevel();
if( $level <= $options['depth'] )
{
$node = [];
$node['id'] = $id;
$node['name'] = $translatedLabel;
$node['uri'] = $item->getUri();
$node['has_children'] = $item->hasChildren();
$node['level'] = $level;
if( $lastLevel !== null )
{
if( $level > $lastLevel )
{
$parent = $levelParent[$level] = $lastNode['id'];
}
else
{
if( $level < $lastLevel )
{
$parent = $levelParent[$level];
}
}
$lastParent = $parent;
}
$node['parent'] = $parent;
$tree[$id] = $node;
$lastLevel = $level;
$lastNode = $node;
}
}
return $tree;
}
}
menu.js
menu.js requests the menu data and renders it on the page.
define([
"dojo/dom",
"dojo/request/xhr",
"dojo/store/Memory",
"dijit/tree/ObjectStoreModel",
"dijit/Tree",
"dojo/store/JsonRest",
"dijit/form/ComboBox",
"dojo/i18n!app/nls/core",
"dojo/domReady!"
], function (dom,
xhr, Memory, ObjectStoreModel, Tree, JsonRest, ComboBox, core) {
//"use strict";
function run() {
var searchInput, searchStore;
xhr.get("/api/menustore/adminmenus/", {
handleAs: "json"
}).then(function (res) {
var i, l, store = [], memory, model;
l = res.length;
for( i = 0; i < l; i++ ) {
store.push(res[i]);
}
memory = new Memory({
data: store,
getChildren: function (object) {
return this.query({parent: object.id});
}
});
// Create the model
var model = new ObjectStoreModel({
store: memory,
query: {id: 'admin'},
mayHaveChildren: function(object) {
return typeof object.has_children !== "undefined" && object.has_children;
}
});
// Create the Tree.
var tree = new Tree({
id: "admin-menu",
model: model,
persist: true,
showRoot: false,
onClick: function (item) {
if( typeof item.uri !== "undefined" && item.uri !== null ) {
location.href = item.uri;
}
},
getIconClass: function (item, opened) {
return (item && item.has_children) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
}
}, "admin-left-menu");
tree.startup();
});
searchStore = new JsonRest({
target: '/api/store/search',
useRangeHeaders: false,
idProperty: 'id'});
searchInput = new ComboBox({
trim: true,
"class": "search",
store: searchStore,
searchAttr: "name",
placeholder: core.search
}, "search");
searchInput.startup();
searchInput.on("change", function (evt) {
console.log(evt);
console.log(searchInput.store);
});
}
return {
run: run
};
});
//# sourceURL=menu.js

Symfony doctrine:query:dql
Jan 28th
When developing application that use databases, it is often helpful to run SQL standalone to get the information you need, then integrate it into your code.
With Symfony and Doctrine, it is nice to use DQL because it is a nice bridge between the database and the entities.
You can run the commands, using DQL on the command line with the doctrine:query:dql command, like so:
php bin/console doctrine:query:dql "SELECT p.firstname,p.lastname FROM AppBundle\Entity\Common\Person p WHERE p.id=125"

Bingo Game Maker
Nov 22nd
This is a very simple way to create bingo boards with random layouts.
Granted you need PHP and Linux, but if you have them, and you want to create bingo boards, you're all set.
The way it works is you create a text file with one square per line. You need at least 25 lines to fill the board because I didn't feel like making any FREE squares.
Run it through bingo.php on the command line like so:
php bingo.php
Once you're happy with it, you can run it a bunch of times using:
source bingo.sh
bingo.php
<?php
$header = <<< HEADER
Bingo
Thanksgiving Bingo
Mark each square as the event occurs.
First person to get all four corners, a complete row across, or a diagonal row gets a prize.
HEADER;
$footer = <<< FOOTER
FOOTER;
$squares = explode(PHP_EOL,trim(file_get_contents('bingowords.txt')));
shuffle($squares);
echo $header.PHP_EOL;
echo ''.PHP_EOL;
for ($j = 0; $j < 25; $j++) {
if (($j % 5) === 0) {
echo ''.PHP_EOL;
}
echo ''.htmlentities($squares[$j]).' '.PHP_EOL;
if (($j % 5) === 4) {
echo ' '.PHP_EOL;
}
}
echo '
'.PHP_EOL;
echo $footer.PHP_EOL;
bingo.sh
#!/bin/bash
rm out.html
for i in `seq 1 25`;
do
php bingo.php >> out.html
done

DataTables - Passing Data to the Server in Client-Side processing mode
Oct 26th
DataTables is AWESOME. I use it for list-based selection, table-based editing, data display and filtering and anything else I can think of because it is so robust that virtually anything is possible. Really.
If the dataset being used is fairly small (you may interpret small any way you like), DataTables client-side processing is amazing. It sorts and filters, updates all the navigation, lets you adjust the number of records displayed - as well as many more optional features - with very little effort.
Client-side processing - where filtering, paging and sorting calculations are all performed in the web-browser.
Ref: https://datatables.net/manual/data/#Client-side-processing
but
For client-side processing no additional data is submitted to the server
Ref: https://datatables.net/reference/option/ajax.data
The goal was to have a single client-side datatable which would display different content based on the user's actions. For example, 'list all the items with a status of "new" and a (time) segment of 1'.
HTML for the DataTable
<table id="datatable" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Id</th>
<th>Vendor</th>
<th>Item</th>
<th>Date</th>
</tr>
</thead>
</table>
JavaScript
var datatable;
$(function($){
datatable = $('#datatable').DataTable(
{
"ajax": {
"url": "data-10-1.php"
},
"columns":
[
{"data": "link"},
{"data": "vendor"},
{"data": "item"},
{"data": "date"}
]
});
// Handle the click events
$(".summary").on("click", function(evt){
var target = evt.target, status, segment;
if (target.hasAttribute("data-status")) {
status=$(target).attr("data-status");
segment=$(target).attr("data-segment");
datatable.ajax.url("data-"+status+"-"+segment+".php");
datatable.ajax.reload();
datatable.draw('full-reset');
}
});
});
Notice that the URLs are variable. The status and segment values are passed in the URL itself, there is no POST or GET data.
Sample HTML which invokes the event handler. This HTML is inside the div with class="summary".
<span class="details" data-status="10" data-segment="4" data-status-text="new">84</span>
In order to extract the status and segment out of the URL, you can use Apache RewriteRules, like so:
<directory /var/www/html>
RewriteEngine On
RewriteRule ^(data)-(\d+)-(\d+)(\.php)$ $1$4?status=$2&segment=$3 [L]
</directory>
status and segment can then be accessed as $_GET variables. Be sure to validate them.
How does it work? As the user clicks on the spans, the event handler reads the data attributes and constructs a URL that is sent to the server. When Apache receives the request, it rewrites it to deliver the data embedded in the URL as GET parameters.
This post courtesy of Game Creek Video