Mittwoch, 27. September 2017

How to implement FancyTree in oracle apex (Lazy Loading) ?

Fancytree is a JavaScript dynamic tree view plugin for jQuery with support for persistence, keyboard, checkboxes, tables, drag'n'drop, and lazy loading. This plugin provides a really huge of builtin optional plugins like :


  • Inline edit (allow you to change node titles using inline editing).
  • Drag n drop (Drag of tree nodes (inside the same or between different trees) behave like a standard jQuery UI draggable, i.e. allow dropping of nodes on standard droppables. behave like a standard jQuery UI droppable, i.e. allow dropping of standard draggables).
  • Plays nice with FontAwesome.
  • A lot of callbacks to customize almost everything.
  • Multiple themes but customizable if you want.
  • A friendly API that allow you to add nodes dinamically to the selected node.
I would like to show in this tutorial how to implement FancyTree  with Lazy Loading  and Ajax (https://github.com/mar10/fancytree)  in Oracle Apex

1- Include all neccessary files


//cdn.jsdelivr.net/gh/mar10/fancytree@2/dist/jquery.fancytree-all-deps.min.js
//cdnjs.cloudflare.com/ajax/libs/js-cookie/2.0.1/js.cookie.min.js
#WORKSPACE_IMAGES#jquery.fancytree.persist.js
#APP_IMAGES#jquery.fancytree.childcounter.js
#APP_IMAGES#jquery.fancytree.themeroller.js


2- Populating the tree using AJAX and lazy loading nodes


var init_Fancy_tree = function(){

    $("#tree").fancytree({
        extensions: ["childcounter", "persist", "edit", "themeroller", "filter" /*"wide"*/ ],
        quicksearch: true,
        selectMode: 1,
        source: {
            url: apex.server.url({
                p_request: "APPLICATION_PROCESS=GET_NODE_DATAA",
                x01: 0
            }),
            data: {
                rootKey: "root"
            },
            cache: false
        },
        lazyLoad: function(event, data) {
            var node = data.node;
            data.result = {
                url: apex.server.url({
                    p_request: "APPLICATION_PROCESS=GET_NODE_DATAA",
                    x01: node.key
                }),
                data: {
                    mode: "children",
                    parent: node.key
                },
                cache: false
            };
        },
        select: function(event, data) {
            // Display list of selected nodes
            var s = data.tree.getSelectedNodes().join(", ");
            $("#P28_ITEM").val(s);

            console.log(s);
        },
        activate: function(event, data) {
            $("#P28_TITLE").val(data.node.title);
            $("#P28_ID").val(data.node.key);
            console.log(data.node.title);
        },
        persist: {
            expandLazy: true,
            // fireActivate: false,    // false: suppress `activate` event after active node was restored
            // overrideSource: false,  // true: cookie takes precedence over `source` data attributes.
            store: "auto" // 'cookie', 'local': use localStore, 'session': sessionStore
        },
        childcounter: {
            deep: true,
            hideZeros: true,
            hideExpanded: true
        },
        loadChildren: function(event, data) {
            // update node and parent counters after lazy loading
            data.node.updateCounters();
        },
        edit: {
            triggerStart: ["f2", "dblclick", "shift+click", "mac+enter"],
            beforeEdit: function(event, data) {
                // Return false to prevent edit mode
            },
            edit: function(event, data) {
                // Editor was opened (available as data.input)
            },
            beforeClose: function(event, data) {
                // Return false to prevent cancel/save (data.input is available)
                console.log(event.type, event, data);
                if (data.originalEvent.type === "mousedown") {
                    // We could prevent the mouse click from generating a blur event
                    // (which would then again close the editor) and return `false` to keep
                    // the editor open:
                    //                  data.originalEvent.preventDefault();
                    //                  return false;
                    // Or go on with closing the editor, but discard any changes:
                    //                  data.save = false;
                }
            },
            save: function(event, data) {
                // Save data.input.val() or return false to keep editor open
                console.log("save...", this, data);
                // Simulate to start a slow ajax request...
                setTimeout(function() {
                    $(data.node.span).removeClass("pending");
                    // Let's pretend the server returned a slightly modified
                    // title:
                    data.node.setTitle(data.node.title + "!");
                }, 2000);
                // We return true, so ext-edit will set the current user input
                // as title
                return true;
            },
            close: function(event, data) {
                // Editor was removed
                if (data.save) {
                    // Since we started an async request, mark the node as preliminary
                    $(data.node.span).addClass("pending");
                }
            },
            filter: {
                autoApply: true, // Re-apply last filter if lazy data is loaded
                autoExpand: false, // Expand all branches that contain matches while filtered
                counter: true, // Show a badge with number of matching child nodes near parent icons
                fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
                hideExpandedCounter: true, // Hide counter badge if parent is expanded
                hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
                highlight: true, // Highlight matches by wrapping inside <mark> tags
                leavesOnly: false, // Match end nodes only
                nodata: true, // Display a 'no data' status node if result is empty
                mode: "dimm" // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
            }
        }

    });
};







3- Ajax Call Back

DECLARE 
    l_node emp.empno%TYPE; 
    l_json VARCHAR2(4000); 
BEGIN 
    IF apex_application.g_x01 = 0 THEN 
      SELECT '['||listagg('{"key":'||id||','|| '"title":'||'"'||name||'"'||','|| '"lazy":'||'true'|| ',"icon":"'||icon||'"'||'}',',') within group(order by id)||']' 
      INTO   l_json 
      FROM   (SELECT LEVEL             lvl, 
                     empno             id, 
                     mgr               parent_id, 
                     ename             name, 
                     connect_by_isleaf is_leaf, 
                     icon   icon, 
                     empno             LINK 
              FROM   emp 
              CONNECT BY PRIOR empno = mgr 
              START WITH mgr IS NULL 
              ORDER  SIBLINGS BY ename) 
      WHERE  parent_id IS NULL; 

      htp.P(l_json); 
    ELSIF apex_application.g_x01 != 0 THEN 
      SELECT '['||listagg('{"key":'||id||','|| '"title":'||'"'||name||'"'||','|| '"lazy":'||CASE is_leaf 
                           WHEN 0 THEN 'true' 
                           ELSE 'false'
                         END || ',"icon":"'||icon||'"'||'}',',') within group(order by id)||']' 
             
      INTO   l_json 
      FROM   (SELECT LEVEL             lvl, 
                     empno             id, 
                     mgr               parent_id, 
                     ename             name, 
                     connect_by_isleaf is_leaf, 
                     icon   icon, 
                     empno             LINK 
              FROM   emp 
              CONNECT BY PRIOR empno = mgr 
              START WITH mgr IS NULL 
              ORDER  SIBLINGS BY ename) 
      WHERE  parent_id = apex_application.g_x01; 

      htp.P(l_json); 
    END IF; 
END; 

4- Add css

.ui-state-hover{
     background-color: #e8eff7 !important;
     box-shadow:none !important;
}

.fancytree-plain span.fancytree-node:hover span.fancytree-title {
    background-color: transparent !important;
    border-color: transparent !important;
}

.fancytree-plain span.fancytree-active span.fancytree-title, .fancytree-plain span.fancytree-selected span.fancytree-title {
    background-color: rgba(0, 0, 0, 0) !important;
    border-color: rgba(0, 0, 0, 0) !important;
}
   
    .ui-state-active {
        background-color: #2578cf !important;
        color: white !important;
        padding-left: 0px !important;
    }

ul.fancytree-container {
    border: none !important;
}

5- Demo
https://apex.oracle.com/pls/apex/f?p=123868:55





1 Kommentar:

  1. FancyTree looks like a really useful replacement for the standard Apex tree control.

    But I'm clearly not up to the level of this tutorial, as I can't follow how to install the FancyTree add-on into Apex, or even which Apex component type I should put the Ajax Call Back script you provided above.

    I can see I'd have to customize that script to insert my own query, but I'm not even clear where it would go.

    For example, should I create a region of type PL/SQL Dynamic Content?

    Also, your demo link above wouldn't show a tree on the tree control page, just the Title, ID and Search fields.

    Could you please provide a lower-level tutorial on how to implement this?

    Thanks in advance,

    Stew

    [My apologies if this is a duplicate. Among my other lacking skills, I don't read German.]

    AntwortenLöschen