Вход

Просмотр полной версии : Каталог с несколькими фильтрами: проверка на отображение (в т.ч. используется jQuery)


armidoll
13.06.2015, 10:42
Доброго времени суток.

Задача: сделать каталог с фильтрами

Сам вопрос/суть проблемы в п.6. в самом конце

Принятые проектные решения:

1. Элементы каталога первоначально содержатся в виде (возможно количество свойств увеличится):
var projects = [
{
id: 'pb-15001',
name: 'ПБ-15.001',
area: 200,
thumbUrl: 'img/projects/p-15001/p15001-thumb.jpg',
floors: 2,
type: 'индивидуальный',
bedrooms: 4
},
{
...
},
{
...
}
]

2. Функция makeCatalog() создает HTML код (добавляет в DOM другая) только при инициализации. В итоге контейнером для отдельного элемента каталога служит <div />. Эта же функция заполняет массив refers[] содержащий связанные ссылки на элементы в projects[] (см. п.1) и соответствующие контейнеры <div />.

!!! Также сюда хочу запихнуть информацию ( см. hiddenBy{} ), какие фильтры потребовали скрыть контейнер <div />, чтобы отображался только в том случае, если этому не препятствует ни один из фильтров. Не факт, что такое направление моих мыслей правильное.

function makeCatalog(){ // Create HTML part of the catalog and fill cross-ref array
projects.forEach(function(project){
var $div = $('<div id="' + project.id + '"></div>'),
$a = $('<a href="#"></a>');
$a.append( $('<img src="' + project.thumbUrl + '"/>') );
$a.append( $('<p><span>' + project.name + '</span><span>' + project.area + ' м<sup>2</sup></span></p>') );
$div.append($a);
refers.push({
project: project,
$element: $div,
hiddenBy: {}

});
});
}


3. Есть несколько фильтров (их HTML часть):
<aside id="filters">
<div id="filter-by-area">
Площадь<br />
Min
<input id="area-min" name="area-min" type="number" />
Max
<input id="area-max" name="area-max" type="number" />
<div id="filter-slider"></div>
</div>
<div id="filter-by-order">
Сортировка по<br />
<input type="radio" name="sort-order" value="by-name" checked="checked" />новизне<br />
<input type="radio" name="sort-order" value="area-asc" />возрастанию<br />
<input type="radio" name="sort-order" value="area-desc" />убыванию
</div>
<div id="filter-by-house-type">
Тип дома<br />
<input type="checkbox" checked="checked" name="floors1" />Одноэтажные<br />
<input type="checkbox" checked="checked" name="floors2" />Двухэтажные
</div>
<div id="filter-by-project-type">
Тип проекта<br />
<input type="checkbox" checked="checked" name="individual" />Индивидуальные<br />
<input type="checkbox" checked="checked" name="typical" />Типовые
</div>
<div id="filter-by-bedrooms">
Кол-во спален<br>
<input type="checkbox" checked="checked" name="bed1" />1
<input type="checkbox" checked="checked" name="bed2" />2
<input type="checkbox" checked="checked" name="bed3" />3
<input type="checkbox" checked="checked" name="bed4" />4
<input type="checkbox" checked="checked" name="bed5" />5
</div>
</aside>


4. При наступлении события запускаем различные функции (они пока не прописаны толком) в зависимости от инициировавшего фильтра:
$('#filters').children().change( function(e) {

console.log('fired: ' + $(this).attr('id'));

var firedFilterId = $(this).attr('id');
var firedFilterEl = e.target;

switch (firedFilterId){

case 'filter-by-area':
console.log('switched to: filter-by-area');
updateRefersByArea( $minArea.val(), $maxArea.val(), firedFilterId );
break;

case 'filter-by-order':
console.log('switched to: filter-by-order');
sortRefersByArea(firedFilterEl.value);
appendCatalog();
break;

case 'filter-by-house-type':
console.log('switched to: filter-by-house-type');
console.log(firedFilterEl.name);
break;

case 'filter-by-project-type':
console.log('switched to: filter-by-project-type');
console.log(firedFilterEl.name);
break;

case 'filter-by-bedrooms':
console.log('switched to: filter-by-bedrooms');
console.log(firedFilterEl.name);
break;

default:
break;

}
});


