<?php
require_once '../lib/DataSourceResult.php';
require_once '../lib/Kendo/Autoload.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
header('Content-Type: application/json');
$request = json_decode(file_get_contents('php://input'));
$result = new DataSourceResult('sqlite:..//sample.db');
$type = $_GET['type'];
$fields = array('Id', 'JobTitle', 'Color');
$connectionFields = array('Id', 'FromShapeId', 'ToShapeId', 'Text', 'FromPointX', 'FromPointY', 'ToPointX', 'ToPointY');
switch($type) {
case 'create':
$result = $result->create('OrgChartShapes', $fields, $request, 'Id');
break;
case 'read':
$result = $result->read('OrgChartShapes', $fields, $request);
break;
case 'update':
$result = $result->update('OrgChartShapes', $fields, $request, 'Id');
break;
case 'destroy':
$result = $result->destroy('OrgChartShapes', $request, 'Id');
break;
case 'createConnection':
$result = $result->create('OrgChartConnections', $connectionFields, $request, 'Id');
break;
case 'readConnections':
$result = $result->read('OrgChartConnections', $connectionFields, $request);
break;
case 'updateConnection':
$result = $result->update('OrgChartConnections', $connectionFields, $request, 'Id');
break;
case 'destroyConnection':
$result = $result->destroy('OrgChartConnections', $request, 'Id');
break;
}
echo json_encode($result, JSON_NUMERIC_CHECK);
exit;
}
?>
<?php
$transport = new \Kendo\Data\DataSourceTransport();
$create = new \Kendo\Data\DataSourceTransportCreate();
$create->url('events.php?type=create')
->contentType('application/json')
->type('POST');
$read = new \Kendo\Data\DataSourceTransportRead();
$read->url('events.php?type=read')
->contentType('application/json')
->type('POST');
$update = new \Kendo\Data\DataSourceTransportUpdate();
$update->url('events.php?type=update')
->contentType('application/json')
->type('POST');
$destroy = new \Kendo\Data\DataSourceTransportDestroy();
$destroy->url('events.php?type=destroy')
->contentType('application/json')
->type('POST');
$transport->create($create)
->read($read)
->update($update)
->destroy($destroy)
->parameterMap('function(data) {
return kendo.stringify(data);
}');
$model = new \Kendo\Data\DataSourceSchemaModel();
$shapeIDField = new \Kendo\Data\DataSourceSchemaModelField('Id');
$shapeIDField->type('number')
->editable(false);
$jobTitleField = new \Kendo\Data\DataSourceSchemaModelField('JobTitle');
$jobTitleField->type('string');
$colorField = new \Kendo\Data\DataSourceSchemaModelField('Color');
$colorField->type('string');
$model->id('Id')
->addField($shapeIDField)
->addField($jobTitleField)
->addField($colorField);
$schema = new \Kendo\Data\DataSourceSchema();
$schema->model($model)
->data('data')
->total('total');
$dataSource = new \Kendo\Data\DataSource();
$dataSource->transport($transport)
->schema($schema);
$connectionsTransport = new \Kendo\Data\DataSourceTransport();
$connectionsCreate = new \Kendo\Data\DataSourceTransportCreate();
$connectionsCreate->url('events.php?type=createConnection')
->contentType('application/json')
->type('POST');
$connectionsRead = new \Kendo\Data\DataSourceTransportRead();
$connectionsRead->url('events.php?type=readConnections')
->contentType('application/json')
->type('POST');
$connectionsUpdate = new \Kendo\Data\DataSourceTransportUpdate();
$connectionsUpdate->url('events.php?type=updateConnection')
->contentType('application/json')
->type('POST');
$connectionsDestroy = new \Kendo\Data\DataSourceTransportDestroy();
$connectionsDestroy->url('events.php?type=destroyConnection')
->contentType('application/json')
->type('POST');
$connectionsTransport->create($connectionsCreate)
->read($connectionsRead)
->update($connectionsUpdate)
->destroy($connectionsDestroy)
->parameterMap('function(data) {
return kendo.stringify(data);
}');
$connectionsModel = new \Kendo\Data\DataSourceSchemaModel();
$connectionIDField = new \Kendo\Data\DataSourceSchemaModelField('Id');
$connectionIDField->type('number')
->editable(false);
$textField = new \Kendo\Data\DataSourceSchemaModelField('Text');
$textField->type('string');
$fromField = new \Kendo\Data\DataSourceSchemaModelField('from');
$fromField->type('number')
->from('FromShapeId');
$toField = new \Kendo\Data\DataSourceSchemaModelField('to');
$toField->type('number')
->from('ToShapeId');
$fromXField = new \Kendo\Data\DataSourceSchemaModelField('fromX');
$fromXField->type('number')
->from('FromPointX');
$fromYField = new \Kendo\Data\DataSourceSchemaModelField('fromY');
$fromYField->type('number')
->from('FromPointY');
$toXField = new \Kendo\Data\DataSourceSchemaModelField('toX');
$toXField->type('number')
->from('ToPointX');
$toYField = new \Kendo\Data\DataSourceSchemaModelField('toY');
$toYField->type('number')
->from('ToPointY');
$connectionsModel->id('Id')
->addField($connectionIDField)
->addField($textField)
->addField($fromField)
->addField($toField)
->addField($fromXField)
->addField($fromYField)
->addField($toXField)
->addField($toYField);
$connectionsSchema = new \Kendo\Data\DataSourceSchema();
$connectionsSchema->model($connectionsModel)
->data('data')
->total('total');
$connectionsDataSource = new \Kendo\Data\DataSource();
$connectionsDataSource->transport($connectionsTransport)
->schema($connectionsSchema);
$layout = new \Kendo\Dataviz\UI\DiagramLayout();
$layout->type('tree')
->subtype('tipover')
->underneathHorizontalOffset(140);
$shapeContent = new \Kendo\Dataviz\UI\DiagramShapeDefaultsContent();
$shapeContent->template('#:dataItem.JobTitle#')
->fontSize(17);
$shape_defaults = new \Kendo\Dataviz\UI\DiagramShapeDefaults();
$shape_defaults->visual(new \Kendo\JavaScriptFunction('visualTemplate'))
->content($shapeContent);
$stroke = new \Kendo\Dataviz\UI\DiagramConnectionDefaultsStroke();
$stroke->color('#586477')
->width(2);
$connection_defaults = new \Kendo\Dataviz\UI\DiagramConnectionDefaults();
$connection_defaults->stroke($stroke);
$diagram = new \Kendo\Dataviz\UI\Diagram('diagram');
$diagram->dataSource($dataSource)
->connectionsDataSource($connectionsDataSource)
->layout($layout)
->dataBound('onDataBound')
->shapeDefaults($shape_defaults)
->connectionDefaults($connection_defaults)
->dataBound('onDataBound')
->edit('onEdit')
->addEvent('onAdd')
->remove('onRemove')
->cancel('onCancel')
->itemRotate('onItemRotate')
->pan('onPan')
->select('onSelect')
->zoomStart('onZoomStart')
->zoomEnd('onZoomEnd')
->click('onClick')
->mouseEnter('onMouseEnter')
->mouseLeave('onMouseLeave')
->dragStart('onDragStart')
->drag('onDrag')
->dragEnd('onDragEnd');
echo $diagram->render();
?>
<div class="box wide">
<h4>Console log</h4>
<div class="console"></div>
</div>
<script>
function onDataBound(e) {
kendoConsole.log("Diagram data bound");
}
function onEdit(e) {
kendoConsole.log("Diagram edit");
}
function onAdd(e) {
kendoConsole.log("Diagram add");
}
function onRemove(e) {
kendoConsole.log("Diagram remove");
}
function onCancel(e) {
kendoConsole.log("Diagram cancel");
}
function onItemRotate(e) {
var rotation = e.item.rotate();
kendoConsole.log("Rotate - angle: " + rotation.angle + " center: " + rotation.x + "," + rotation.y);
}
function onPan(e) {
kendoConsole.log("Pan: " + e.pan.toString());
}
function onSelect(e) {
var action;
var items;
if (e.selected.length) {
action = "Selected";
items = e.selected;
} else if (e.deselected.length) {
action = "Deselected";
items = e.deselected;
}
kendoConsole.log(action + ": " + items.length);
}
function onZoomStart(e) {
kendoConsole.log("Zoom start: " + e.zoom);
}
function onZoomEnd(e) {
kendoConsole.log("Zoom end: " + e.zoom);
}
function onClick(e) {
kendoConsole.log("Click: " + elementText(e.item));
}
function onMouseEnter(e) {
kendoConsole.log("Mouse enter: " + elementText(e.item));
}
function onMouseLeave(e) {
kendoConsole.log("Mouse leave: " + elementText(e.item));
}
function onDragStart(e) {
kendoConsole.log("Drag start " + draggedElementsTexts(e));
}
function onDrag(e) {
kendoConsole.log("Drag " + draggedElementsTexts(e));
}
function onDragEnd(e) {
kendoConsole.log("Drag end " + draggedElementsTexts(e));
}
var diagram = kendo.dataviz.diagram;
var Shape = diagram.Shape;
var Connection = diagram.Connection;
var Point = diagram.Point;
function elementText(element) {
var text;
if (element instanceof Shape) {
text = element.dataItem.JobTitle;
} else if (element instanceof Point) {
text = "(" + element.x + "," + element.y + ")";
} else if (element instanceof Connection) {
var source = element.source();
var target = element.target();
var sourceElement = source.shape || source;
var targetElement = target.shape || target;
text = elementText(sourceElement) + " - " + elementText(targetElement);
}
return text;
}
function draggedElementsTexts(e) {
var text;
var elements;
if (e.shapes.length) {
text = "shapes: ";
elements = e.shapes;
} else {
text = "connections: ";
elements = e.connections;
}
text += $.map(elements, function (element) {
return elementText(element);
}).join(",");
return text;
}
function visualTemplate(options) {
var dataviz = kendo.dataviz;
var g = new dataviz.diagram.Group();
var dataItem = options.dataItem;
if (dataItem.JobTitle === "President") {
g.append(new dataviz.diagram.Circle({
radius: 60,
stroke: {
width: 2,
color: dataItem.Color || "#586477"
},
fill: "#e8eff7"
}));
} else {
g.append(new dataviz.diagram.Rectangle({
width: 240,
height: 67,
stroke: {
width: 0
},
fill: "#e8eff7"
}));
g.append(new dataviz.diagram.Rectangle({
width: 8,
height: 67,
fill: dataItem.Color,
stroke: {
width: 0
}
}));
}
return g;
}
</script>
<?php
class DataSourceResult {
protected $db;
private $stringOperators = array(
'eq' => 'LIKE',
'neq' => 'NOT LIKE',
'doesnotcontain' => 'NOT LIKE',
'contains' => 'LIKE',
'startswith' => 'LIKE',
'endswith' => 'LIKE',
'isnull' => 'IS',
'isnotnull' => 'IS NOT',
'isempty' => '==',
'isnotempty' => '!='
);
private $operators = array(
'eq' => '=',
'gt' => '>',
'gte' => '>=',
'lt' => '<',
'lte' => '<=',
'neq' => '!=',
'isnull' => 'IS',
'isnotnull' => 'IS NOT'
);
private $aggregateFunctions = array(
'average' => 'AVG',
'min' => 'MIN',
'max' => 'MAX',
'count' => 'COUNT',
'sum' => 'SUM'
);
function __construct($dsn, $username=null, $password=null, $driver_options=null) {
$this->db = new PDO($dsn, $username, $password, $driver_options);
}
private function total($tableName, $properties, $request) {
if (isset($request->filter)) {
$where = $this->filter($properties, $request->filter);
$statement = $this->db->prepare("SELECT COUNT(*) FROM $tableName $where");
$this->bindFilterValues($statement, $request->filter);
} else {
$statement = $this->db->prepare("SELECT COUNT(*) FROM $tableName");
}
$statement->execute();
$total = $statement->fetch(PDO::FETCH_NUM);
return (int)($total[0]);
}
private function page() {
return ' LIMIT :take OFFSET :skip';
}
private function group($data, $groups, $table, $request, $propertyNames) {
if (count($data) > 0) {
return $this->groupBy($data, $groups, $table, $request, $propertyNames);
}
return array();
}
private function mergeSortDescriptors($request) {
$sort = isset($request->sort) && count($request->sort) ? $request->sort : array();
$groups = isset($request->group) && count($request->group) ? $request->group : array();
return array_merge($sort, $groups);
}
private function groupBy($data, $groups, $table, $request, $propertyNames) {
if (count($groups) > 0) {
$field = $groups[0]->field;
$count = count($data);
$result = array();
$value = $data[0][$field];
$aggregates = isset($groups[0]->aggregates) ? $groups[0]->aggregates : array();
$hasSubgroups = count($groups) > 1;
$groupItem = $this->createGroup($field, $value, $hasSubgroups, $aggregates, $table, $request, $propertyNames);
for ($index = 0; $index < $count; $index++) {
$item = $data[$index];
if ($item[$field] != $value) {
if (count($groups) > 1) {
$groupItem["items"] = $this->groupBy($groupItem["items"], array_slice($groups, 1), $table, $request, $propertyNames);
}
$result[] = $groupItem;
$groupItem = $this->createGroup($field, $data[$index][$field], $hasSubgroups, $aggregates, $table, $request, $propertyNames);
$value = $item[$field];
}
$groupItem["items"][] = $item;
}
if (count($groups) > 1) {
$groupItem["items"] = $this->groupBy($groupItem["items"], array_slice($groups, 1), $table, $request, $propertyNames);
}
$result[] = $groupItem;
return $result;
}
return array();
}
private function addFilterToRequest($field, $value, $request) {
$filter = (object)array(
'logic' => 'and',
'filters' => array(
(object)array(
'field' => $field,
'operator' => 'eq',
'value' => $value
))
);
if (isset($request->filter)) {
$filter->filters[] = $request->filter;
}
return (object) array('filter' => $filter);
}
private function addFieldToProperties($field, $propertyNames) {
if (!in_array($field, $propertyNames)) {
$propertyNames[] = $field;
}
return $propertyNames;
}
private function createGroup($field, $value, $hasSubgroups, $aggregates, $table, $request, $propertyNames) {
if (count($aggregates) > 0) {
$request = $this->addFilterToRequest($field, $value, $request);
$propertyNames = $this->addFieldToProperties($field, $propertyNames);
}
$groupItem = array(
'field' => $field,
'aggregates' => $this->calculateAggregates($table, $aggregates, $request, $propertyNames),
'hasSubgroups' => $hasSubgroups,
'value' => $value,
'items' => array()
);
return $groupItem;
}
private function calculateAggregates($table, $aggregates, $request, $propertyNames) {
$count = count($aggregates);
if (count($aggregates) > 0) {
$functions = array();
for ($index = 0; $index < $count; $index++) {
$aggregate = $aggregates[$index];
$name = $this->aggregateFunctions[$aggregate->aggregate];
$functions[] = $name.'('.$aggregate->field.') as '.$aggregate->field.'_'.$aggregate->aggregate;
}
$sql = sprintf('SELECT %s FROM %s', implode(', ', $functions), $table);
if (isset($request->filter)) {
$sql .= $this->filter($propertyNames, $request->filter);
}
$statement = $this->db->prepare($sql);
if (isset($request->filter)) {
$this->bindFilterValues($statement, $request->filter);
}
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertAggregateResult($result[0]);
}
return (object)array();
}
private function convertAggregateResult($propertyNames) {
$result = array();
foreach($propertyNames as $property => $value) {
$item = array();
$split = explode('_', $property);
$field = $split[0];
$function = $split[1];
if (array_key_exists($field, $result)) {
$result[$field][$function] = $value;
} else {
$result[$field] = array($function => $value);
}
}
return $result;
}
private function sort($propertyNames, $sort) {
$count = count($sort);
$sql = '';
if ($count > 0) {
$sql = ' ORDER BY ';
$order = array();
for ($index = 0; $index < $count; $index ++) {
$field = $sort[$index]->field;
if (in_array($field, $propertyNames)) {
$dir = 'ASC';
if ($sort[$index]->dir == 'desc') {
$dir = 'DESC';
}
$order[] = "$field $dir";
}
}
$sql .= implode(',', $order);
}
return $sql;
}
private function where($properties, $filter, $all) {
if (isset($filter->filters)) {
$logic = ' AND ';
if ($filter->logic == 'or') {
$logic = ' OR ';
}
$filters = $filter->filters;
$where = array();
for ($index = 0; $index < count($filters); $index++) {
$where[] = $this->where($properties, $filters[$index], $all);
}
$where = implode($logic, $where);
return "($where)";
}
$field = $filter->field;
$propertyNames = $this->propertyNames($properties);
if (in_array($field, $propertyNames)) {
$type = "string";
$index = array_search($filter, $all);
$value = ":filter$index";
if (isset($properties[$field])) {
$type = $properties[$field]['type'];
} else if ($this->isDate($filter->value)) {
$type = "date";
} else if (array_key_exists($filter->operator, $this->operators) && !$this->isString($filter->value)) {
$type = "number";
}
if ($type == "date") {
$field = "date($field)";
$value = "date($value)";
}
if ($type == "string") {
$operator = $this->stringOperators[$filter->operator];
} else {
$operator = $this->operators[$filter->operator];
}
if ($operator == "isnull" || $operator == "isnotnull") {
$value = 'NULL';
}
if ($operator == "isempty" || $operator == "isnotempty") {
$value = '';
}
return "$field $operator $value";
}
}
private function flatten(&$all, $filter) {
if (isset($filter->filters)) {
$filters = $filter->filters;
for ($index = 0; $index < count($filters); $index++) {
$this->flatten($all, $filters[$index]);
}
} else {
$all[] = $filter;
}
}
private function filter($properties, $filter) {
$all = array();
$this->flatten($all, $filter);
$where = $this->where($properties, $filter, $all);
return " WHERE $where";
}
private function isDate($value) {
$result = date_parse($value);
return $result["error_count"] < 1 && checkdate($result['month'], $result['day'], $result['year']);
}
private function isString($value) {
return !is_bool($value) && !is_numeric($value) && !$this->isDate($value);
}
protected function propertyNames($properties) {
$names = array();
foreach ($properties as $key => $value) {
if (is_string($value)) {
$names[] = $value;
} else {
$names[] = $key;
}
}
return $names;
}
private function bindFilterValues($statement, $filter) {
$filters = array();
$this->flatten($filters, $filter);
for ($index = 0; $index < count($filters); $index++) {
$value = $filters[$index]->value;
$operator = $filters[$index]->operator;
$date = date_parse($value);
if ($operator == 'contains' || $operator == 'doesnotcontain') {
$value = "%$value%";
} else if ($operator == 'startswith') {
$value = "$value%";
} else if ($operator == 'endswith') {
$value = "%$value";
}
$statement->bindValue(":filter$index", $value);
}
}
public function create($table, $properties, $models, $key) {
$result = array();
$data = array();
$propertyNames = $this->propertyNames($properties);
if (!is_array($models)) {
$models = array($models);
}
$errors = array();
foreach ($models as $model) {
$columns = array();
$values = array();
$input_parameters = array();
foreach ($propertyNames as $property) {
if ($property != $key) {
$columns[] = $property;
$values[] = '?';
$input_parameters[] = $model->$property;
}
}
$columns = implode(', ', $columns);
$values = implode(', ', $values);
$sql = "INSERT INTO $table ($columns) VALUES ($values)";
$statement = $this->db->prepare($sql);
$statement->execute($input_parameters);
$status = $statement->errorInfo();
if ($status[1] > 0) {
$errors[] = $status[2];
} else {
$model->$key = $this->db->lastInsertId();
$data[] = $model;
}
}
if (count($errors) > 0) {
$result['errors'] = $errors;
} else {
$result['data'] = $data;
}
return $result;
}
public function destroy($table, $models, $key) {
$result = array();
if (!is_array($models)) {
$models = array($models);
}
$errors = array();
foreach ($models as $model) {
$sql = "DELETE FROM $table WHERE $key=?";
$statement = $this->db->prepare($sql);
$statement->execute(array($model->$key));
$status = $statement->errorInfo();
if ($status[1] > 0) {
$errors[] = $status[2];
}
}
if (count($errors) > 0) {
$result['errors'] = $errors;
}
return $result;
}
public function update($table, $properties, $models, $key) {
$result = array();
$propertyNames = $this->propertyNames($properties);
if (in_array($key, $propertyNames)) {
if (!is_array($models)) {
$models = array($models);
}
$errors = array();
foreach ($models as $model) {
$set = array();
$input_parameters = array();
foreach ($propertyNames as $property) {
if ($property != $key) {
$set[] = "$property=?";
$input_parameters[] = $model->$property;
}
}
$input_parameters[] = $model->$key;
$set = implode(', ', $set);
$sql = "UPDATE $table SET $set WHERE $key=?";
$statement = $this->db->prepare($sql);
$statement->execute($input_parameters);
$status = $statement->errorInfo();
if ($status[1] > 0) {
$errors[] = $status[2];
}
}
if (count($errors) > 0) {
$result['errors'] = $errors;
}
}
if (count($result) == 0) {
$result = "";
}
return $result;
}
public function read($table, $properties, $request = null) {
$result = array();
$propertyNames = $this->propertyNames($properties);
$result['total'] = $this->total($table, $properties, $request);
$sql = sprintf('SELECT %s FROM %s', implode(', ', $propertyNames), $table);
if (isset($request->filter)) {
$sql .= $this->filter($properties, $request->filter);
}
$sort = $this->mergeSortDescriptors($request);
if (count($sort) > 0) {
$sql .= $this->sort($propertyNames, $sort);
}
if (isset($request->skip) && isset($request->take)) {
$sql .= $this->page();
}
$statement = $this->db->prepare($sql);
if (isset($request->filter)) {
$this->bindFilterValues($statement, $request->filter);
}
if (isset($request->skip) && isset($request->take)) {
$statement->bindValue(':skip', (int)$request->skip, PDO::PARAM_INT);
$statement->bindValue(':take', (int)$request->take, PDO::PARAM_INT);
}
$statement->execute();
$data = $statement->fetchAll(PDO::FETCH_ASSOC);
if (isset($request->group) && count($request->group) > 0) {
$data = $this->group($data, $request->group, $table, $request, $propertyNames);
$result['groups'] = $data;
} else {
$result['data'] = $data;
}
if (isset($request->aggregate)) {
$result["aggregates"] = $this->calculateAggregates($table, $request->aggregate, $request, $propertyNames);
}
return $result;
}
public function readJoin($table, $joinTable, $properties, $key, $column, $request = null) {
$result = $this->read($table, $properties, $request);
for ($index = 0, $count = count($result['data']); $index < $count; $index++) {
$sql = sprintf('SELECT %s FROM %s WHERE %s = %s', $column, $joinTable, $key, $result['data'][$index][$key]);
$statement = $this->db->prepare($sql);
$statement->execute();
$data = $statement->fetchAll(PDO::FETCH_NUM);
$result['data'][$index]['Attendees'] = $data;
}
return $result;
}
}
?>