Histoire de satisfaire ma curiosité je me suis mis en tête de faire une petite appli Ext JS pour noter quelques bouts d'infos ;)
Je suis parti de rien, juste de google, Ext JS, de leur doc et leur forum. En quelques heures a pris forme la petite appli que vous pouvez télécharger ici.
L'objectif est simple : vous fournir une appli simple, pas trop complexe, utilisant les contrôles de base d'Ext JS afin de vous aider à découvrir ce framework particulièrement puissant !
L'appli s'appelle KB comme Knowledge Base : il s'agit juste d'un petit stockage XML d'infos basiques... snippets de code, urls intéressantes, détails techniques que vous avez mis des heures à trouver pour un usage unique mais qui pourraient servir à nouveau dès vous l'aurez oublié :D etc... ou tout autre usage que vous y trouverez ;)
Aperçu de l'appli
Code JS
Ext.data.Types.Tags = {
convert: function(v, data) {
return data.join(', ');
},
sortType: function(v) {
return v;
},
type: 'Tags'
};
Ext.apply(Ext.util.Format, {
kbtext: function(value) {
value = value.replace(/&/g, '&');
value = value.replace(/</g, '<');
value = value.replace(/>/g, '>');
//value = value.replace(/ /g, ' ');
value = value.replace(/\t/g, ' ');
value = value.replace(/((https?|ftp):\/\/[^ \n'"]+)/gi, '<a href="$1" target="_blank">$1</a>');
value = value.replace(/\[\[\[\s*((.|\r|\n|\t)*?)\s*\]\]\]\n?/g, '<pre class="kb">$1</pre>');
value = value.replace(/\[p\[\s*((.|\r|\n|\t)*?)\s*\]p\]/gi, '<p class="kb">$1</p>');
value = value.replace(/\[\[\s*((.|\r|\n|\t)*?)\s*\]\]/g, '<code class="kb">$1</code>');
value = value.replace(/''([^']+)''/g, '<b>$1</b>');
value = value.replace(/\n/g, '<br />');
return value;
},
kbtitle: function(value) {
value = '<b>'+value+'</b>';
return value;
}
});
Ext.onReady(function() {
/*
var editor = new Ext.ux.grid.RowEditor({
saveText: 'Update'
});
*/
var proxy = new Ext.data.HttpProxy({
api: {
read : '?action=getitems',
create : '?action=additem',
update: '?action=moditem',
destroy: '?action=delitem'
}
});
var Item = Ext.data.Record.create([
{name: 'id', type : 'int', readOnly: true},
{name: 'created', type : 'date', readOnly: true},
{name: 'modified', type : 'date', readOnly: true},
'title',
'text',
{name: 'tags', type : 'Tags'},
]);
// TAGS
var tags = new Ext.data.JsonStore({
url: '?action=gettags',
storeId: 'tags',
fields: ['tag']
});
var writer = new Ext.data.JsonWriter({
encode: true,
writeAllFields: true
});
// ITEMS
var kbitems = new Ext.data.JsonStore({
proxy: proxy,
storeId: 'kbitems',
idProperty: 'id',
root: 'items',
writer: writer,
fields: Item,
sortInfo: {field:'title', direction:'ASC'},
listeners: {
beforeload: function() {
kbitems.setBaseParam('q', Ext.getCmp('q').getValue());
var selected = Ext.getCmp('liste_tags').getSelectedRecords();
var t = [];
for(i=0; i<selected.length; i++) {
t.push(selected[i].get('tag'));
}
kbitems.setBaseParam('tags', t.join("\t"));
}
}
});
// VIEWPORT
new Ext.Viewport({
layout: 'border',
items: [{
region: 'west',
collapsible: false,
title: 'Knowledge Base',
width: 250,
items: [{
xtype: 'panel',
border: false,
header: false,
layout: 'column',
items: [{
columnWidth: 1,
xtype: 'textfield',
fieldLabel: '',
id: 'q',
name: 'q'
}, {
xtype: 'button',
text: 'Clear',
listeners: {
click: function() {
Ext.getCmp('q').setValue('');
kbitems.reload();
}
}
}, {
xtype: 'button',
text: 'Search',
listeners: {
click: function() {
kbitems.reload();
}
}
}]
},{
xtype: 'panel',
border: false,
header: false,
layout: 'column',
items: [{
columnWidth: 0.34,
xtype: 'button',
text: 'Refresh',
listeners: {
click: function(){
tags.reload();
}
}
}, {
columnWidth: 0.33,
xtype: 'button',
text: 'All tags',
listeners: {
click: function(){
Ext.getCmp('liste_tags').selectRange(0, tags.getCount()-1);
kbitems.reload();
}
}
}, {
columnWidth: 0.33,
xtype: 'button',
text: 'No tag',
listeners: {
click: function(){
Ext.getCmp('liste_tags').clearSelections();
kbitems.reload();
}
}
}]
},{
xtype: 'listview',
store: tags,
id: 'liste_tags',
simpleSelect: false,
multiSelect: true,
columns: [{
header: 'Tags',
dataIndex: 'tag'
}],
listeners: {
selectionchange: function(){
kbitems.reload();
}
}
}]
}, {
region: 'center',
title: 'Datas',
layout: 'fit',
items: {
xtype: 'editorgrid',
border: false,
store: kbitems,
id: 'liste_items',
loadingText: 'Loading ...',
//plugins: [editor],
clicksToEdit: 2,
disableSelection: false,
sm: new Ext.grid.RowSelectionModel({
multipleSelect: true
}),
tbar: [{
iconCls: 'icon-item-add',
text: 'Add',
handler: function(){
var e = new Item({
title: '',
text: '',
tags: ''
});
//editor.stopEditing();
Ext.getCmp('liste_items').stopEditing();
kbitems.insert(0, e);
Ext.getCmp('liste_items').getView().refresh();
Ext.getCmp('liste_items').getSelectionModel().selectRow(0);
//editor.startEditing(0);
Ext.getCmp('liste_items').startEditing(0);
}
},{
//ref: '../removeBtn',
iconCls: 'icon-item-delete',
text: 'Remove',
//disabled: true,
handler: function(){
//editor.stopEditing();
Ext.getCmp('liste_items').stopEditing();
var s = Ext.getCmp('liste_items').getSelectionModel().getSelections();
for(var i = 0, r; r = s[i]; i++){
kbitems.remove(r);
}
}
}],
columns: [
new Ext.grid.RowNumberer()
,{
header: 'Created',
dataIndex: 'created',
width: 70,
sortable: true,
xtype: 'datecolumn',
format: 'd/m/Y',
hidden: true,
isCellEditable: false,
editor: {
xtype: 'datefield',
readOnly: true
}
//tpl: '{modified:date("d/m/Y")}'
},{
header: 'Modified',
dataIndex: 'modified',
width: 70,
sortable: true,
xtype: 'datecolumn',
format: 'd/m/Y',
hidden: true,
isCellEditable: false,
editor: {
xtype: 'datefield',
readOnly: true
}
//tpl: '{modified:date("d/m/Y")}'
},{
header: 'Title',
dataIndex: 'title',
width: 140,
sortable: true,
renderer: Ext.util.Format.kbtitle,
editor: {
xtype: 'textfield',
allowBlank: false
}
//tpl: '<b>{title}</b>'
},{
header: 'Text',
dataIndex: 'text',
width: 600,
renderer: Ext.util.Format.kbtext,
editor: {
xtype: 'textarea',
height: 200
}
//tpl: '{text:kbtext}'
},{
header: 'Tags',
dataIndex: 'tags',
sortable: true,
width: 100,
editor: {
xtype: 'textfield'
}
}]
}
}]
});
tags.load({
/*
callback: function(){
Ext.getCmp('liste_tags').selectRange(0, tags.getCount()-1);
}
*/
});
});
Code PHP
<?php
error_reporting(0);
$kbxml = "kb.xml";
if ( get_magic_quotes_gpc() != 0 ) {
if ( !empty($_GET) ) {
recstripslashes($_GET);
}
if ( !empty($_POST) ) {
recstripslashes($_POST);
}
if ( !empty($_REQUEST) ) {
recstripslashes($_REQUEST);
}
}
if ( isset($_REQUEST['action']) ) {
$kb = new kb($kbxml);
$kb->action($_REQUEST['action']);
echo '{failure:true}';
exit;
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" />
<!--link rel="stylesheet" type="text/css" href="extjs/examples/ux/css/RowEditor.css" /-->
<title>Knowledge Base</title>
<style type="text/css">
.icon-item-add {
background-image: url(add.png) !important;
}
.icon-item-delete {
background-image: url(del.png) !important;
}
pre.kb, code.kb {
color: #338;
background: #f8f8ff;
max-height: 200px;
overflow:hidden;
overflow-x: hidden;
overflow-y: auto;
}
p.kb {
background: #fafafa;
max-height: 200px;
overflow:hidden;
overflow-x: hidden;
overflow-y: auto;
}
</style>
</head>
<body>
<script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="extjs/ext-all.js"></script>
<!--script type="text/javascript" src="extjs/examples/ux/RowEditor.js"></script-->
<script type="text/javascript" src="kb.js"></script>
</body>
</html>
<?php
/**
*
*/
class kb {
/**
*
* @var int
*/
public $versions_saved = 4;
/**
*
* @var string
*/
protected $filename = "";
/**
*
* @var array
*/
protected $items = array();
/**
*
* @var array
*/
protected $tags = array();
/**
*
* @var string
*/
protected $parsedata = "";
/**
*
* @var int
*/
protected $maxint = 0;
/**
*
* @var string
*/
protected $parseitem = array();
/**
*
* @param string $filename
*/
public function kb($filename) {
$this->filename = $filename;
if ( !file_exists($filename) ) {
$this->save();
}
$this->load();
}
/**
*
*/
public function load() {
$this->items = array();
$parser = xml_parser_create('UTF-8');
xml_set_object($parser, $this);
xml_set_element_handler($parser, 'startXML', 'endXML');
xml_set_character_data_handler($parser, 'charXML');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
xml_parse($parser, file_get_contents($this->filename));
$this->tags = array_keys($this->tags);
sort($this->tags);
}
/**
*
* @param <type> $parser
* @param <type> $name
* @param <type> $attr
*/
protected function startXML($parser, $name, $attr) {
if ( $name == 'item' ) {
$this->parseitem = array(
'created' => '0000-00-00',
'modified' => '0000-00-00',
'title' => '',
'text' => '',
'id' => '',
'tags' => array(),
);
}
$this->parsedata = '';
}
/**
*
* @param <type> $parser
* @param <type> $name
*/
protected function endXML($parser, $name) {
if ( $name == 'tag' ) {
$this->parseitem['tags'][] = $this->parsedata;
$this->tags[$this->parsedata] = 1;
}
elseif ( $name == 'item' ) {
$this->items[$this->parseitem['id']] = $this->parseitem;
if ( $this->maxint < $this->parseitem['id'] ) {
$this->maxint = $this->parseitem['id'];
}
}
elseif ( in_array($name, array('modified', 'created', 'title', 'text', 'id')) ) {
$this->parseitem[$name] = $this->parsedata;
}
}
/**
*
* @param <type> $parser
* @param <type> $data
*/
protected function charXML($parser, $data) {
if ( trim($data) != '' ) {
$this->parsedata .= $data;
}
}
/**
*
*/
public function save() {
$xml = '<?xml version="1.0" encoding="UTF-8"?><kb>';
$xml .= "<items>\n";
usort($this->items, "sortitem");
foreach ( $this->items as $item ) {
$xml .= "<item>\n";
$xml .= ' <id>' . $item['id'] . "</id>\n";
$xml .= ' <created>' . $item['created'] . "</created>\n";
$xml .= ' <modified>' . $item['modified'] . "</modified>\n";
$xml .= ' <title><![CDATA[' . $item['title'] . "]]></title>\n";
$xml .= ' <text><![CDATA[' . $item['text'] . "]]></text>\n";
$xml .= " <tags>\n";
sort($item['tags']);
foreach ( $item['tags'] as $tag ) {
$xml .= ' <tag><![CDATA[' . $tag . "]]></tag>\n";
}
$xml .= " </tags>\n";
$xml .= "</item>\n";
}
$xml .= '</items>';
$xml .= '</kb>';
if ( file_exists($this->filename) ) {
copy($this->filename, $this->filename . '.' . date('Ymd.His') . '.bak');
}
$saved = glob($this->filename . '*');
if ( is_array($saved) ) {
if ( count($saved) > $this->versions_saved ) {
for ( $i = 0; $i < count($saved) - $this->versions_saved; $i++ ) {
@unlink($saved[$i]);
}
}
}
file_put_contents($this->filename, $xml);
}
/**
*
* @param string $action
*/
public function action($action) {
switch ( $action ) {
case 'gettags' : $this->_gettags();
break;
case 'getitems' : $this->_getitems();
break;
case 'delitem' : $this->_delitem();
break;
case 'additem' : $this->_additem();
break;
case 'moditem' : $this->_moditem();
break;
default: exit;
}
}
/**
*
*/
protected function _delitem() {
if ( isset($_REQUEST['items']) ) {
$id = trim(trim($_REQUEST['items']), '"');
if ( is_numeric($id) && isset($this->items[$id]) ) {
unset($this->items[$id]);
$this->save();
echo '{success:true}';
exit;
}
}
}
/**
*
*/
protected function _moditem() {
if ( isset($_REQUEST['items']) ) {
$json = json_decode($_REQUEST['items'], true);
if ( is_array($json) && isset($json['id']) && isset($this->items[$json['id']]) ) {
$item = $this->items[$json['id']];
$item['modified'] = date('Y-m-d');
$item['tags'] = array();
if ( isset($json['title']) ) {
$item['title'] = $json['title'];
}
if ( isset($json['text']) ) {
$item['text'] = $json['text'];
}
if ( isset($json['tags']) && !empty($json['tags']) ) {
if ( is_array($json['tags']) ) {
$json['tags'] = implode(',', $json['tags']);
}
$item['tags'] = array_map('trim', explode(',', $json['tags']));
}
if ( !empty($item['title']) ) {
$this->items[$item['id']] = $item;
$this->save();
echo json_encode(array('success' => true, 'items' => $item));
exit;
}
}
}
}
/**
*
*/
protected function _additem() {
$item = array(
'created' => date('Y-m-d'),
'modified' => date('Y-m-d'),
'title' => '',
'text' => '',
'id' => $this->maxint + 1,
'tags' => array(),
);
if ( isset($_REQUEST['items']) ) {
$json = json_decode($_REQUEST['items'], true);
if ( is_array($json) ) {
if ( isset($json['title']) ) {
$item['title'] = $json['title'];
}
if ( isset($json['text']) ) {
$item['text'] = $json['text'];
}
if ( isset($json['tags']) && !empty($json['tags']) ) {
if ( is_array($json['tags']) ) {
$json['tags'] = implode(',', $json['tags']);
}
$item['tags'] = array_map('trim', explode(',', $json['tags']));
}
}
if ( !empty($item['title']) ) {
$this->items[$item['id']] = $item;
$this->save();
echo json_encode(array('success' => true, 'items' => $item));
exit;
}
}
}
/**
*
*/
protected function _gettags() {
$a = array();
foreach ( $this->tags as $tag ) {
$a[] = array('tag' => $tag);
}
echo json_encode($a);
exit;
}
/**
*
*/
protected function _getitems() {
$q = strtolower(isset($_REQUEST['q']) ? $_REQUEST['q'] : '');
$tags = strtolower(isset($_REQUEST['tags']) ? "\t" . $_REQUEST['tags'] . "\t" : '');
$result = array();
if ( !empty($q) || !empty($tags) ) {
foreach ( $this->items as $item ) {
$c1 = strtolower($item['title']);
$c2 = strtolower($item['text']);
$ok = false;
if ( empty($item['tags']) ) {
$ok = true;
}
else {
foreach ( $item['tags'] as $tag ) {
if ( strpos($tags, "\t" . strtolower($tag) . "\t") !== false ) {
if ( empty($q) ) {
$ok = true;
}
elseif ( strpos($c1, $q) !== false ) {
$ok = true;
}
elseif ( strpos($c2, $q) !== false ) {
$ok = true;
}
elseif ( strpos($tag, $q) !== false ) {
$ok = true;
}
}
}
}
if ( $ok ) {
$result[] = $item;
}
}
}
echo json_encode(array('items' => $result));
exit;
}
}
function sortitem($a, $b) {
$a = strtolower($a['title']);
$b = strtolower($b['title']);
return strcmp($a, $b);
}
function recstripslashes(& $array) {
if ( is_array($array) ) {
foreach ( $array as $key => $value ) {
if ( is_array($value) ) {
recstripslashes($array[$key]);
}
else {
$array[$key] = stripslashes($value);
}
}
}
else {
$array = stripslashes($array);
}
}
?>