KB : petite appli JS sans BDD

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

Téléchargement

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
$kbxml = "kb.xml";
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);
}

?>

Commentaires

Pas de commentaires

Ajouter un commentaire

Envoyer

Cet article a été vu 362 fois