5. Например, функция updateRefersByArea() отображает или скрывает контейнеры <div /> в зависимости от выбранного значения площади:
function updateRefersByArea(minArea, maxArea, firedId){ // Show or hide projects depending on change of min and max area values

console.log('updateRefersByArea() is starting...');

refers.forEach(function(record){

var hidden = record.hiddenBy;

if (hidden.firedId == null){
hidden.firedId = false;
}

if (record.project.area >= minArea && record.project.area <= maxArea ){

hidden.firedId = false;
record.$element.show()

} else {

hidden.firedId = true;
record.$element.hide();
}
});
}


6. Теперь, наконец-то, вопрос:

Каким образом лучше сделать проверку в каждой из функций, запускающейся в зависимости от того, какой фильтр выстрелил (см., например, п.5)?
В результате проверки функция должна отобразить/не отобразить соответствующий контейнер <div /> в зависимости от того, потребовали или нет еще какие-то фильтры скрыть контейнер <div />.

Вижу два очевидных пути:
1. Перебирать по именам значения в hiddenBy {} на true / false
2. Преобразовать hiddenBy {} в массив hiddenBy[] и каждую функцию/фильтр закрепить за элементом массива с определенным индексом

Не хочу так поступать, т.к. п.3 покоя не даёт

3. В голове крутится, что неплохо бы было как-то сделать, чтобы куда-то в "список" добавлялся в нашем случае firedFilterId (например, в массив), и дальше происходило следующее при запуске функции на отображение/скрытие:
3.1. Удаляется/добавляется в список firedFilterId
3.2. Проверяется length массива, если '0' отображаем сразу без проблем (т.к. не содержит id ни одного фильтра), иначе пропускаем / или смотрим есть ли такое св-во у объекта

Сегодня сяду долбить вариант с массивом, потом хочу попробовать через свойства объектов. Суть наверное ясна, не хочется прописывать все условия поштучно или завязывать функции/фильтры на элементы массива вручную, а хочется чего-то такого, чтобы если потом добавишь еще хоть 10 фильтров всё бы раз и заработало, без необходимости переписывать кучу кода. Может через строку как-то попробовать, не знаю...

Ну и, конечно, желательно чтобы компьютер пользователя в спячку не впадал в итоге...

Пардон за сумбур в голове и плохое знание матчасти, но знал бы не спрашивал.

рони
13.06.2015, 11:09
:)
фильтрация массива
function blender(arr, filters) {
return arr.filter(function(el) {
return filters.every(function(filter) {
return filter(el)
})
})
};
var projects = [
{
id: 'pb-15001',
name: 'ПБ-15.001',
area: 200,
thumbUrl: 'img/projects/p-15001/p15001-thumb.jpg',
floors: 400,
type: 'индивидуальный',
bedrooms: 4
},
{
id: 'pb-15001',
name: 'ПБ-15.001',
area: 300,
thumbUrl: 'img/projects/p-15001/p15001-thumb.jpg',
floors: 200,
type: 'индивидуальный',
bedrooms: 4
},
{
id: 'pb-15002',
name: 'ПБ-15.001',
area: 200,
thumbUrl: 'img/projects/p-15001/p15001-thumb.jpg',
floors: 100,
type: 'индивидуальный',
bedrooms: 4
}
]

fn = function(el) {
return el['floors'] == 100
}
fn2 = function(el) {
return el['area'] == 200
}
var test = blender(projects, [fn, fn2])
alert(JSON.stringify(test))

laimas
13.06.2015, 11:34
Суть наверное ясна, не хочется прописывать все условия поштучно или завязывать функции/фильтры на элементы массива вручную, а хочется чего-то такого, чтобы если потом добавишь еще хоть 10 фильтров всё бы раз и заработало, без необходимости переписывать кучу кода. Может через строку как-то попробовать, не знаю...

Если это фильтр для имеющего на странице, то под JQ есть плагин, который фильтрует по указанным свойствам, сортирует, может скрывать тем, что не указаны в фильтрах, и др. Название не помню его, но найти можно, да и не один скорее всего.

Если же предполагается, что нужно фильтровать выборку табличных данных, согласно наборам которых они и будут выводиться, и которыми могут быть не только фильтры, но и отображаемые поля таблиц данных, сортировка, разрешения на групповое редактирование, указание таблицы данных или набор таблиц, возможность сохранять такие конфигурации как наборы под различные задачи и др., то нужно делать совсем по иному.

