2015-12-01 2 views
2

У меня есть данные, в которых есть глубины, и мне нужно отобразить эти данные, отображающие глубину вложенности. Как только данные визуализируются, их нужно сортировать так, чтобы каждая «глубина» сортировалась независимо, но оставалась прикрепленной к ней.Сортировка дерева Javascript

До сих пор мне удалось преобразовать глубины в дерево, как массив объектов, и на основе этого сделать рендер. У меня есть сортировка на месте, но я не могу понять, как заставить дочерние элементы оставаться относительно родителя.

Вот код, который я написал до сих пор:

var data = getData(); 
 

 
// convert the data to a tree structure 
 
var tree = convertToTree(data); 
 

 

 
$(function($) { 
 

 
    init(); 
 

 

 
    function init() { 
 
    renderTree(tree); 
 
    } 
 

 
    function renderTree(tree) { 
 
    var $table = $('#tree1'); 
 

 
    var rows = []; 
 

 
    function renderTreeRow(row) { 
 
     if (row.hasOwnProperty('score')) { 
 
     rows.push([ 
 
      '<tr class="depth_' + row.depth + '">', 
 
      ' <td>' + row.label + '</td>', 
 
      ' <td>' + row.row + '</td>', 
 
      ' <td>' + row.depth + '</td>', 
 
      ' <td>' + row.score + '</td>', 
 
      ' <td>' + row.count + '</td>', 
 
      '</tr>' 
 
     ]); 
 
     } else { 
 
     rows.push([ 
 
      '<tr class="depth_' + row.depth + ' group_heading">', 
 
      ' <td colspan="5">' + row.label + '</td>', 
 
      '</tr>' 
 
     ]); 
 
     } 
 
     if (row.hasOwnProperty('children') && row.children.length) { 
 
     $.each(row.children, function(i, childRow) { 
 
      renderTreeRow(childRow); 
 
     }); 
 
     } 
 
    } 
 

 
    $.each(tree, function(i, row) { 
 
     renderTreeRow(row); 
 
    }); 
 

 
    $table.find('tbody tr').remove(); 
 
    $('tbody', $table).append(rows.join('')); 
 
    } 
 

 
    $('th[data-sort-asc]').off('.data_sort_asc').on('click.data_sort_asc', function(event) { 
 
     var target = $(event.target).closest('th'); 
 
     var sortAscending = target.attr('data-sort-asc') == "true"; 
 
     var property = target.attr('data-sort-property'); 
 
     var treeSorter = sortByProperty(property, sortAscending); 
 
     treeSorter(tree); 
 
     renderTree(tree); 
 
     // flip sort direction 
 
     target.attr('data-sort-asc', !sortAscending); 
 
     target.find('span').remove().end().append('<span class="glyphicon glyphicon-sort-by-attributes' + (sortAscending ? '' : '-alt') + '" aria-hidden="true"></span>'); 
 
    }).append('<span class="glyphicon glyphicon-sort" aria-hidden="true"></span>') 
 
    .css({ 
 
     'cursor': 'pointer' 
 
    }); 
 

 
}); 
 

 

 

 
function sortByProperty(property, ascending) { 
 
    ascending = ascending !== undefined ? ascending : true; 
 
    var flip = ascending ? 1 : -1; 
 

 
    function sortTree(arr) { 
 
    function compare(a, b) { 
 
     if (a[property] < b[property]) { 
 
     return -1 * flip; 
 
     } 
 
     if (a[property] > b[property]) { 
 
     return 1 * flip; 
 
     } 
 
     return 0; 
 
    } 
 
    $.each(arr, function(i, el) { 
 
     if (el.hasOwnProperty('children')) { 
 
     sortTree(el.children); 
 
     } 
 
    }); 
 
    arr.sort(compare); 
 
    } 
 
    return sortTree; 
 
} 
 

 
/* 
 
function flatternTree(tree) { 
 
\t var ret = []; 
 
\t $.each(tree, function(i, row) { 
 
\t \t ret.push(row); 
 
\t \t if(row.hasOwnProperty('children') && row.children.length) { 
 
\t \t \t ret = ret.concat(flatternTree(row.children)); 
 
\t \t } 
 
\t }); 
 
\t return ret; 
 
} 
 
*/ 
 

 
function convertToTree(arr) { 
 
    return treeify(splitToSegments(arr)); 
 
} 
 

 
function splitToSegments(arr) { 
 
    var result = []; 
 
    var accum, segment; 
 

 
    $.each(arr, function(i, it) { 
 
    if (it.depth === 0) { 
 
     accum = true; 
 
     if (segment && segment.length) { 
 
     result.push(segment); 
 
     } 
 
     segment = [it]; 
 
    } else if (accum) { 
 
     segment.push(it); 
 
    } 
 
    }); 
 

 
    if (segment && segment.length) { 
 
    result.push(segment); 
 
    } 
 

 
    return result; 
 
} 
 

 
function treeify(arr) { 
 
    return arr.map(function(o) { 
 
    return o.reduce(function(a, b) { 
 
     var lastChild; 
 
     a.children = a.children || []; 
 
     if (a.depth + 1 === b.depth) { 
 
     a.children.push(b); 
 
     } else { 
 
     lastChild = a.children[a.children.length - 1]; 
 
     lastChild.children = lastChild.children || []; 
 
     lastChild.children.push(b); 
 
     } 
 
     return a; 
 
    }); 
 
    }); 
 
} 
 

 

 
function getData() { 
 
    // rows of data that we get, we have depth but not nested as parent>child relationships. The order & depth determines parent>child relationship 
 
    var payload = [ 
 
    {row: 1, group: "group_30", "depth": 0, "label": "TOP"}, 
 
    {row: 2, group: "group_30", "depth": 1, "score": 10, "count": 600, "label": "A1"}, 
 
    {row: 3, group: "group_30", "depth": 2, "score": 10, "count": 100, "label": "A1>2-1"}, 
 
    {row: 4, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-1>3-1"}, 
 
    {row: 5, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-1>3-2"}, 
 
    {row: 6, group: "group_30", "depth": 2, "score": 20, "count": 200, "label": "A1>2-2"}, 
 
    {row: 7, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-2>3-1"}, 
 
    {row: 8, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-2>3-2"}, 
 
    {row: 9, group: "group_30", "depth": 2, "score": 30, "count": 300, "label": "A1>2-3"}, 
 
    {row: 10, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-3>3-1"}, 
 
    {row: 11, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-3>3-2"}, 
 
    {row: 12, group: "group_30", "depth": 1, "score": 10000, "count": 10, "label": "B1"}, 
 
    {row: 13, group: "group_30", "depth": 2, "score": 1000, "count": 1, "label": "B1>2-1"}, 
 
    {row: 14, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-1>3-1"}, 
 
    {row: 15, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-1>3-2"}, 
 
    {row: 16, group: "group_30", "depth": 2, "score": 3000, "count": 3, "label": "B1>2-2"}, 
 
    {row: 17, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-2>3-1"}, 
 
    {row: 18, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-2>3-2"}, 
 
    {row: 19, group: "group_30", "depth": 2, "score": 6000, "count": 6, "label": "B1>2-3"}, 
 
    {row: 20, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-3>3-1"}, 
 
    {row: 21, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-3>3-2"} 
 
    ]; 
 
    return payload; 
 
}
/* Styles go here */ 
 

 
.depth_0 { 
 
    background-color: #B2A788; 
 
} 
 
.depth_1 { 
 
    background-color: #7686B2; 
 
} 
 
.depth_2 { 
 
    background-color: #9BC2CC; 
 
} 
 
.depth_3 { 
 
    background-color: #DCE5FF; 
 
} 
 
.group_heading td { 
 
    border-bottom: 3px solid #BFB8A5 
 
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css"> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> 
 

 
<p>Clicking the headers should sort each 'grouping' independantly, so that child elements appear directly after the parent. When the parent is sorted the child elements should move with it. Thanks!</p> 
 

 
<table id="tree1" class="table"> 
 
    <thead> 
 
    <tr> 
 
     <th>label</th> 
 
     <th data-sort-asc="true" data-sort-property="row">row</th> 
 
     <th data-sort-asc="true" data-sort-property="depth">depth</th> 
 
     <th data-sort-asc="true" data-sort-property="score">score</th> 
 
     <th data-sort-asc="true" data-sort-property="count">count</th> 
 
    </tr> 
 
    </thead> 
 
    <tbody> 
 

 
    </tbody> 
 
</table>

Спасибо за любые мысли!

ответ

1

Отвечая на мой собственный вопрос - я ожидаю, что есть более элегантный способ сделать это, но публикация на случай, если это поможет другим. Предложения по его улучшению приветствуются.

var data = getData(); 
 

 
// convert the data to a tree structure 
 
var tree = convertToTree(data); 
 

 

 
$(function($) { 
 

 
    init(); 
 

 

 
    function init() { 
 
    renderTree(tree); 
 
    } 
 

 
    function renderTree(tree) { 
 
    var $table = $('#tree1'); 
 

 
    var rows = []; 
 

 
    function renderTreeRow(row) { 
 
     if (row.hasOwnProperty('score')) { 
 
     rows.push([ 
 
      '<tr class="depth_' + row.depth + '">', 
 
      ' <td>' + row.label + '</td>', 
 
      ' <td>' + row.row + '</td>', 
 
      ' <td>' + row.depth + '</td>', 
 
      ' <td>' + row.score + '</td>', 
 
      ' <td>' + row.count + '</td>', 
 
      '</tr>' 
 
     ]); 
 
     } else { 
 
     rows.push([ 
 
      '<tr class="depth_' + row.depth + ' group_heading">', 
 
      ' <td colspan="5">' + row.label + '</td>', 
 
      '</tr>' 
 
     ]); 
 
     } 
 
     if (row.hasOwnProperty('children') && row.children.length) { 
 
     $.each(row.children, function(i, childRow) { 
 
      renderTreeRow(childRow); 
 
     }); 
 
     } 
 
    } 
 

 
    $.each(tree, function(i, row) { 
 
     renderTreeRow(row); 
 
    }); 
 

 
    $table.find('tbody tr').remove(); 
 
    $('tbody', $table).append(rows.join('')); 
 
    } 
 

 
    $('th[data-sort-asc]').off('.data_sort_asc').on('click.data_sort_asc', function(event) { 
 
     var target = $(event.target).closest('th'); 
 
     var sortAscending = target.attr('data-sort-asc') == "true"; 
 
     var property = target.attr('data-sort-property'); 
 
     var treeSorter = sortByProperty(property, sortAscending); 
 
     treeSorter(tree); 
 
     renderTree(tree); 
 
     // flip sort direction 
 
     target.attr('data-sort-asc', !sortAscending); 
 
     target.find('span').remove().end().append('<span class="glyphicon glyphicon-sort-by-attributes' + (sortAscending ? '' : '-alt') + '" aria-hidden="true"></span>'); 
 
    }).append('<span class="glyphicon glyphicon-sort" aria-hidden="true"></span>') 
 
    .css({ 
 
     'cursor': 'pointer' 
 
    }); 
 

 
}); 
 

 

 

 
function sortByProperty(property, ascending) { 
 
    ascending = ascending !== undefined ? ascending : true; 
 
    var flip = ascending ? 1 : -1; 
 

 
    function sortTree(arr) { 
 
    function compare(a, b) { 
 
     if (a[property] < b[property]) { 
 
     return -1 * flip; 
 
     } 
 
     if (a[property] > b[property]) { 
 
     return 1 * flip; 
 
     } 
 
     return 0; 
 
    } 
 
    $.each(arr, function(i, el) { 
 
     if (el.hasOwnProperty('children')) { 
 
     sortTree(el.children); 
 
     } 
 
    }); 
 
    arr.sort(compare); 
 
    } 
 
    return sortTree; 
 
} 
 

 
function convertToTree(arr) { 
 
    var result = []; 
 
    var previous; 
 
    var lastDepth = []; 
 

 
    $.each(arr, function(i, it) { 
 
    previous = arr[i-1]; 
 
    if (it.depth === 0) { // top level 
 
     result.push(it); 
 
    } 
 
    else if (it.depth > previous.depth) { 
 
     // child of previous 
 
     previous.children = previous.children || []; 
 
     previous.children.push(it); 
 
    } 
 
    else { 
 
     lastDepth[it.depth-1].children = lastDepth[it.depth-1].children || []; 
 
     lastDepth[it.depth-1].children.push(it); 
 
    } 
 
    lastDepth[it.depth] = it; 
 
    }); 
 

 
    return result; 
 
} 
 

 
function getData() { 
 
    // rows of data that we get, we have depth but not nested as parent>child relationships. The order & depth determines parent>child relationship 
 
    var payload = [ 
 
    {row: 1, group: "group_30", "depth": 0, "label": "TOP"}, 
 
    {row: 2, group: "group_30", "depth": 1, "score": 10, "count": 600, "label": "A1"}, 
 
    {row: 3, group: "group_30", "depth": 2, "score": 10, "count": 100, "label": "A1>2-1"}, 
 
    {row: 4, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-1>3-1"}, 
 
    {row: 5, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-1>3-2"}, 
 
    {row: 6, group: "group_30", "depth": 2, "score": 20, "count": 200, "label": "A1>2-2"}, 
 
    {row: 7, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-2>3-1"}, 
 
    {row: 8, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-2>3-2"}, 
 
    {row: 9, group: "group_30", "depth": 2, "score": 30, "count": 300, "label": "A1>2-3"}, 
 
    {row: 10, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "A1>2-3>3-1"}, 
 
    {row: 11, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "A1>2-3>3-2"}, 
 
    {row: 12, group: "group_30", "depth": 1, "score": 10000, "count": 10, "label": "B1"}, 
 
    {row: 13, group: "group_30", "depth": 2, "score": 1000, "count": 1, "label": "B1>2-1"}, 
 
    {row: 14, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-1>3-1"}, 
 
    {row: 15, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-1>3-2"}, 
 
    {row: 16, group: "group_30", "depth": 2, "score": 3000, "count": 3, "label": "B1>2-2"}, 
 
    {row: 17, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-2>3-1"}, 
 
    {row: 18, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-2>3-2"}, 
 
    {row: 19, group: "group_30", "depth": 2, "score": 6000, "count": 6, "label": "B1>2-3"}, 
 
    {row: 20, group: "group_30", "depth": 3, "score": 222, "count": 9, "label": "B1>2-3>3-1"}, 
 
    {row: 21, group: "group_30", "depth": 3, "score": 22, "count": 99, "label": "B1>2-3>3-2"} 
 
    ]; 
 
    return payload; 
 
}
/* Styles go here */ 
 

 
.depth_0 { 
 
    background-color: #B2A788; 
 
} 
 
.depth_1 { 
 
    background-color: #7686B2; 
 
} 
 
.depth_2 { 
 
    background-color: #9BC2CC; 
 
} 
 
.depth_3 { 
 
    background-color: #DCE5FF; 
 
} 
 
.group_heading td { 
 
    border-bottom: 3px solid #BFB8A5 
 
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css"> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> 
 

 

 
<table id="tree1" class="table"> 
 
    <thead> 
 
    <tr> 
 
     <th>label</th> 
 
     <th data-sort-asc="true" data-sort-property="row">row</th> 
 
     <th data-sort-asc="true" data-sort-property="depth">depth</th> 
 
     <th data-sort-asc="true" data-sort-property="score">score</th> 
 
     <th data-sort-asc="true" data-sort-property="count">count</th> 
 
    </tr> 
 
    </thead> 
 
    <tbody> 
 

 
    </tbody> 
 
</table>

Смежные вопросы