Ajax - не просто полезная штука, это незаменимая штука. Но нет в мире совершенства. Под каждую задачу нужно писать отдельный скрипт. И если проект достаточно масштабный (у меня - корпоративная система управления предприятием с браузерным интерфейсом), файлы JS начинают разрастаться угрожающими темпами. Разумеется, в процессе работы выявляются закономерности, и более-менее похожие по функциональности элементы стандартизируются. Но все равно, разнообразие остается слишком велико, и непрерывно возрастает при развитии проекта.
Отсюда возник закономерный вопрос: а можно ли сделать всего одну, но универсальную функцию для бесконечного разнообразия задач? Не, я в курсе, что наверняка есть какие-нибудь фреймворки, но интереснее же придумать и сделать самому. К тому же никогда не любил фреймворки, все они в чем-то да ограничены.
После некоторых размышлений в голову пришла концептуальная идея: перенести основной функционал со стороны клиента на сторону сервера. Очевидно, что среверные скрипты (в моем случае - на языке Perl), предоставляют неизмеримо большую гибкость. К тому же и без того по определению пишутся под конкретные функции с переменными параметрами. Так пусть заодно и определяют и обработку своего результата на стороне клиента. В результате задача сформировалась следующим образом: необходим способ разметки пакета HTTP, чтобы универсальная функция на стороне клиента могла вызвать любой серверный скрипт, передать ему любые необходимые параметры и интерпретировать его ответ.
В результате получился следующий формат вывода результатов работы серверных скриптов. Можно расширять и углублять дальше, если потребуется. На данный момент сделана возможность обращения не только по ID, добавлена подпрограмма для обработки innerHTML выпадающих списков SELECT, создающая новые элементы списка через конструктор.
Как нетрудно заметить, свойства, обрабатываемые жабаскриптом в порядке object.property=value, типа innerHTML или className поддерживаются все, т.к. не требуют исключений. Из исключений, требующих отдельной функции, на данный момент обрабатываются свойства style, classList и setAttribute, а также передача файлов и установка cookies, так что в плане разметки обращение к ним ничем не отличается от работы с любыми другими элементами и свойствами.
Список свойств указывается в виде свойство="значение", разделенных пробелами, запятой или точкой с запятой. Если значение содержит только буквенно-цифровые символы и символ подчеркивания, кавычки можно опустить, в противном случае следует использовать одинарные или двойные кавычки.
Пример вывода результатов серверным скриптом:
И так далее, все согласно HTML. Очевидно, что в результате над тремя элементами с указанными ID будут выполнены определенные действия, кроме того, в определенном порядке будут выполнены 3 вызова жабаскрипт-функций. Все действия выполняются пообъектно, т.е. в том порядке, в котором происходит первое обращение к данному ID. Если вы сперва изменили содержимое элемента 1, потом содержимое элемента 2, в потом к элементу 1 добавили новый стиль, фактически в результате сперва выполнятся все действия с элементом 1, а потом с элементом 2.Код:print '<SPLIT target="div1" property="innerHTML">'; print $text1; print '<script target="div1" property="before">alert(\'alert1\')</script>'; print '<SPLIT target="div1_class" method="getElementsByClassName" property="style">'; print 'width=100px'; print 'background-color="#ffffff" padding="20px 5px"; print '<SPLIT target="input1" property="value" noclear>'; print $text2; print '<SPLIT target="select1" property="innerHTML">'; print qq '<option value="$arr[$e][1]">$arr[$e][7]</option>'; print qq '<option selected value="$arr[$e][1]">$arr[$e][7]</option>'; print '<SPLIT target="select1" property="className">'; print 'class1'; print '<script target="input1">alert(\'alert2\')</script>'; print '<script>alert(\'alert3\');alert(\'alert4\')</script>'; print '<SPLIT target="select1" property="classList">'; print 'add=newclass delete=oldclass toggle=class_2'; print '<SPLIT target="select1" property="setAttribute">'; print 'src="aaa.bbb.ru"'; my $ctime = time + 600; #время актуальности +- секунды print "<cookie name='sid' value='$cookie' expires='$ctime'>"; print "<cookie name='sim' value='Test' expires='$ctime'>";
С яваскрипт-функциями аналогично. Если не задано принудительное изменение очередности, то все они будут выполняться после выполнения всех действий с HTML, в той последовательности, в котором были написаны.
<SPLIT>
method - необязательный параметр, указывает метод выборки элементов. По умолчанию - getElementById. Поддерживаются методы getElementsByTagName, getElementsByName, getElementsByClassName, querySelector и querySelectorAll.
target - обязательный параметр, указывает ID элемента или условия для поиска согласно выбранному методу.
property - обязательный параметр, указывает изменяемое свойство элемента.
noclear - необязательный параметр, Его наличие указывает, что в свойствах innerHTML и value результат будет дополняться к уже существующему, а не заменять его. Параметр задается (если нужен) при первом обращении к ID, в не зависимости от свойства.
Есле в теле одного ответа содержится несколько обращений к одному свойству одного элемента, то все они в любом случае будут просуммированы. А общий результат, если это innerHTML или value, будет либо дополнен к имеющемуся, либо заменит его, в зависимости от параметра noclear.
<SCRIPT>
target - необязательный параметр (ID элемента). По умолчанию notarget. Привязывает очередность выполнения функции к действиям с элементом с указанным ID.
property - необязательный параметр (before/after). По умолчанию after. Указывает, будет эта функция выполняться перед действиями с указанным элементом или после. Если стоит before, но не указан target, будет выполняться в самом начале.
<cookie>
Внутри тега задается список параметров видп имя=значение. Пример есть выше. Name и value = обязательные параметры, expires задается в UnixTime в секундах.
Функция запроса вызывается следующим образом:
SendURL(url, form, aspect);
url - обязательный параметр, адрес вызываемого серверного скрита. Может содержать дополнительные параметры стандартного вида GET. Например: ('cgi/function.cgi?action=1&option='+this.value).
form - необязательный параметр, имя формы (FormName), данные из которой будут собраны и присоединены к запросу, если указанная форма существует. Может быть указано несколько форм в виде массива [].
aspect - необязательный параметр (1/0), по умолчанию 0. Определяет параметр запроса, 0 - асинхронный, 1 - синхронный.
Пример вызова:
Сама функцияКод:SendURL(('cgi/function.cgi?action=1&option='+this.value), 'MyForm', 1);Код:function SendURL(url, frm, asp) { var data = StringForSend(frm); var xmlHttp = new XMLHttpRequest(); var syn = (!asp || asp == '' || asp == 0) ? true : false; // 1 - синхронный xmlHttp.open("POST", url, syn); xmlHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { var string = xmlHttp.responseText; if (xmlHttp.status == 200) { var arr = new Array(), chk = new Object(), scr = new Object(); string = string.replace(/<script([^>]*)>([^<]*)<\/script>/gi, function(a,b,c) { c = c.replace(/&[lg]t;/g, m => m.charAt(1) === "l" ? "<" : ">"); var obj = SendURLprops(b); if (!('target' in obj)) obj.target = 'notarget'; if (!('property' in obj)) obj.property = 'after'; if (!(obj.target in scr)) scr[obj.target] = new Object(); if (!(obj.property in scr[obj.target])) scr[obj.target][obj.property] = new Array(); scr[obj.target][obj.property].push(c+';'); return ''; }); string = string.replace(/<cookie([^>]*)>/gi, function(a,b) { var obj = SendURLprops(b); var cookie = obj.name + '=' + encodeURIComponent(obj.value) + ';'; for (var pos in obj) { if (pos == 'name' || pos == 'value') continue; if (pos == 'expires') obj[pos] = new Date(obj[pos] * 1000).toUTCString(); cookie += (' ' + pos + '=' + obj[pos] + ';'); } document.cookie = cookie; return ''; }); var res = string.split('<SPLIT '); for (var i = 0; i < res.length; i++) { res[i].replace(/^([^>]*)>(.*)$/, function(a,b,c) { var obj = SendURLprops(b); obj.noclear = (b.match(/\bnoclear\b/i)) ? true : false; if (!('method' in obj)) obj.method = 'getElementById'; if (!(obj.method in chk && obj.target in chk[obj.method])) { arr.push({target: obj.target, method: obj.method, noclear: obj.noclear}); if(!chk[obj.method]) chk[obj.method] = {target: obj.target}; if(!chk[obj.method][obj.target]) chk[obj.method][obj.target] = new Object(); } if (obj.property in chk[obj.method][obj.target]) chk[obj.method][obj.target][obj.property] += c; else chk[obj.method][obj.target][obj.property] = c; }); } SendURLeval(scr.notarget, 'before'); if (arr.length == 0) console.log('Not defined targets for output'); for (var i = 0; i < arr.length; i++) { if (document[arr[i].method](arr[i].target)) { SendURLeval(scr[arr[i].target], 'before'); var lst = document[arr[i].method](arr[i].target); var obj = (arr[i].method == 'getElementById' || arr[i].method == 'querySelector') ? [lst] : lst; if (obj.length == 0) console.log('Not found targets for '+arr[i].target); for (var j = 0; j < obj.length; j++) { if (!obj[j]) continue; for (var pos in chk[arr[i].method][arr[i].target]) { var str = chk[arr[i].method][arr[i].target][pos]; if (obj[j].type == "textarea" && pos == 'innerHTML') str = str.replace(/\\n/g, '\n'); if (pos == 'style' || pos == 'setAttribute' || pos == 'classList') SendURLattrib(obj[j], pos, str); else if (obj[j].type == "select-one" && pos == 'innerHTML') SendURLselect(obj[j], str, arr[i].noclear); else if ((pos == 'innerHTML' || pos == 'value') && arr[i].noclear == true) obj[j][pos] += str; else obj[j][pos] = str; } } SendURLeval(scr[arr[i].target], 'after'); } else console.log('Error target for '+arr[i].target); } SendURLeval(scr.notarget, 'after'); } else console.log('Error Ajax request: '+xmlHttp.status); } }; xmlHttp.send(data); } function StringForSend(frm) { var data = new FormData(); //data.append('refer', document.getElementById('refer').value); //добавляем к каждому запросу if (document.cookie.length > 0) { var pairs = document.cookie.split(";"); for (var i = 0; i < pairs.length; i++){ var pair = pairs[i].split("="); data.append(pair[0], decodeURIComponent(pair[1])); } } data.append('random', Math.random()); if (!frm || frm == '') return data; var arr = (frm instanceof Array) ? frm : [frm]; for (var j = 0; j < arr.length; j++) { var elements = document.forms[arr[j]].querySelectorAll('input, select, textarea'); for (var i = 0; i < elements.length; i++) { if (!elements[i].name || elements[i].name == '') continue; if (["text", "hidden", "password", "textarea"].includes(elements[i].type)) { data.append(elements[i].name, elements[i].value); } if (elements[i].type == "radio" || elements[i].type == "checkbox") { if (elements[i].checked) data.append(elements[i].name, elements[i].value); } if (elements[i].type == "select-one" && elements[i].selectedIndex >= 0) { data.append(elements[i].name, elements[i].options[elements[i].selectedIndex].value); } if (elements[i].type == "file" && elements[i].files.length > 0) { if (elements[i].getAttribute('multiple') == null) { data.append(elements[i].name, elements[i].files[0]); } else { for (var e = 0; e < elements[i].files.length; e++) { data.append((elements[i].name+'_'+e), elements[i].files[e]); } } } } } return data; } function SendURLeval(scr, pos) { if (scr && scr[pos]) for (var i = 0; i < scr[pos].length; i++) eval(scr[pos][i]); } function SendURLprops(str) { var obj = new Object(); str.replace(/\s*(\w+)\s*[=:]{1}\s*("{1}[^"]*"{1}|'{1}[^']*'{1}|[\w-]+)\s*/gi, function(a,b,c) { obj[b.trim()] = c.trim().replace(/(^["']|["']$)/g, ''); }); return obj; } function SendURLattrib(obj, pos, str) { var res = SendURLprops(str); for (var key in res) { if (pos == 'style') obj.style[key] = res[key]; if (pos == 'classList') obj.classList[key](res[key]); if (pos == 'setAttribute') obj.setAttribute(key, res[key]); } } function SendURLselect(obj, str, cls) { if (cls == false) obj.options.length = 0; str.replace(/<option([^>]*)>([^<]*)<\/option>/gi, function(a,b,c) { c = c.replace(/&[lg]t;/g, m => m.charAt(1) === "l" ? "<" : ">"); var res = SendURLprops(b); var opt = document.createElement("option"); opt.appendChild(document.createTextNode(c)); opt.setAttribute("value", res.value); if (b.match(/\bselected\b/i)) opt.selected = true; obj.appendChild(opt); }); }
Поскольку POST создается в виде объекта, чтобы в него можно было засунуть файлы и куки, что в обычном состоянии Аякс не позволяет, ссылаясь на безопасность, на стороне CGI-скрипта чтение нужно делать комбинированным: GET читать как обычно, через переменные окружения, а вот POST через CGI->param.
Код:use CGI ':standard'; my $buffer = $ENV{'QUERY_STRING'}; my @pairs = split(/&/, $buffer); foreach $pair(@pairs) { my ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/chr(hex($1))/eg; $input{$name} = $value; } for my $name (CGI::param()) { for my $value (scalar CGI::param($name)) { $input{$name} = $value; } }
-
Скрыть объявление
Друзья, в это тяжёлое и непонятное для всех нас время мы просим вас воздержаться от любых упоминаний политики на форуме, - этим ситуации не поможешь, а только возникнут ненужные ссоры и обиды. Это касается также шуток и юмора на тему конфликта. Пусть войны будут только виртуальными, а политики решают разногласия дипломатическим путём. С уважением, администрация Old-Games.RU.
-
Скрыть объявлениеЕсли Вы видите это сообщение, значит, вы ещё не зарегистрировались на нашем форуме.
Зарегистрируйтесь, если вы хотите принять участие в обсуждениях. Перед регистрацией примите к сведению:
- Не регистрируйтесь с никами типа asdfdadhgd, 354621 и тому подобными, не несущими смысловой нагрузки (ник должен быть читаемым!): такие пользователи будут сразу заблокированы!
- Не регистрируйте больше одной учётной записи. Если у вас возникли проблемы при регистрации, то вы можете воспользоваться формой обратной связи внизу страницы.
- Регистрируйтесь с реально существующими E-mail адресами, иначе вы не сможете завершить регистрацию.
- Обязательно ознакомьтесь с правилами поведения на нашем форуме, чтобы избежать дальнейших конфликтов и непонимания.
С уважением, администрация форума Old-Games.RU
Комментарии
Сортировать комментарии по