armidoll
13.06.2015, 12:27
рони,
Спасибо за пример. Изучу.

laimas,
Если это фильтр для имеющего на странице, то под JQ есть плагин, который фильтрует по указанным свойствам, сортирует, может скрывать тем, что не указаны в фильтрах, и др. Название не помню его, но найти можно, да и не один скорее всего.

Да, дело касается именно фильтрации имеющегося на странице, по-возможности без удаления_старого_каталога / создания_нового /выводе_нового. В идеале просто перебрать контейнеры после каждого вызова соответствующей функции и отобразить/скрыть.

Плагины использовать сейчас не хочу, т.к. по сути именно и изучаю как они внутри работают.

laimas
13.06.2015, 13:53
по сути именно и изучаю как они внутри работают

Ну тогда и вопрос не должно быть. :) Шутка.

Меня смущает тип данных filter-by-house-type, filter-by-bedrooms, ... то есть речь о скорее всего идет о недвижимости. Я как раз сейчас работаю над заказом для агентства недвижимости. У них два десятка риэлторов, и каждый вынужден получать табличные данные такими, какие установлены в единой конфигурации. Для администрирования это крайне неудобно, а тасовать полученные данные на клиенте используя фильтрацию, это бессмыслица, так сервер вынужден отдавать все скопом, а не только то, что нужно, а если предполагается и редактирование табличных данных, то это усложнение как серверной, так клиентской части кода. Да и вывод табличных данных для администрирования не ради тасовать на клиенте делается, а все тики для работы.

Вот и было сделано пользовательское управление выводом табличных данных, которое у каждого пользователя индивидуальное. Клиент хранит и управляет наборами, а правила для наборов определяет сервер.

Фильтр, а также и наборы для других представлений, как то сортировка, и т.д., уж никак не определяются как filter-by-house-type, filter-by-bedrooms. Поступая таким образом практически не возможно написать универсальное управление выводом, для которого выходными данными были бы только таблица и ее поля. Для каждой таблицы и ее полей придется писать свою "кочергу".

Агентство занимается недвижимостью - квартиры, дома, участки, и другие. Все это, это хранение данных в различных таблицах. Но не смотря на это, и различие в типах недвижимости, у этих таблиц много полей описывающих один и те же данные: адреса, площади, количество комнат и т.п. Кроме этого ведь есть и другие таблицы обслуживающие работу агентства и тоже нуждающиеся в управлении.

Фильтр, вернее параметры фильтра, как и параметры сортировки определяются типом полей данных. А их не так и много, числа, числа с плавающей точкой, списки, даты. Достаточно унифицировать имена полей таблиц так, чтобы чтобы к примеру поле адреса "город" во всех таблицах указывало на на один и тот же тип параметров, который будет выбраться как параметры фильтра или сортировки.

Остается в таблицу определяющую поля таблиц, которым разрешают/запрещают выбор для какого либо из наборов (отображение, фильтр, сортировка и т.д.), указать тип данных, по которым клиент и будет строить поля для выбора параметров фильтра, сортировки...

То есть параметры полей, это унифицированные наборы, определяемые самими полями данных - все строится на примитивах, которые в конечном итоге позволяют указать любую конфигурацию вывода данных. Нужно добавить в управление какую либо таблицу еще, значит это указать ее поля в таблице разрешений/запрещений, и она будет автоматически доступна клиенту для определения вывода ее данных, а сервер по этим же описанным разрешениям/запрещениям и на основе набора клиента выводит данные в том представлении, которое и определяет клиент.

А вот для пользователей на страницах, которым тоже будет доступен фильтр вывода данных, определяется иной сервис. Фильтр, это все таки не тасовать полученный набор на странице, а получение данных только отвечающих фильтру. Но так как вывод данных в любом случае будет постраничный, а на каждой из них пользователя может что-то заинтересовать, то удобно было бы делать выборку таких объектов на каждой странице и заносить их в набор. В этом случае можно запрашивать у сервера только те данные, которые заинтересовали пользователя, и с ними уже проводить различные операции - сортировать, удалять из набора и т.д..

