Filtrado de datos declarativos en el frente. Js / ts


¿A menudo tiene que escribir controladores de filtro para sus datos? Pueden ser matrices para dibujar tablas, tarjetas, listas, cualquier cosa.


Cuando el filtrado es estático, entonces todo es simple. Características estándar map, filtery reducebastante. Pero, ¿qué pasa si los datos tienen una estructura compleja o anidamiento, y puede haber muchas reglas para el filtro? Las reglas pueden repetirse, los datos pueden cambiarse y cuanto más controles aparecen los filtros, más complejo e inestable será el código del controlador.


¿Cómo resolver el problema de la creciente complejidad?


Yo mismo enfrenté este problema al desarrollar una aplicación que funciona con una gran cantidad de datos. Poco a poco se agregaron más y más filtros nuevos.


, . . , . , . - .


, .
, :


  1. ng-table — . .
  2. List.js — ng-table, .
  3. filter.js — , , .
  4. Isotope — DOM . .

, :


  1. .
  2. .
  3. .
  4. , and or .
  5. . .
  6. ( , , , , , )

awesome-data-filter.


, , .



.


npm install awesome-data-filter

.
, :


const users = [
  {
    age: 31,
    name: "Marina Gilmore",
  },
  {
    age: 34,
    name: "Joyner Mccray",
  },
  {
    age: 23,
    name: "Inez Copeland",
  },
  {
    age: 23,
    name: "Marina Mitchell",
  },
  {
    age: 25,
    name: "Prince Spears",
  },
];

:


const filterValue = {
  age: 23,
  searchText: "mari",
};

, .


:


  • matchText — ;
  • equalProp — ;
    • betweenDates — ;
    • equalOneOf — ;
    • someInArray — ;
    • isEmptyArray — ;
    • lessThen — , ;
    • moreThen — , ;
    • not — .

matchText equalProp.


:


  • filterField — ;
  • elementField — .

import { 
  buildFilter, 
  elementField, 
  filterField, 
} from "awsome-data-filter";
import { matchText, equalProp } from "awsome-data-filter/rules";
import { and } from "awsome-data-filter/conditions";

const filter = buildFilter({
    rules: {
      elementFilter: and([
        matchText(filterField("searchText"), elementField("name")),
        equalProp(filterField("age"), elementField("age")),
      ]),
    },
  });

const { elements } = filter(
    filterValue,
    {
      groups: [],
      elements: users,
    },
);

console.log(elements);
// elements: [{ age: 23, name: "Marina Mitchell" }]

filter groups elements.


, . .



, , elements.


and or, 2 .


import { 
  buildFilter, 
  elementField, 
  filterField, 
} from "awsome-data-filter";
import { matchText, equalProp } from "awsome-data-filter/rules";
import { or } from "awsome-data-filter/conditions";

const filter = buildFilter({
    rules: {
      elementFilter: or([
        matchText(filterField("searchText"), elementField("name")),
        equalProp(filterField("age"), elementField("age")),
      ]),
    },
  });

const { elements } = filter(
    filterValue,
    {
      groups: [],
      elements: users,
    },
);

console.log(elements);
// elements: 
// [
//   {
//     age: 31,
//     name: "Marina Gilmore",
//   },
//   {
//     age: 23,
//     name: "Inez Copeland",
//   },
//   {
//     age: 23,
//     name: "Marina Mitchell",
//   }
// ]

filterField, elementField .


constValue .
or(..., matchText, [and([..., matchText, ...]), or([..., ...])])


. :


const dataList = [
  {
    groupName: "first group",
    list: [
      { age: 31, name: "Marina" },
      { age: 23, name: "Fabio" },
    ],
  },
  {
    groupName: "second group",
    groups: [
      {
        groupName: "third group",
        list: [],
        groups: [
          {
            groupName: "fourth group",
            list: [{ age: 42, name: "Li" }],
          },
        ],
      },
    ],
    list: [
      { age: 41, name: "Marina" },
      { age: 29, name: "Inez" },
      { age: 33, name: "Marina" },
    ],
  },
  {
    groupName: "fifth group",
    list: [
      { age: 21, name: "Dmitriy" },
      { age: 22, name: "Li" },
      { age: 45, name: "Mitchell" },
    ],
  },
];

traversal:


import { 
  buildFilter, 
  elementField, 
  filterField, 
} from "awsome-data-filter";
import { matchText } from "awsome-data-filter/rules";

const filter = buildFilter({
    traversal: {
      getChildrenFunc: group => group.list, //    
      setChildrenFunc: (group, list) => ({ ...group, list }), //      
      getGroupsFunc: group => group.groups, //    
      setGroupsFunc: (group, groups) => ({ ...group, groups }), //    
    },
    rules: {
      elementFilter: matchText(filterField("searchText"), elementField("name")),
    },
  });

const filterValue = {
  searchText: "li",
};

const { groups } = filter(filterValue, {
  groups: dataList, //      
  elements: [], //       
});

console.log(groups);
// groups: 
//[
//  {
//    groupName: "second group",
//    groups: [
//      {
//        groupName: "third group",
//        list: [],
//        groups: [
//          {
//            groupName: "fourth group",
//            list: [{ age: 42, name: "Li" }],
//          },
//        ],
//      },
//    ],
//    list: [],
//  },
//  {
//    groupName: "fifth group",
//    list: [
//      { age: 22, name: "Li" },
//    ],
//  },
//]

elementFilter , . groupFilter .


import { 
  buildFilter, 
  elementField, 
  filterField, 
} from "awsome-data-filter";
import { matchText } from "awsome-data-filter/rules";

const filter = buildFilter({
    traversal: {
      getChildrenFunc: group => group.list, //    
      setChildrenFunc: (group, list) => ({ ...group, list }), //      
      getGroupsFunc: group => group.groups, //    
      setGroupsFunc: (group, groups) => ({ ...group, groups }), //    
    },
    rules: {
      elementFilter: matchText(filterField("searchText"), elementField("name")),
      groupFilter: matchText(filterField("groupName"), elementField("groupName")),
    },
  });

const filterValue = {
  searchText: "li",
  groupName: "fi",
};

const { groups } = filter(filterValue, {
  groups: dataList,
  elements: [],
});

console.log(groups);
// groups:
//[
//  {
//    groupName: "first group",
//    list: [
//      { age: 31, name: "Marina" },
//      { age: 23, name: "Fabio" },
//    ],
//  },
//  {
//    groupName: "second group",
//    groups: [
//      {
//        groupName: "third group",
//        list: [],
//        groups: [
//          {
//            groupName: "fourth group",
//            list: [{ age: 42, name: "Li" }],
//          },
//        ],
//      },
//    ],
//    list: [],
//  },
//  {
//    groupName: "fifth group",
//    list: [
//      { age: 22, name: "Li" },
//    ],
//  },
//]

first group , , , .
fifth group , , .


. :


const standardStrategy: StrategiesFilterInterface = {
  elementHandler: ({ //     
    element,
    tools: { 
        isGroupFilterIsActive, //     
        applyElementFilter //   
    },
  }) => {
    if (isGroupFilterIsActive) return null;
    if (!applyElementFilter) return element;
    return applyElementFilter(element, true) ? element : null;
  },
  groupHandler: ({
    element: group,
    originalElement: originalGroup,
    tools: {
      isGroupFilterIsActive,
      applyElementFilter,
      getIsGroupFilterHaveMatch,
      getGroupsFunc,
      getChildrenFunc,
      setChildrenFunc,
    },
  }) => {
    let newChildren = [];
    let originalChildren = [];
    const children = getChildrenFunc(group);
    const childrenExists = !!children;
    //     
    if (children) {
      originalChildren = [...children];
      newChildren = originalChildren.filter(element =>
        applyElementFilter
          ? applyElementFilter(element, !isGroupFilterIsActive)
          : !isGroupFilterIsActive,
      );
    }
    //     ,    ,   
    if (!newChildren.length && getIsGroupFilterHaveMatch(group)) {
      return originalGroup;
    }
    //     ,    
    if (childrenExists) {
      group = setChildrenFunc(group, newChildren);
    }

    //   
    const newGroups = getGroupsFunc(group);
    const isGroupsExists = !!(newGroups && newGroups.length);
    const isElementExists = !!(newChildren && newChildren.length);
    //      ,   
    return isElementExists || isGroupsExists ? group : null;
  },
};

, filterStrategy.



awesome-data-filter . .



, . .


, .


All Articles