Предварительный просмотр функции
Без лишних слов давайте взглянем на превью:
фон спроса
Ранее компания ранее разработала Vuue Project, который использует каскадную селекторную селектор Elementui. Удивительно, что отцы продукта начинают быть демонами. О, это не плохо. Я хочу использовать его в моем проекте (проект основан на JS + PHP, а передние и задние концы не разделены).
Ежедневная битва:
«Можете ли вы использовать многоуровневую связь?», «Нет!»
«Но ElementUi зависит от Vue. Два проекта имеют разные архитектуры, и этот элемент управления нужно разрабатывать отдельно».
«Это требование очень простое, мне все равно, как его выполнить».
"ммп"
анализ функциональных точек
Так как боя мало, никак, только чистый Js плагин можно написать. Вот некоторые особенности:
- Неограниченные уровни, множественный выбор и одиночный выбор выбираются через параметры конфигурации;
- Родитель выбран, все дети выбраны
2.1.Выбрать дочерний элемент, если выбраны все дочерние элементы одного уровня, то выбирается родитель; 2.2 При выборе дочернего элемента, если один из дочерних элементов на том же уровне не выбран, родительский элемент выбран наполовину; 2.3.Снять галочку с дочернего, если не выбран дочерний на том же уровне, то родитель не выбран; 3. Поддержка повторения полного пути выбранных данных родитель-родитель-потомок (разделители можно настроить с помощью элементов конфигурации) 4. Поддержка удаления одного выбранного элемента данных 5. Поддержка удаления всех выбранных элементов данных одним щелчком мыши (вы можете выбрать, включать или нет с помощью элементов конфигурации)
расположение идей
Здесь мы сначала анализируем принцип привязки данных Vue:
data中的每个数据,都通过数据劫持的方式,绑定了setter和getter,以便使之成为响应式
同时结合观察者模式,每个数据都维护一个观察者列表,只要使用到该数据,便将其加入到观察者列表中。
当该数据发生改变时,通知所有观察者,并传递新的数据。
Вернемся к требованиям: родитель изменяет дочерний и вносит соответствующие изменения, а дочерний изменяет, родитель также вносит соответствующие изменения.
В сочетании с практикой привязки данных Vue подумайте, может ли он извлечь уроки из своих идей?
Каждый родитель является целевым объектом и управляет списком наблюдателей. Список содержит ряд дочерних элементов. Если у дочернего элемента есть дочерние элементы, в это время дочерний объект становится целевым объектом, а также управляет списком наблюдателей. Ввод.
При этом в качестве целевого объекта дочерний объект должен иметь в списке наблюдателей не только своего дочернего, но и своего родителя, при изменении которого будет уведомлять всех внутренних наблюдателей об обновлении данных.
То есть, если принят режим наблюдателя или режим публикации-подписки, дочерний элемент должен быть разделен на два угла одновременно, который является одновременно издателем и подписчиком.
Принимая это во внимание, принять эту форму относительно сложно.Весь элемент управления должен не только поддерживать класс каскадной панели, но также должен одновременно добавлять список публикации и подписки для всех данных, а также более поздние затраты на обслуживание. тоже большой. пройти~
На самом деле, какой бы ни была идея, центральный момент остается прежним, а именно: изменить исходный DOM операции, изменить данные операции. вскореМышление, основанное на представлениях, превращается в мышление, основанное на данных. Давайте сначала посмотрим, как выглядят исходные данные (тот же формат, что и данные, переданные в ElementUi):
var tags = [
{
id: 1,
label: '中部',
children: [
{
id: 5,
label: '山西',
children: [
{ id: 6, label: '太原' }
]
}
]
},
{
id: 8,
label: '西北',
children: [
{
id: 9,
label: '陕西'
}
]
}
]
Формат данных очень простой, если не рассматривать каскадную выборку и DOM просто рендерится, то вообще проблем нет, достаточно рекурсивно зациклить данные, чтобы создать DOM.
Но я каскадный селектор.Как узнать, выбраны ли текущие данные?Как управлять родительским или дочерним выбранным состоянием?
Если он управляется представлением, рекурсивно визуализируйте приведенные выше данные непосредственно в тело, нажмите на родителя, обработайте все дочерние элементы под ним, щелкните дочерний элемент, получите статус того же уровня и обработайте все дочерние элементы под ним, пока операционный родитель. . Использование Jquery проще, не более чем серия siblings() parent(). Но это отклоняется от первоначального замысла элемента управления, и функция вообще не расширяется. Ближе к дому посмотрите, как управлять данными.
Прежде всего, глядя на входящие данные, они не должны соответствовать нашим потребностям Мы надеемся, что каждый узел имеет идентификацию того, выбран ли он, включая его родителя, дочернего элемента, текущий путь и так далее. Предварительный план таков:
{
id: 1,
checked: false, // 是否被选中
hasChildren: true, // 是否有子级
indeterminate: false,
label: '中部', // 文本
level: 1, // 节点等级
parent: null, // 父级
path: [1], // 当前节点ID路径
pathLabels: ['中部'], // 当前节点文本路径
value: 1, // 同ID
children: [ // 子节点
{
id: 2,
checked: false,
hasChildren: false,
indeterminate: false,
label: '山西',
level: 1,
parent: null,
path: [1, 2],
pathLabels: ['中部', '山西'],
value: 2
}
]
},
Таким образом, операция проста. Точно так же DOM создается рекурсивно. Если текущий проверенный узел истинен, это выбранное состояние. Как сделать привязку событий? Как узнать, какие данные являются текущим щелкнутым узлом?
Просто, при привязке DOM установить uid чекбокса (в режиме множественного выбора), соответствующего текущим данным, на id текущих данных через setAttribute, найти соответствующий ID в адаптированных данных при клике и установить его проверенным к !проверено. . Есть три конкретных ситуации:
一. 情况一:如果当前节点为一级节点
直接递归遍历所有子节点,设置子级 checked 属性
二. 情况二:如果当前节点不是一级节点,且有子节点,有两步:
1. 递归遍历所有子节点,设置子级 checked 属性
2. 找到与当前节点同级的节点,根据同级节点的 checked 属性,设置父级节点为 选中 || 半选中 || 取消选中
三. 情况三:如果当前节点为最后一级子节点:
找到与当前节点同级的节点,根据同级节点的 checked 属性,设置父级节点为 选中 || 半选中 || 取消选中
дизайн-мышление
Выше мы сказали, что нам нужно обрабатывать исходные данные, а все последующие операции основываются на обработанных данных. Это что-то вроде тени режима адаптера? Да, это можно понять, но не совсем. В этом примере создано всего два класса.
один дляКласс узла CascaderNode, провести преобразование упаковки (аналогично адаптерам) для каждого уровня узлов в исходных данных и добавить соответствующие методы (такие как: get text path, id path, set parent || child status и т. д.), т. е. каждый узел уровняКласс узлаэкземпляр .
один дляКласс каскадной панели EoCascader, этот класс также является классом, который окончательно выставляет интерфейс внешнему миру. Его основная функция — принимать пользовательские параметры и вызывать вышеупомянутыйКласс узла, создавать экземпляры узлов, визуализировать DOM на основе обработанных узлов, связывать события DOM...
Таким образом, каждый узел и DOM полностью развязаны по функциям, и DOM не нужно заботиться о том, как работает узел, а узлу не нужно заботиться о том, как внешний мир использует его собственный экземпляр.
Анализ сложности
Как ребенок манипулирует родителем
Интересно, заметили ли вы, что после обработки исходных данных выше каждый узел имеетindeterminate
Атрибут, дочерний управляет родителем, задается через этот атрибут. Значение по умолчанию — false, то есть не выбрано, а если оно истинно, то выбрано наполовину.
Сначала получите всех дочерних элементов под родительским элементом текущего узла (узел, на который нажали в данный момент)length
, допустим 3
через массивevery
чтобы узнать, все ли дочерние элементы выбраны, если он возвращает true, это означает, что все дочерние элементы выбраны, а если он возвращает false, это означает, что один из них не выбран.
Также установите переменную флагаflag
, запишите количество выбранных детей
еслиflag
Это то же самое, что и количество дочерних элементов (при условии, что здесь 3), тогда все родители выбираются
еслиflag
Если он не равен 0 и не равен количеству дочерних элементов, это означает, что один или несколько дочерних элементов не выбраны, родитель выбран наполовину, а родительский узел установлен.indeterminate
свойство верно.
Это закончено? Нет, а как насчет родителя здесь и родителя? Рекурсивно установить родителя родителяindeterminate
стоимость. код показывает, как показано ниже:
CascaderNode.prototype.onChildCheck = function onChildCheck(checked) {
this.checked = checked
var parent = this.parent
// 获取同级是否选中
var isChecked = parent.children.every(function (child) {
return child.checked
})
this.setCheckState(this.parent, isChecked);
}
// 递归函数,设置父级状态
CascaderNode.prototype.setCheckState = function setCheckState(parent, isChecked) {
parent.checked = isChecked
// 同级节点个数
var totalNum = parent.children.length;
// 记录同级节点选中个数
var checkedNum = parent.children.reduce(function (c, p) {
var num = p.checked ? 1 : p.indeterminate ? 0.5 : 0;
return c + num;
}, 0);
parent.indeterminate = checkedNum !== totalNum && checkedNum > 0;
// 如果父级还有父级,将 父级的父级传入,递归本函数即可
parent.parent && this.setCheckState(parent.parent, isChecked)
}
Как отображаются каскадные панели?
Во-первых, подумайте, как отображаются первичные данные? Вспоминая формат данных, вот прямой псевдокод:
var data = [
{
一级数据-1
child: [
二级数据-1-1
]
},
{
一级数据-2
child: [
二级数据-2-1
]
}
]
Хотите визуализировать каскадные панели? Хорошо сказано:
function render(data) {
// ...遍历
return
`
<div class="menu-wrap">
<li>一级数据-1</li>
<li>一级数据-2</li>
</div>
`
}
Обработать таким образом панель первого уровня не проблема, а вы думали о том, как отрисовать панель второго уровня? Вы также можете сохранить рекурсию, обход, код ужасен.
Здесь я должен рассказать об идее рендеринга ElementUi.Он поддерживает двумерный массив каскадных панелей отдельно, который специально используется для рендеринга.Конкретные идеи заключаются в следующем:
Настройте массив меню для хранения данных узла для рендеринга. В исходном случае это узел первого уровня:
[[一级数据-1, 一级数据-2]]
// 切记,每个数据都是 * 节点类 * 处理后的,其内部包含着子节点的数据
С помощью метода рендеринга визуализируется панель первого уровня. При нажатии на определенные данные на панели первого уровня получить дочерний узел под текущими данными и нажать его в меню
[[一级数据-1, 一级数据-2], [二级数据-1-1, 二级数据-1-2]]
Преимущество этого в том, что есть штатные данные для рендеринга, и больше нет необходимости выполнять рекурсивный рендеринг через обработанные данные класса узла, класс узла только оборачивает данные и возвращает экземпляр узла. Слой данных и слой рендеринга снова разделены.
Как хранить, удалять и повторять выбранные данные?
хранение и удаление
Этот пример хранится в виде объекта с текущим выбранным идентификатором узла в качестве ключа и текстовым путем в качестве значения. При рендеринге выбранного списка выполните итерацию по объекту.
Как это удалить? Не думайте, что просто удалите данные, соответствующие ID в списке хранилища и все будет хорошо, а еще разберитесь с выделенным состоянием текущего элемента в каскадной панели и выделенным состоянием его родителя. все еще звоните вышеonChildCheck
метод.
эхо данных
Если текущая страница является страницей редактирования, вход в каскадную панель страниц должен отображать последние отправленные данные.
Примечание. Эхо здесь состоит из двух частей: одна — это список выбранных данных (и текстовый путь), а другая — каскадная панель и статус выбора ее узла.
При использовании этого элемента управления разработчик может указать, какие данные были выбраны при последней отправке данных, следующим образом:
var cascader = new eo_cascader(tags, {
// 其他参数...
// 非编辑页,checkedValue 传入 null
// 编辑时 checkedValue 传入最后一级的 ID 即可
checkedValue: [4, 7, 10, 11, 21, 31, 33] || null
})
Теперь, когда получен идентификатор узла последнего уровня, с ним легко справиться.
- Рекурсивно найти дочерний узел, соответствующий текущему идентификатору, получить текстовый путь дочернего узла и сохранить его в списке данных для отображения выбранного текста;
- В то же время получить все данные родительского узла дочернего узла, установить их выбранное состояние (включая родителя родителя, слой за слоем, вплоть до узла первого уровня) и сформировать новый
menus
Массив для рендеринга каскадных панелей.
Суммировать
Хотя этот элемент управления не такой мощный, как каскадный селектор в ElementUi, такой как асинхронный сбор данных, данные с возможностью поиска и другие функции, он в основном отвечает потребностям отца продукта и будет продолжать итерации в будущем.
Завершение всего контроля, обнаружил, что управление данными является настолько мощным. Представьте, если вы основаны на DOM-управлении, вам достаточно работы с выбранным состоянием в каскадной панели. Если вы выполняете определенную функцию, вы можете также начать со слоя данных и управлять DOM на основе данных, что будет другим миром. Прикрепить элементы управленияисходный код, если есть какая-либо ошибка, отправьте ее и исправьте вовремя.
БамбукНаконец, нужно сказать: жареная лапша с соусом, давай!