Я это делал исходя из задач, которые определяются риэлтором для работы с данными, и сервисом для клиента. Какова конечная задача вашего фильтра я не знаю, если ради "крутить/вертеть" данными на странице, ну красиво, но а толку от этого? :) Для галереи, и подобного контента это и хорошо, да и полезно будет, а если недвижимость, то вряд ли целесообразно.

armidoll
14.06.2015, 13:07
Да, вы правильно подметили, что каталог имеет некоторое отношение к объектам недвижимости, правда к проектам индивидуальных домов (представленных в виде архитектурно-строительной документации). Смысл каталога - позволить пользователю отобрать для изучения проекты в соответствии с его требованиями.

То что будет реализовано в данной версии, это, я бы сказал, не какое-то серьезное решение с БД, а скорее ближе к вашему понятию "крутить-вертеть" только на клиентской стороне. Там проектов-то не более сотни будет в лучшем случае, да и то в будущем.

laimas
14.06.2015, 15:18
Ну если такое, может быть это и имеет смысл, хотя на мой взгляд, это бессмыслица, даже пусть и для сотни объектов. Пользователь то ведь использует фильтр в надежде на то, что сервер вернет ему объекты удовлетворяющие условию фильтра, для этого они и служат, и не только для недвижимости, но и для вывода товаров и прочего.
А тут получается, что сперва вывалили все, а потом в этом "мусоре крутить-вертеть" надо.
И где в этом смысл зарыт я не понимаю.

armidoll
14.06.2015, 18:55
Так а если объектов немного (<100), они не связаны с другими таблицами/полями/не являются сложными структурами, жрущими вагон ресурсов, то зачем каждый раз после изменения фильтров к серверу обращаться?

И такой еще момент, вываливается не все, а только определенная достаточно однородная категория, мы ведь не валим в кучу проекты домов, одежду и электробритвы.

С кэшем ведь удобнее и быстрее будет в итоге, разве нет? Вот представьте паршивый бесплатный wi-fi в ресторанном дворике где-нить или на даче.

К примеру, как вы считаете, каталоги такого уровня стоит пользователю целиком пересылать и дальше фильтровать только у него или после каждого изменения фильтров с запросами к серверу обращаться (я ведь правильно понимаю идею?):
http://doma-is-brusa.com/catalog/
или
http://www.rovaniemi.ru/houses/houses/individual/large/

PS Обратите внимание, это не БД по недвижке, тут совсем другой масштаб, он гораздо скромнее.

laimas
14.06.2015, 20:20
не являются сложными структурами, жрущими вагон ресурсов

Это кто такое сказал, что сложная структура, это жрать ресурсы вагонами? )

С кэшем ведь удобнее и быстрее будет в итоге, разве нет?

Кеширование не является способностью исключительно клиента, кешировать можно даже и запросы, но суть не в этом. Тем более, пусть у меня "паршивый бесплатный wi-fi в ресторанном дворике где-нить или на даче". Захожу я к вам с "паршивого", а вы "ведь не валите в кучу проекты домов, одежду и электробритвы", а значит, по идее, я буду иметь представление о том, что же вы "валите" на страницу. Но каким образом я получу это представление: а) попадая на главную страницу, где знакомлюсь с родом деятельности сервиса, и загружаю на свой "паршивый" минимально необходимый для этого объем информации; б) а не тут то было, мне на мой "паршивый" вывалили все дома или еще чего-то?

Надеюсь, что вариант а). Возникает вопрос - если я заинтересован в покупке, и возможно у вас есть дом с видом на Эйфелеву башню, с двумя этажами и о трех окнах, с ценой от и до, то что для моего "паршивого" будет выгоднее: а) если есть выбор по фильтру, выбрать нужные мне параметры его, и получить пусть уже больший, чем у главной страницы объем, но все таки ограниченный; б) нет, получай все сполна, а потом ищи в нем свою башню?

Будь у меня "паршивый", я тем более бы предпочел вариант а). То что клиент к вам зашел, еще не означает, что он что-то выберет, и совсем не обязательно клиенту впаривать все, ссылаясь на "прожорливость ресурсов или паршивость устройств". Решать за клиента что ему надо, это на мой взгляд самоуверенность, а вот предугадать желания клиента, это хорошо. И если бы я такое решал в рамках ограниченного предложения, то использовал бы, например, таргетинг (https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%D1%80%D0%B3%D0%B5%D1%82%D0%B8%D0%BD%D 0%B3), с целью предугадать и предложить, если есть что. Но в любом случае бы старался избежать вываливания всего что есть клиенту.

