Ответ 1
Итак, расскажите о том, как создавать базовые графические приложения. Прежде чем продолжить, я хочу, чтобы вы знали, что код ниже может быть написан в ~ 20 LoC в Knockout/ Angular, но я решил не делать этого, потому что это ничему не научило.
Итак, расскажите о графическом интерфейсе.
Все это сводится к двум вещам.
- Презентация - это ваш HTML, css и все, с чем пользователь напрямую взаимодействует.
- Данные - это ваши фактические данные и логика.
Мы хотим отделить их, чтобы они могли действовать независимо. Мы хотим получить реальное представление о том, что пользователь видит в объекте JavaScript, чтобы он был легко поддающимся проверке, читабельным для чтения и т.д. И т.д. Подробнее см. Разделение проблем.
Начнем с данных.
Итак, что каждая вещь имеет в вашем приложении?
- A Первый элемент, true или false
- A Sub Item либо true, либо false, но никогда не будет true, если первый элемент не соответствует true.
- A Второй элемент, который является либо истинным, либо ложным.
- A Число элементов, которое является числом
- Каждый из этих элементов - это яблоко, банан или манго.
Самая интуитивная вещь - начать прямо там.
// our item, like we've just described it :)
function Thing(){ //we use this as an object constructor.
this.firstItem = false;
this.subItem = false;
this.secondItem = false;
this.numItems = 0;
this.items = []; // empty list of items
}
Итак, теперь мы можем создать их с помощью new Thing()
, а затем установить их свойства, например thing.firstItem = true
.
Но у нас нет a Thing
у нас есть вещи. Вещи - это просто (упорядоченная) куча вещей. Упорядоченная коллекция обычно представлена массивом в JavaScript, поэтому мы можем иметь:
var stuff = []; // our list
var thing = new Thing(); // add a new item
stuff.push(thing); // add the thing we just created to our list
Мы можем, конечно, также сообщать об этом PHP при отправке. Одна альтернатива представляет объект JSON и читает это в PHP (это приятно!), Альтернативно мы можем сериализовать его как параметры формы (если у вас есть какие-либо проблемы с методами в этом вопросе - дайте мне знать).
Теперь у меня просто куча объектов... и головная боль.
Довольно проницательный. Пока у вас есть только объекты, вы не указали их поведение нигде. У нас есть наш "уровень данных", но пока у нас нет никакого уровня представления. Мы начнем с избавления от всех идентификаторов и добавления поведения.
Введите шаблоны!
Вместо клонирования существующих объектов мы хотим иметь способ "cookie cutter" для создания внешнего вида новых элементов. Для этого мы будем использовать шаблон. Начнем с того, что вы узнаете, как выглядит ваш "список элементов" в HTML-шаблоне. В принципе, учитывая ваш html, это что-то вроде:
<script type='text/template' data-template='item'>
<ul class="clonedSection">
<li style="list-style-type: none;">
<label><input class="main-item" type="checkbox" />First Item</label>
<ul class="sub-item" style="display: none;">
<li style="list-style-type: none;">
<label><input type="checkbox" />Sub Item</label>
</li>
</ul>
</li>
<li style="list-style-type: none;">
<label>
<input class="main-item" type="checkbox" />Second Item</label>
<ul class="sub-item" style='display: none;'>
<li style="list-style-type: none;">
How many items:
<select class="medium" required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;"><div></div></li>
</ul>
</li>
</ul>
</script>
Теперь давайте создадим "немой" метод для отображения шаблона на экране.
var template;
function renderItem(){
template = template || $("[data-template=item]").html();
var el = $("<div></div>").html(template);
return el; // a new element with the template
}
[Здесь наша первая демонстрационная демонстрация jsfiddle] (http://jsfiddle.net/RLRtv/, которая просто добавляет три элемента, без поведения на экране. код, см., что вы это понимаете, и не бойтесь спросить о битах, которые вы не понимаете:)
Связывание их вместе
Затем добавим некоторое поведение, когда мы создадим элемент, мы свяжем его с Thing
. Таким образом, мы можем сделать одностороннее связывание данных (где изменения в представлении отражают модель). Мы можем реализовать другое направление привязки позже, если вы заинтересованы, но это не часть исходного вопроса, поэтому для краткости пока пропустите его.
function addItem(){
var thing = new Thing(); // get the data
var el = renderItem(); // get the element
el. // WHOOPS? How do I find the things, you removed all the IDs!?!?
}
Итак, где мы застряли? Нам нужно добавить поведение к нашему шаблону, но обычные HTML-шаблоны не имеют для этого крючка, поэтому мы должны сделать это вручную. Начнем с изменения нашего шаблона с помощью свойств привязки данных.
<script type='text/template' data-template='item'>
<ul class="clonedSection">
<li style="list-style-type: none;">
<label>
<input class="main-item" data-bind = 'firstItme' type="checkbox" />First Item</label>
<ul class="sub-item" data-bind ='subItem' style="display: none;">
<li style="list-style-type: none;">
<label>
<input type="checkbox" />Sub Item</label>
</li>
</ul>
</li>
<li style="list-style-type: none;">
<label>
<input class="main-item" data-bind ='secondItem' type="checkbox" />Second Item</label>
<ul class="sub-item" style='display: none;'>
<li style="list-style-type: none;">How many items:
<select class="medium" data-bind ='numItems' required>
<option value="">---Select---</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</li>
<li style="list-style-type: none;">
<div data-bind ='items'>
</div>
</li>
</ul>
</li>
</ul>
</script>
Просмотреть все атрибуты data-bind
, которые мы добавили? Попробуйте выбрать их.
function addItem() {
var thing = new Thing(); // get the data
var el = renderItem(); // get the element
//wiring
el.find("[data-bind=firstItem]").change(function(e){
thing.firstItem = this.checked;
if(thing.firstItem){//show second item
el.find("[data-bind=subItem]").show(); //could be made faster by caching selectors
}else{
el.find("[data-bind=subItem]").hide();
}
});
el.find("[data-bind=subItem] :checkbox").change(function(e){
thing.subItem = this.checked;
});
return {el:el,thing:thing}
}
В эта скрипта мы добавили свойства к первому элементу и подпункту, и они уже обновили элементы.
Давайте продолжим делать то же самое для второго атрибута. Это почти то же самое, привязка напрямую. На стороне примечания есть несколько библиотек, которые делают это для вас автоматически - Нокаут например
Вот еще одна скрипка со всеми установленными связями, это завершило наш уровень представления, наш слой данных и их привязку.
var template;
function Thing() { //we use this as an object constructor.
this.firstItem = false;
this.subItem = false;
this.secondItem = false;
this.numItems = 0;
this.items = []; // empty list of items
}
function renderItem() {
template = template || $("[data-template=item]").html();
var el = $("<div></div>").html(template);
return el; // a new element with the template
}
function addItem() {
var thing = new Thing(); // get the data
var el = renderItem(); // get the element
el.find("[data-bind=firstItem]").change(function (e) {
thing.firstItem = this.checked;
if (thing.firstItem) { //show second item
el.find("[data-bind=subItem]").show(); //could be made faster by caching selectors
} else {
el.find("[data-bind=subItem]").hide();
}
});
el.find("[data-bind=subItem] :checkbox").change(function (e) {
thing.subItem = this.checked;
});
el.find("[data-bind=secondItem]").change(function (e) {
thing.secondItem = this.checked;
if (thing.secondItem) {
el.find("[data-bind=detailsView]").show();
} else {
el.find("[data-bind=detailsView]").hide();
}
});
var $selectItemTemplate = el.find("[data-bind=items]").html();
el.find("[data-bind=items]").empty();
el.find("[data-bind=numItems]").change(function (e) {
thing.numItems = +this.value;
console.log(thing.items);
if (thing.items.length < thing.numItems) {
for (var i = thing.items.length; i < thing.numItems; i++) {
thing.items.push("initial"); // nothing yet
}
}
thing.items.length = thing.numItems;
console.log(thing.items);
el.find("[data-bind=items]").empty(); // remove old items, rebind
thing.items.forEach(function(item,i){
var container = $("<div></div>").html($selectItemTemplate.replace("{number}",i+1));
var select = container.find("select");
select.change(function(e){
thing.items[i] = this.value;
});
select.val(item);
el.find("[data-bind=items]").append(container);
})
});
return {
el: el,
thing: thing
}
}
for (var i = 0; i < 3; i++) {
var item = addItem();
window.item = item;
$("body").append(item.el);
}
Кнопки
Самое интересное, что теперь мы закончили утомительную часть, кнопки - кусок пирога.
Добавьте кнопку "добавить"
<input type='button' value='add' data-action='add' />
и JavaScript:
var stuff = [];
$("[data-action='add']").click(function(e){
var item = addItem();
$("body").append(item.el);
stuff.push(item);
});
Мальчик, что было легко.
Хорошо, поэтому удаление должно быть довольно сложным, не так ли?
HTML:
<input type='button' value='remove' data-action='remove' />
JS:
$("[data-action='remove']").click(function(e){
var item = stuff.pop()
item.el.remove();
});
Хорошо, так что это было очень мило. Итак, как мы получаем наши данные? Позвольте создать кнопку, которая отображает все элементы на экране?
<input type='button' value='show' data-action='alertData' />
и JS
$("[data-action='alertData']").click(function(e){
var things = stuff.map(function(el){ return el.thing;});
alert(JSON.stringify(things));
});
Woah! У нас есть фактическое представление наших данных в нашем слое модели. Мы можем делать все, что захотим, очень мило.
Что делать, если я хочу представить его как форму? $.param
на помощь.
<input type='button' value='formData' data-action='asFormData' />
И JS:
$("[data-action='asFormData']").click(function(e){
var things = stuff.map(function(el){ return el.thing;});
alert($.param({data:things}));
});
И в то время как этот формат не очень приятный, то что-то PHP (или любая другая популярная технология) с радостью прочитает на стороне сервера.
Итак, чтобы обернуть его
- Отдельная презентация из данных
- Если у вас есть JS-логика - есть один источник правды - объекты JavaScript
- Подумайте об этом подробнее, узнайте об общих рамках, таких как KnockoutJS или AngularJS, которые имеют интересные менее подробные решения этой проблемы (за счет допущений).
- Подробнее о архитектуре пользовательского интерфейса. Это хороший (но трудный для начинающих) ресурс
- Избегайте дублирования идентификаторов, они плохие - пока вы там не храните данные в своем доме.
- Не бойтесь задавать вопрос - так вы учитесь.
- Вы можете легко избавиться от jQuery здесь.