Я не пытаюсь вас отговорить, делайте как считаете нужным, любая идея имеет право на осуществление, а успешна ли она будет, время покажет. Это всего лишь мое представление. :)

armidoll
14.06.2015, 21:46
Спасибо, что тратите на меня свое время, на разъяснения. Честно, благодарен!

Когда дочитаю учебник по javascript и возьму в руки, например, по php, встану на диаметрально противоположную позицию по отношению к решаемой задаче и негативному анализу будет подвергнут javascript.

Через анализ к синтезу ;)

PS Вы мне про высокие материи широкого контекста, а я только с массивами/объектами сижу разбираюсь :D

laimas
15.06.2015, 00:15
Если есть время, почему бы не потратить его, ведь все равно на что-то да будет потрачено.

Нет в моих словах никакой высокой материи, и даже намеков нет на конкретно JS или РНР, тут сугубо "политические мотивы" - речь о электорате. Чем привлечь, как не растерять, а "истинная политическая платформа", это уже вторично. ;)

armidoll
15.06.2015, 10:10
В итоге с фильтрами пришел к написании двух функций runBeforeShow() и runBeforeHide(). Они используются для проверки можно ли отобразить элемент <div /> (при активации любого фильтра проверяется требуют ли другие фильтры не отображать).

function runBeforeShow (hiddenByData, firedId){
if(hiddenByData.ids[firedId] == true){ // Set fired filter Id to false
hiddenByData.ids[firedId] = false;
hiddenByData.counter--;

if (hiddenByData.counter == 0){ // Check if there was the fired filter only
return true;
} else {
return false;
}
}
}

function runBeforeHide (hiddenByData, firedId){
if (!hiddenByData.ids[firedId] || hiddenByData.ids[firedId] == false) {
hiddenByData.ids[firedId] = true;
hiddenByData.counter++;
}
}


Вызываются, например, стр.06 и 12:
function updateRefersByFloor(firedValue, firedChecked, firedId){
refers.forEach(function(record){
var hiddenByData = record.hiddenBy;
if (firedChecked){
if (record.project.floors == firedValue ){
if(runBeforeShow(hiddenByData, firedId)) {
record.$element.show();
}
}
} else {
if (record.project.floors == firedValue){
runBeforeHide(hiddenByData, firedId);
record.$element.hide();
}
}

});
}


Сейчас у обеих функций два параметра:
1) Список флагов-id фильтров требующих отображать/не отображать элемент
2) Id активированного фильтра, требующего скрыть/отобразить

Список содержится в hiddenBy.ids[] :

refers.push({
project: project,
$element: $div,
hiddenBy: {
ids: [],
counter: 0
}
});


Вопрос:
стоит ли указанные в начале функции сделать методами объекта hiddenBy{} , если всего будет создаваться до сотни таких объектов? Функции меняют только значения свойств объекта hiddenBy{}.
Вроде даже можно будет уменьшить передаваемые им аргументы с двух до одного (только firedID).

armidoll
15.06.2015, 13:17
удалено

armidoll
19.08.2015, 22:50
Есть форма:

<form id="filter">
...
<input id="area-01" type="checkbox" name="area" value="100">100
...
</form>
Есть массив для фильтров
var filters = []
Есть обработчик событий, который слушает <form:"filter">. Пусть его часть выглядит так:
var firedEl = event.target;
var property = firedEl.name;
var value = firedEl.value;

Нужно чтобы js сгенерировал функцию используя текущие значения переменных property и value. Сама функция хранится в массиве filters[] и для нашего примера должна выглядеть так:

function(el) {
return el["area"] == 100
}

Как будет выглядеть js код, создающий такую функцию?
Он должен быть универсальным и работать для любых допустимых значений property и value.

Добавлено:
Сделал пока так:
Catalog.prototype.addFilter = function(property, value) {

var funcBody = "return el[" + property + "] == " + value + " ;" ;
var func = new Function ("el", funcBody);

this._filters.push(func);
};

А почему не получилось так?:

Catalog.prototype.addFilter = function(property, value) {

function makeFilter(prop, val) {
return function(el) {
return el[prop] == val;
};
}

var func = makeFilter(property, value);

this._filters.push(func);
}