initial
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
<!---
|
||||
Access check aka проверка доступа.
|
||||
Если доступа нет, код, заключенный внутри тега, не выполняется, текст не выводится.
|
||||
|
||||
Два варианта использования.
|
||||
1. При заданном значении действия проверяется наличие этого доступа к объекту, ответ да-нет
|
||||
2. Проверяется уровень доступа к заданному объекту
|
||||
|
||||
Для пустого объекта
|
||||
|
||||
Если задать строковый атрибут output, в переменную с таким именем запишется
|
||||
TRUE(доступ разрешен)/FALSE(доступ запрещен).
|
||||
Тег обязательно должен быть закрыт.
|
||||
---><cfsilent>
|
||||
<!--- проверка на закрытость тега - без закрытия возможна неоднозначность со вложенными тегами --->
|
||||
<cfif NOT thisTag.hasEndTag>
|
||||
<cfthrow
|
||||
type="custom" message="Unclosed AC.CFM tag found."
|
||||
detail="AC.CFM tag must be closed. Self-closed tag is acceptable, e.g.: <cfmodule template=""mod/ac.cfm"" />.">
|
||||
</cfif>
|
||||
|
||||
<cfif thisTag.ExecutionMode is 'start'>
|
||||
|
||||
<cfparam name="ATTRIBUTES.obj" type="string" default=""/><!---
|
||||
объект, к которому проверяется доступ --->
|
||||
<cfparam name="ATTRIBUTES.act" type="numeric" default="#request.PERMISSION_READ#"/><!---
|
||||
действие, которое проверяется --->
|
||||
<cfparam name="ATTRIBUTES.output" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.permissionOut" type="string" default=""/>
|
||||
|
||||
|
||||
|
||||
<cfset permission=request.PERMISSION_NONE/>
|
||||
|
||||
<cfif ATTRIBUTES.obj EQ "">
|
||||
<cfif #request.usrAuthenticated()#>
|
||||
<cfset permission=request.PERMISSION_WRITE/>
|
||||
<cfelse>
|
||||
<cfset permission=request.PERMISSION_READ/>
|
||||
</cfif>
|
||||
|
||||
<cfset permitted=(ATTRIBUTES.act EQ #request.PERMISSION_READ# OR #request.usrAuthenticated()#)/>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<cftry>
|
||||
<cfset permitted=(len(ATTRIBUTES.obj) GT 0)>
|
||||
|
||||
<!---*** Максимальный доступ для перечисленных объектов и ролей
|
||||
Можно подумать о минимальном--->
|
||||
<cfquery name="qACL" datasource="#request.DS#">
|
||||
select coalesce(max(permission),0) as permission
|
||||
from usr_role ur
|
||||
join acl on acl.role_id=ur.role_id
|
||||
where ur.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#request.usr_id#">
|
||||
and acl.obj in (<cfqueryparam cfsqltype="cf_sql_varchar" value="#ATTRIBUTES.obj#" list="true">)
|
||||
</cfquery>
|
||||
|
||||
<cfset permitted=(qACL.permission GE ATTRIBUTES.act)>
|
||||
<cfset permission=#qACL.permission#/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset permitted="Yes"/><!---***--->
|
||||
<cfrethrow/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.output)>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#", permitted)><!---boolean--->
|
||||
</cfif>
|
||||
<cfif len(ATTRIBUTES.permissionOut)>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.permissionOut#", permission)><!---integer 0 1 2--->
|
||||
</cfif>
|
||||
|
||||
<cfif NOT permitted><cfexit method="EXITTAG"></cfif>
|
||||
</cfif><!---thisTag.ExecutionMode is 'start'--->
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!---v0.2 19:01 06.10.2020
|
||||
---><cfparam name="ATTRIBUTES.contenttype"/>
|
||||
<cfswitch expression=#ATTRIBUTES.contenttype#>
|
||||
<cfcase value="text/plain"><img src="img/mimetype/text.png"/></cfcase>
|
||||
<cfcase value="text/html"><img src="img/mimetype/html.png"/></cfcase>
|
||||
<cfcase value="application/pdf"><img src="img/mimetype/pdf.png"/></cfcase>
|
||||
<cfcase value="image/gif,image/png,image/jpeg"><img src="img/mimetype/image.png"/></cfcase>
|
||||
<cfcase value="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"><img src="img/mimetype/excel.png"/></cfcase>
|
||||
<cfcase value="application/vnd.ms-powerpoint"><img src="img/mimetype/powerpoint.png"/></cfcase>
|
||||
<cfcase value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"><img src="img/mimetype/word.png"/></cfcase>
|
||||
<cfdefaultcase><img src="img/mimetype/document.png"/></cfdefaultcase>
|
||||
</cfswitch><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,23 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
<cfparam name="ATTRIBUTES.value" default="1"/>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"checked")>
|
||||
<cfset checked=!(ATTRIBUTES.checked EQ "0" OR ATTRIBUTES.checked EQ "")/>
|
||||
<cfelse>
|
||||
<cfset checked=!(ATTRIBUTES.container[ATTRIBUTES.name] EQ "0" OR ATTRIBUTES.container[ATTRIBUTES.name] EQ "")/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfset attributeList="name,value,checked,container,suffix,id,disabled"/>
|
||||
|
||||
</cfsilent><cfoutput><input type="checkbox" name="#ATTRIBUTES.name#"<!---
|
||||
---><cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#ATTRIBUTES.value#"<!---
|
||||
---><cfif checked> checked="checked"</cfif><!---
|
||||
---><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,30 @@
|
||||
<cfparam name="ATTRIBUTES.lang" type="string" default=""/>
|
||||
<cfparam name="request.language" type="string" default=""/>
|
||||
<cfif len(ATTRIBUTES.lang) AND #ATTRIBUTES.lang# NEQ #request.language#>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
|
||||
<!--- <cfloop collection=#CALLER# item="item">
|
||||
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||
</cfloop> --->
|
||||
|
||||
<cfassociate basetag="cf_table" datacollection="columns"/>
|
||||
<cfset attributeList=""/> <!--- *** сделать проброс атрибутов --->
|
||||
<cfparam name="thisTag.th" type="array" default=#[{content="",field=""}]#/><!---контейнер для данных вложенного элемента TH. Предусмотрен только один элемент массива, остальные игнорируются--->
|
||||
<!---<cfparam name="thisTag.td" type="array" default=#[{content="",field=""}]#/>--->
|
||||
<cfparam name="thisTag.td" type="array" default=#thisTag.th#/><!---контейнер для данных вложенного элемента TD. Предусмотрен только один элемент массива, остальные игнорируются--->
|
||||
<!---Зачем массив? - потому что массив вложенных тегов, а у нас считается только первый вложенный тег TD--->
|
||||
|
||||
<cfset ATTRIBUTES.td=#thisTag.td#/><!---поле, видное включающему тегу table в коллекции columns--->
|
||||
<cfset ATTRIBUTES.th=#thisTag.th#/><!---поле, видное включающему тегу table в коллекции columns--->
|
||||
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.field" default="#thisTag.td[1].field#"/><!---***---><!---настоящий атрибут - имя поля запроса (query) для ВЫВОДА, может задаваться как атрибут тега--->
|
||||
<cfparam name="ATTRIBUTES.formatter" type="function" default=#function (x) {return x;}#/>
|
||||
<cfparam name="ATTRIBUTES.sortable" type="boolean" default=true/>
|
||||
<cfparam name="ATTRIBUTES.visible" type="boolean" default=true/>
|
||||
<cfparam name="ATTRIBUTES.columnOrder" type="integer" default=0/>
|
||||
<!--- Желательное поведение сортировки колонок: для некоторых колонок указан порядок, на этом месте они и оказываются. Не указан - идут в порядке появления в коде. Если 2 колонки с одним порядком сортировки, первой выводится та, которая первая по тексту. --->
|
||||
</cfif>
|
||||
@@ -0,0 +1,46 @@
|
||||
<cfsilent>
|
||||
<cfimport prefix="c" taglib="."/>
|
||||
|
||||
|
||||
<!---*** идея включить титул в метаданные была не совсем пустой, но когда титул определяется в селекте, это не слишком удобно --->
|
||||
<cfassociate basetag="cf_grid_head" datacollection="headersArray"/>
|
||||
<cfset envelope=getBaseTagData('cf_grid_head',1)/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.title" type="string" default="#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].title#"/>
|
||||
<cfparam name="ATTRIBUTES.name" type="string"/><!--- Заголовок указвается на том поле, по которому будет сортировка, а не на том, которе отображается --->
|
||||
<cfparam name="ATTRIBUTES.arrows" type="boolean" default="Yes"/>
|
||||
|
||||
|
||||
|
||||
<cfset attributeList="name,arrows,title"/>
|
||||
|
||||
<!--- В данном случае реализован подход: дополнительные атрибуты передаются вниз.
|
||||
Другой вариант - создавать атрибут extra, в который писать суффикс QueryString.
|
||||
Первый подход, использованный здесь, позволяет иметь доступ к дополнительным атрибутам, но непонятно, зачем - ведь их имена все равно неизвестны.
|
||||
Очевидный минус этого подхода - возможный конфликт имен с именами существующих атрибутов --->
|
||||
<cfset extraAttributes=structNew()/>
|
||||
|
||||
<!--- дополнительные атрибуты родительского тега--->
|
||||
<cfif StructKeyExists(envelope, "extraAttributes") AND isStruct(envelope.extraAttributes)>
|
||||
<cfloop collection=#envelope.extraAttributes# item="attr">
|
||||
<cfif NOT listFindNoCase(attributeList, attr)><!--- проверка относительно локальных атрибутов, чтобы их не перетереть--->
|
||||
<cfset val="#structFind(envelope.extraAttributes, attr)#"/>
|
||||
<cfif IsSimpleValue(val)>
|
||||
<cfset structInsert(extraAttributes, attr, val)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||
<cfif IsSimpleValue(val)>
|
||||
<cfset structInsert(extraAttributes, attr, val, "Yes")/><!--- локальные атрибуты перетрут родительские --->
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
|
||||
</cfsilent><cfif thisTag.executionMode is "end"><cfoutput>#ATTRIBUTES.title#
|
||||
<cfif ATTRIBUTES.arrows><c:order_arrows sortArray=#envelope.ATTRIBUTES.sortArray# n=#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].ordinal# attributeCollection=#extraAttributes#/></cfif></cfoutput></cfif>
|
||||
@@ -0,0 +1,178 @@
|
||||
<!--- combobox
|
||||
v3.1 23.10.2020 support of list in multiple selection
|
||||
v3.0 12.08.2016 ATTRIBUTES.readonly
|
||||
v2.1 04.05.2016 ATTRIBUTES.disabled
|
||||
v2 с форматным выводом
|
||||
|
||||
---><cfprocessingdirective suppresswhitespace="true">
|
||||
<!---
|
||||
<cfparam name="ATTRIBUTES.query" type="query"/>
|
||||
<cfparam name="ATTRIBUTES.queryString" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.list" type="string"/>
|
||||
--->
|
||||
<cfparam name="ATTRIBUTES.DS" type="string" default="#request.DS#"/>
|
||||
<cfparam name="ATTRIBUTES.combo"/><!--- имя контрола (name)--->
|
||||
<cfparam name="ATTRIBUTES.key"/><!--- Имя ключевого поля---><!--- ограничение: не поддерживаем составные ключи --->
|
||||
<cfparam name="ATTRIBUTES.display" default=""/><!--- имя поля резалтсета --->
|
||||
<cfparam name="ATTRIBUTES.displayf" default=""/><!--- форматный вывод, поля резалтсета писать: ##field##, например "*##field1##:##field2##*" --->
|
||||
<cfparam name="ATTRIBUTES.selected" default=""/><!--- в случае base64 - завернуто в base64--->
|
||||
<cfparam name="ATTRIBUTES.base64" default="No"/><!---*** это архитектурная ошибка. Base64 is url unsafe. Но поскольку все передается POST-ом, обычно не проявляется --->
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.style_selector" default=""/>
|
||||
<cfparam name="ATTRIBUTES.style_spec" type="struct" default=#structNew()#/>
|
||||
<cfparam name="ATTRIBUTES.class_selector" default=""/>
|
||||
<cfparam name="ATTRIBUTES.class_spec" type="struct" default=#structNew()#/>
|
||||
<cfparam name="ATTRIBUTES.flag_disabled" type="string" default=""/><!--- проверяется на > 0. Возможно, не следует отключать выбранное значение --->
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="false"/><!--- в случае readonly выводится текст и скрытое поле с ID, если множественное выделение, то надо наверно выводить все выделенные элементы? --->
|
||||
<cfparam name="ATTRIBUTES.extra" type="string" default=""/><!--- --->
|
||||
|
||||
|
||||
<cfset attributeList="template,query,queryString,combo,key,display,displayf,selected,empty,DS,base64,disabled,style_selector,style_spec,class_selector,class_spec,flag_disabled,readonly,extra"/>
|
||||
|
||||
<cffunction name="escape64" returnType="string">
|
||||
<cfargument name="val" type="string">
|
||||
<cfif #ATTRIBUTES.base64#>
|
||||
<cfreturn ToBase64(val)>
|
||||
<cfelse>
|
||||
<cfreturn val>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="isSelected" returnType="boolean">
|
||||
<!--- tests for presense of key (second arg) in passed array (first arg) or, if sel is simple value just compares key and sel.
|
||||
Used for checking against multiple alternatives like "save" OR "saveAndClose"
|
||||
--->
|
||||
<cfargument name="sel">
|
||||
<cfargument name="key">
|
||||
<cfscript>
|
||||
if (isArray(sel)) {
|
||||
for(i=1; i <= ArrayLen(sel); i++){
|
||||
if (sel[i]==key) {return true;}
|
||||
}
|
||||
return false;
|
||||
} else { //считаем, что из сложных типов тут бывает только массив
|
||||
if (!isSimpleValue(sel)) {return false;}
|
||||
/*return (sel == key);*/
|
||||
return listFind(sel,key);
|
||||
}
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="strSelected" returnType="string">
|
||||
<cfargument name="sel">
|
||||
<cfargument name="key">
|
||||
<!---unsupported in CF8: <cfreturn (isSelected(sel,key)? " selected" : "")>--->
|
||||
<cfif isSelected(sel,key)>
|
||||
<cfreturn " selected">
|
||||
<cfelse>
|
||||
<cfreturn "">
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<!--- функция условного обертывания --->
|
||||
<cffunction name="w" returnType="string">
|
||||
<cfargument name="prefix"/>
|
||||
<cfargument name="input"/>
|
||||
<cfargument name="suffix" default=""/>
|
||||
|
||||
<cfif len(input) GT 0>
|
||||
<cfreturn "#prefix##input##suffix#"/>
|
||||
<cfelse>
|
||||
<cfreturn ""/>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="strAttribute" returnType="string">
|
||||
<!---returns formatted string ' attribute="value"',
|
||||
where attribute name is the first argument,
|
||||
and value is taken from passed structure(third argument) by key (second argument)--->
|
||||
<cfargument name="name"/>
|
||||
<cfargument name="selector"/>
|
||||
<cfargument name="spec" type="struct"/>
|
||||
|
||||
<cfif StructKeyExists(spec, selector)>
|
||||
<cfreturn ' #name#="#structFind(spec, selector)#"'/>
|
||||
<cfelse>
|
||||
<cfreturn "">
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<!--- ---------------------------------------------------------------------------------- --->
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"query")>
|
||||
<cfset qSel=ATTRIBUTES.query>
|
||||
<cfelseif structKeyExists(ATTRIBUTES,"queryString")>
|
||||
<cfquery name="qSel" datasource="#ATTRIBUTES.DS#">
|
||||
#preserveSingleQuotes(ATTRIBUTES.queryString)#
|
||||
</cfquery>
|
||||
<cfelseif structKeyExists(ATTRIBUTES,"list")>
|
||||
<cfset qSel=#queryNew("item")#/>
|
||||
<cfloop list=#ATTRIBUTES.list# item="item">
|
||||
<cfset queryAddRow(qSel,{"item":"#item#"})/>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
|
||||
<!--- parse class specifications--->
|
||||
<cfscript>
|
||||
</cfscript>
|
||||
|
||||
<cfif ATTRIBUTES.readonly>
|
||||
<cfif isArray(ATTRIBUTES.selected)>
|
||||
<cfset selectedList=ArrayToList(ATTRIBUTES.selected)/>
|
||||
<cfelse>
|
||||
<cfset selectedList=ATTRIBUTES.selected/>
|
||||
</cfif>
|
||||
|
||||
<cfquery name="qSelFiltered" dbtype="query">
|
||||
select * from qSel where <cfif len(selectedList)>#ATTRIBUTES.key# in (#selectedList#)<cfelse>1=0</cfif>
|
||||
</cfquery>
|
||||
|
||||
<cfoutput>
|
||||
<div class="textbox-disabled" style="display:inline-block;"<cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>>
|
||||
<cfif qSelFiltered.recordCount GT 0>
|
||||
<cfloop query="qSelFiltered"><!---
|
||||
---><cfset key=evaluate("#ATTRIBUTES.key#")/><!---
|
||||
---><cfset key=trim(escape64(key))/><!---
|
||||
---><div><input type="hidden" name="#ATTRIBUTES.combo#" value="#key#"/>#Evaluate(ATTRIBUTES.display)#<cfset Evaluate("WriteOutput('#ATTRIBUTES.displayf#')")/></div>
|
||||
</cfloop>
|
||||
<cfelseif isDefined("ATTRIBUTES.empty")>
|
||||
<input type="hidden" name="#ATTRIBUTES.combo#" value="#ATTRIBUTES.empty#"/><!-- #ATTRIBUTES.combo#: nothing selected -->
|
||||
|
||||
<cfelse>
|
||||
|
||||
</cfif>
|
||||
</div>
|
||||
</cfoutput>
|
||||
<cfelse><!--- ATTRIBUTES.readonly --->
|
||||
|
||||
<cfoutput>
|
||||
<select name="#ATTRIBUTES.combo#"<!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><cfif len(ATTRIBUTES.extra)>#ATTRIBUTES.extra#</cfif>>
|
||||
<cfif isDefined("ATTRIBUTES.empty")>
|
||||
<option value="#ATTRIBUTES.empty#"></option><!--- *** Надо было делать не пустое, а дефолтное значение, чтобы можно было ввести, например, {N/A, -1} --->
|
||||
</cfif>
|
||||
<cfloop query=qSel>
|
||||
<cfset key=evaluate("#ATTRIBUTES.key#")/><!--- в контексте запроса, поэтому находится в цикле! *** не самый лучший способ в смысле производительности --->
|
||||
<cfset key=trim(escape64(key))/>
|
||||
<cfset str_sel=strSelected(ATTRIBUTES.selected, key)/>
|
||||
<option value="#key#"#str_sel#<!---
|
||||
---><cfif len(ATTRIBUTES.style_selector) GT 0>#strAttribute("style",
|
||||
Evaluate(ATTRIBUTES.style_selector),ATTRIBUTES.style_spec)#</cfif><!---
|
||||
---><cfif len(ATTRIBUTES.class_selector) GT 0>#strAttribute("class", Evaluate(ATTRIBUTES.class_selector),ATTRIBUTES.class_spec)#</cfif><!---
|
||||
---><cfif len(ATTRIBUTES.flag_disabled) GT 0><cfif Evaluate(ATTRIBUTES.flag_disabled) GT 0> disabled="1"</cfif></cfif><!---
|
||||
--->>#Evaluate(ATTRIBUTES.display)#<cfset Evaluate("WriteOutput('#ATTRIBUTES.displayf#')")/></option>
|
||||
</cfloop>
|
||||
</select>
|
||||
</cfoutput>
|
||||
</cfif>
|
||||
|
||||
</cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset value=#ATTRIBUTES.value#/>
|
||||
<cfelse>
|
||||
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||
<cfparam name="ATTRIBUTES.format" default="YYYY-MM-DD"/>
|
||||
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.format)#"/>
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfset attributeList="name,value,container,suffix,id,format,size,disabled"/>
|
||||
|
||||
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#dateFormat(value, ATTRIBUTES.format)#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,29 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset value=#ATTRIBUTES.value#/>
|
||||
<cfelse>
|
||||
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||
<cfparam name="ATTRIBUTES.dformat" default="YYYY-MM-DD"/>
|
||||
<cfparam name="ATTRIBUTES.tformat" default="HH:MM"/>
|
||||
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.dformat)+len(ATTRIBUTES.tformat)+1#"/>
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.timeFirst" type="boolean" default="No"/>
|
||||
|
||||
<cfif ATTRIBUTES.timeFirst>
|
||||
<cfset value="#timeFormat(value, ATTRIBUTES.tformat)# #dateFormat(value, ATTRIBUTES.dformat)#"/>
|
||||
<cfelse>
|
||||
<cfset value="#dateFormat(value, ATTRIBUTES.dformat)# #timeFormat(value, ATTRIBUTES.tformat)#"/>
|
||||
</cfif>
|
||||
<cfset attributeList="name,value,container,suffix,id,dformat,tformat,size,disabled"/>
|
||||
|
||||
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#value#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!---moved to layout--->
|
||||
<!---<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||
<cfparam name="ATTRIBUTES.backUrl" type="string" default="."/>
|
||||
<cfparam name="ATTRIBUTES.enable" type="boolean" default="No"/>
|
||||
<cfoutput>
|
||||
<div class="row" style="margin:0px;">
|
||||
<div class="col-sm-3" style="padding: 0px;">
|
||||
<button type="submit" name="save" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить","Save")#</button>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding: 0px 5px;">
|
||||
<button type="submit" name="saveAndClose" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить и закрыть","Save and Close")#</button>
|
||||
</div>
|
||||
<div class="col-sm-3" style="padding: 0px;">
|
||||
<button type="button" name="close" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;" onClick="document.location.href='#ATTRIBUTES.backUrl#'">#request.i18("Закрыть","Close")#</button>
|
||||
</div>
|
||||
</div>
|
||||
</cfoutput>
|
||||
</cfif>--->
|
||||
@@ -0,0 +1,18 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.titleMap" default=#structNew()#/>
|
||||
<cfparam name="ATTRIBUTES.sortArray" default=#arrayNew(1)#/>
|
||||
|
||||
<cfset attributeList="titleMap,sortArray"/>
|
||||
|
||||
<cfset extraAttributes=structNew()/>
|
||||
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||
<cfif IsSimpleValue(val)>
|
||||
<cfset structInsert(extraAttributes, attr, val)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
</cfsilent><!---
|
||||
---><cfif thisTag.executionMode is "start"><tr><cfelseif thisTag.executionMode is "end"></tr></cfif>
|
||||
@@ -0,0 +1,11 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||
<cfif NOT ATTRIBUTES.canWrite>
|
||||
<cfexit method="exitTag"/>
|
||||
</cfif>
|
||||
<cfparam name="ATTRIBUTES.entity"/>
|
||||
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.extra" default=""/>
|
||||
<cfparam name="ATTRIBUTES.fwx"/>
|
||||
<cfparam name="ATTRIBUTES.title" default="создать"/>
|
||||
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=-1<cfif len(ATTRIBUTES.extra)>&#ATTRIBUTES.extra#</cfif>&#ATTRIBUTES.fwx#" title="#ATTRIBUTES.title#" class="add"></a></cfoutput><cfexit method="exitTag"/>
|
||||
@@ -0,0 +1,10 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||
<cfif NOT ATTRIBUTES.canWrite>
|
||||
<cfexit method="exitTag"/>
|
||||
</cfif>
|
||||
<cfparam name="ATTRIBUTES.entity"/>
|
||||
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.id"/>
|
||||
<cfparam name="ATTRIBUTES.fwx"/>
|
||||
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#_del.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" title="удалить" name="#ATTRIBUTES.id#" class="del"></a></cfoutput><cfexit method="exitTag"/>
|
||||
@@ -0,0 +1,9 @@
|
||||
<!---***deprecated. Use link_view_edit---><cfsilent>
|
||||
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.entity"/>
|
||||
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.id"/>
|
||||
<cfparam name="ATTRIBUTES.fwx"/>
|
||||
<cfparam name="ATTRIBUTES.title_edit" default="редактировать"/>
|
||||
<cfparam name="ATTRIBUTES.title_view" default="просмотреть"/>
|
||||
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" name="#ATTRIBUTES.id#" <cfif ATTRIBUTES.canWrite>title="#ATTRIBUTES.title_edit#" class="edit"<cfelse>title="#ATTRIBUTES.title_view#" class="view"</cfif>></a></cfoutput><cfexit method="exitTag"/>
|
||||
@@ -0,0 +1,6 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.entity"/>
|
||||
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.id"/>
|
||||
<cfparam name="ATTRIBUTES.fwx"/>
|
||||
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" title="просмотреть" name="#ATTRIBUTES.id#" class="view"></a></cfoutput><cfexit method="exitTag"/>
|
||||
@@ -0,0 +1,9 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.entity"/>
|
||||
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.id"/>
|
||||
<cfparam name="ATTRIBUTES.fwx"/>
|
||||
<cfparam name="ATTRIBUTES.title_edit" default="редактировать"/>
|
||||
<cfparam name="ATTRIBUTES.title_view" default="просмотреть"/>
|
||||
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" name="#ATTRIBUTES.id#" <cfif ATTRIBUTES.canWrite>title="#ATTRIBUTES.title_edit#" class="edit"<cfelse>title="#ATTRIBUTES.title_view#" class="view"</cfif>></a></cfoutput><cfexit method="exitTag"/>
|
||||
@@ -0,0 +1,25 @@
|
||||
<cfprocessingdirective suppressWhitespace="YES"><cfsilent>
|
||||
|
||||
<cfparam name="ATTRIBUTES.page" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.acl" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.label" type="string" default=""/>
|
||||
|
||||
<cfif structKeyExists(#ATTRIBUTES#,"external")>
|
||||
<cfset class="menu-item-ext"/>
|
||||
<cfset target=' target="_blank"'/>
|
||||
<cfelse>
|
||||
<cfset class="menu-item"/>
|
||||
<cfset target=''/>
|
||||
</cfif>
|
||||
|
||||
</cfsilent><!---
|
||||
---><cfmodule template="../ac.cfm" obj="#ATTRIBUTES.acl#"><cfif NOT (request.thisPage EQ ATTRIBUTES.page)><cfsilent>
|
||||
<cfif len(ATTRIBUTES.queryString)>
|
||||
<cfset pageUrl="#ATTRIBUTES.page#?#ATTRIBUTES.queryString#"/>
|
||||
<cfelse>
|
||||
<cfset pageUrl="#ATTRIBUTES.page#"/>
|
||||
</cfif>
|
||||
</cfsilent><cfoutput><li class="#class#"><a href="#pageUrl#"#target#>#ATTRIBUTES.label#</a></li></cfoutput>
|
||||
<cfelse><cfoutput><li class="selected">#ATTRIBUTES.label#</li></cfoutput></cfif><!---
|
||||
---></cfmodule></cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||
@@ -0,0 +1,6 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.targetid"/>
|
||||
<cfparam name="ATTRIBUTES.enabled" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.title" default="Сейчас"/>
|
||||
</cfsilent><cfoutput><button onclick="document.getElementById(#ATTRIBUTES.targetid#).value=""></</cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,36 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.n" type="numeric"/>
|
||||
<cfset attributeList="n,sortArray"/>
|
||||
|
||||
<cfset img_up="arr_up.png"/>
|
||||
<cfset img_dn="arr_dn.png"/>
|
||||
<cfset HIGHLIGHTED_CNT=2/>
|
||||
|
||||
<cfif isDefined("ATTRIBUTES.sortArray")>
|
||||
<cfset i=1>
|
||||
<cfloop condition="i LE HIGHLIGHTED_CNT AND i LE arrayLen(ATTRIBUTES.sortArray)">
|
||||
<cftry>
|
||||
<cfif ATTRIBUTES.sortArray[i].fld EQ ATTRIBUTES.n>
|
||||
<cfif ATTRIBUTES.sortArray[i].asc>
|
||||
<cfset img_up="arr_up#i#.png">
|
||||
<cfelse>
|
||||
<cfset img_dn="arr_dn#i#.png">
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfcatch type="Any"></cfcatch>
|
||||
</cftry>
|
||||
<cfset i=i+1>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset extra_query_string=""/>
|
||||
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||
<cfif NOT listFindNoCase(attributeList,attr)>
|
||||
<cfset val="#structFind(ATTRIBUTES,attr)#"/>
|
||||
<cfif IsSimpleValue(val)>
|
||||
<cfset extra_query_string="#extra_query_string#&#attr#=#val#"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
</cfsilent><cfoutput><a href="?sort=#ATTRIBUTES.n#a#extra_query_string#" class="noprint" style="padding:2px;" title="по возрастанию"><img src="img/#img_up#" alt="по возрастанию"></a><a href="?sort=#ATTRIBUTES.n#d#extra_query_string#" title="по убыванию"><img src="img/#img_dn#" alt="по убыванию"></a></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,80 @@
|
||||
<cfparam name="nStart" type="integer" default="1"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.recordCount" type="integer"/>
|
||||
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer"/>
|
||||
<cfparam name="ATTRIBUTES.thisPage" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.addtionalUrlParams" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.self" type="string" default=""/>
|
||||
<!---<cfparam name="ATTRIBUTES.fwx" type="string" default=""/>--->
|
||||
<cfparam name="ATTRIBUTES.output" type="string" default="paginator"/>
|
||||
|
||||
<!--- Исключение: рассчитанный индекс страницы превышает количество существующих страниц - надо возвращаться на первую --->
|
||||
|
||||
<cfset nPages = Int((ATTRIBUTES.recordCount-1)/ATTRIBUTES.recordsPerPage)+1/>
|
||||
<cfset nCurrentPage = Int((nStart-1)/ATTRIBUTES.recordsPerPage)+1/>
|
||||
<cfif nStart GT ATTRIBUTES.recordCount>
|
||||
<cfset nStart=1/>
|
||||
</cfif>
|
||||
|
||||
<cfset UrlSuffix=""/>
|
||||
<cfif len(ATTRIBUTES.addtionalUrlParams) GT 0>
|
||||
<cfset UrlSuffix="&#ATTRIBUTES.addtionalUrlParams#"/>
|
||||
</cfif>
|
||||
<cfif len(ATTRIBUTES.self) GT 0>
|
||||
<cfset UrlSuffix="&track=#ATTRIBUTES.self#"/>
|
||||
</cfif>
|
||||
|
||||
<cfset somePages=3/>
|
||||
|
||||
<cfsavecontent variable="pages">
|
||||
<cfif ATTRIBUTES.recordCount GT ATTRIBUTES.recordsPerPage>
|
||||
<cfset doMerge = false/>
|
||||
<cfloop index="i" from="1" to="#nPages#">
|
||||
<cfset nPageStartRecordNum=(i-1)*ATTRIBUTES.recordsPerPage+1>
|
||||
<!---
|
||||
схема, которая обеспечивает дихотомический поиск
|
||||
первые несколько страниц (не менее 2)
|
||||
если есть разрыв - многоточие
|
||||
посередине
|
||||
если есть разрыв - многоточие
|
||||
несколько страниц до текушей
|
||||
текущую без ссылки
|
||||
несколько страниц после текущей
|
||||
если есть разрыв - многоточие
|
||||
посередине
|
||||
если есть разрыв - многоточие
|
||||
последние несколько страниц
|
||||
--->
|
||||
<!---Можно сделать на многоточии ссылку на середину интервала!--->
|
||||
<!---Хочется добиться, чтобы при хождении вперед-назад значения повторялись--->
|
||||
<!---забавный эффект, когда остается разрыв в 1 страницу, а там все равно многоточие--->
|
||||
<cfoutput>
|
||||
<cfif i NEQ nCurrentPage>
|
||||
<cfif i GT somePages AND (nCurrentPage-i) GT somePages>
|
||||
<cfif NOT doMerge><!---*** неуклюже--->
|
||||
<cfset nMidPage = ceiling(nCurrentPage/2)/>
|
||||
<cfset nMidStartRecord = (nMidPage-1)*ATTRIBUTES.recordsPerPage+1/>
|
||||
<a href="#ATTRIBUTES.thisPage#?nStart=#nMidStartRecord##UrlSuffix#" title="#nMidPage#">...</a> <cfset doMerge=true/>
|
||||
</cfif>
|
||||
<cfelseif (i-nCurrentPage) GT somePages AND (nPages-i) GT somePages>
|
||||
<cfif NOT doMerge>
|
||||
<cfset nMidPage = ceiling((nCurrentPage+nPages)/2)/>
|
||||
<cfset nMidStartRecord = (nMidPage-1)*ATTRIBUTES.recordsPerPage+1/>
|
||||
<a href="#ATTRIBUTES.thisPage#?nStart=#nMidStartRecord##UrlSuffix#" title="#nMidPage#">...</a> <cfset doMerge=true/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<a href="#ATTRIBUTES.thisPage#?nStart=#nPageStartRecordNum##UrlSuffix#">#i#</a>
|
||||
<cfset doMerge=false/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<b>#i#</b>
|
||||
</cfif>
|
||||
</cfoutput>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
</cfsavecontent>
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.output#.links"=#pages#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.output#.recordsPerPage"=#ATTRIBUTES.recordsPerPage#/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
@@ -0,0 +1,154 @@
|
||||
<cfimport prefix="c" taglib="."/>
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<!--- <cfloop collection=#CALLER# item="item">
|
||||
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||
</cfloop> --->
|
||||
|
||||
<cfparam name="ATTRIBUTES.query" type="query"/>
|
||||
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer" default="500"/>
|
||||
<cfparam name="ATTRIBUTES.nStart" type="integer" default="1"/>
|
||||
<cfparam name="ATTRIBUTES.titleMap" type="struct" default=srtuctNew()/><!---for definition see: field_set.cfm--->
|
||||
<cfparam name="ATTRIBUTES.sortArray" type="array" default=arrayNew(1)/>
|
||||
|
||||
<cfset tableAttributeList="query,recordsPerPage,nStart,titleMap,sortArray"/>
|
||||
<cfset colAttributeList="lang,th,td,content,field,formatter,sortable,visible,columnOrder"/>
|
||||
<cfset tdAttributeList="content,field"/>
|
||||
|
||||
|
||||
<cfparam name="thisTag.columns" type="array"/>
|
||||
|
||||
|
||||
<cffunction name=reorderColumns output="No">
|
||||
<!--- Вариант сортировки. Вынимаем все элементы с указанным порядком в отдельную коллекцию,
|
||||
после чего вставляем их в оставшуюся коллекцию на указанные позиции.
|
||||
нет смысла делать вид, что мы реализуем что-то универсальное
|
||||
(поле для сортировки в совершенно конкретном поле структуры в массиве),
|
||||
поэтому ничего не передаем в аргументах, спокойно ссылаемся на окружение--->
|
||||
<cfset var tmpOrderedIndexes=ArrayNew(1)/>
|
||||
<cfset var tmpUnorderedIndexes=ArrayNew(1)/>
|
||||
|
||||
<cfloop from=1 to=#arrayLen(thisTag.columns)# index="i">
|
||||
<cfif thisTag.columns[i].columnOrder GT 0>
|
||||
<cfset arrayAppend(tmpOrderedIndexes,i)/>
|
||||
<cfelse>
|
||||
<cfset arrayAppend(tmpUnorderedIndexes,i)/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfloop from=#arrayLen(tmpOrderedIndexes)# to=1 step=-1 index="i">
|
||||
<!--- counting up-down preserves entry order for equal columnOrders --->
|
||||
<cfset var pos=max(min(thisTag.columns[tmpOrderedIndexes[i]].columnOrder, len(tmpUnorderedIndexes)+1),1)/>
|
||||
<cfset arrayInsertAt(tmpUnorderedIndexes,pos,tmpOrderedIndexes[i])/>
|
||||
</cfloop>
|
||||
|
||||
<cfset var sortedColumns=ArrayNew(1)/>
|
||||
<cfloop from=1 to=#arrayLen(tmpUnorderedIndexes)# index="i">
|
||||
<cfset sortedColumns[i]=thisTag.columns[tmpUnorderedIndexes[i]]/>
|
||||
</cfloop>
|
||||
|
||||
<cfreturn sortedColumns/>
|
||||
</cffunction>
|
||||
|
||||
<cfset sortedColumns=reorderColumns()/>
|
||||
|
||||
<cfset thisTag.generatedContent=""/>
|
||||
|
||||
<table<!--- passthrough additional attributes
|
||||
---><cfoutput><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(tableAttributeList,attr)<!--- AND isSimpleValue(structFind(ATTRIBUTES,attr))--->> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop></cfoutput>>
|
||||
<thead>
|
||||
<tr>
|
||||
<cfloop array=#sortedColumns# index="column">
|
||||
<cfset th=column.th[1]/>
|
||||
<th<!--- атрибуты колонки передаются в TH, хотя это выглядит не очень нужным --->
|
||||
<cfloop collection=#column# item="colAttr">
|
||||
<cfif NOT listFindNoCase(colAttributeList,colAttr)><cfoutput>#colAttr#="#structFind(column,colAttr)#" </cfoutput></cfif>
|
||||
</cfloop>>
|
||||
<cfif len(column.field) AND column.sortable>
|
||||
<c:order_arrows sortArray=#ATTRIBUTES.sortArray# n=#ATTRIBUTES.titleMap[column.field].ordinal#<!--- attributeCollection=#extraAttributes#--->/>
|
||||
</cfif>
|
||||
<cfif len(th.content)>
|
||||
<cfset Evaluate("WriteOutput('#th.content#')")/>
|
||||
<cfelseif len(column.field)>
|
||||
<cfif structKeyExists(ATTRIBUTES.titleMap[column.field],"title")>
|
||||
<cfoutput>#ATTRIBUTES.titleMap[column.field].title#</cfoutput>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</th>
|
||||
</cfloop>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<cfoutput query=#ATTRIBUTES.query# maxrows=#ATTRIBUTES.recordsPerPage# startrow=#ATTRIBUTES.nStart#>
|
||||
<tr>
|
||||
<cfloop array=#sortedColumns# index="column">
|
||||
<cfset td=column.td[1]/>
|
||||
<td<!--- Если нужно передать атрибут в TD, можно ввести тег <C:TD class="r"/> только как плейсхолдер для атрибутов, а поле будет получено от колонки --->
|
||||
<cfloop collection=#td# item="tdAttr">
|
||||
<cfif NOT listFindNoCase(tdAttributeList,tdAttr)>#tdAttr#="#structFind(td,tdAttr)#" </cfif>
|
||||
</cfloop>
|
||||
><cfif len(td.content)><cfset Evaluate("WriteOutput('#td.content#')")/><!---#td.content#--->
|
||||
<cfelseif len(td.field)>#ATTRIBUTES.query[td.field][currentRow]#
|
||||
<cfelseif len(column.field)>#column.formatter(ATTRIBUTES.query[column.field][currentRow])#
|
||||
</cfif></td>
|
||||
</cfloop>
|
||||
</tr>
|
||||
</cfoutput>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
<!--- <cfdump var=#variables#/> --->
|
||||
</cfif>
|
||||
<!--- <cfexit method="exitTag"/> --->
|
||||
|
||||
<cfscript>
|
||||
/**
|
||||
* Sorts an array of structures based on a key in the structures.
|
||||
*
|
||||
* @param aofS Array of structures.
|
||||
* @param key Key to sort by.
|
||||
* @param sortOrder Order to sort by, asc or desc.
|
||||
* @param sortType Text, textnocase, or numeric.
|
||||
* @param delim Delimiter used for temporary data storage. Must not exist in data. Defaults to a period.
|
||||
* @return Returns a sorted array.
|
||||
* @author Nathan Dintenfass (nathan@changemedia.com)
|
||||
* @version 1, December 10, 2001
|
||||
*/
|
||||
function arrayOfStructsSort(aOfS,key){
|
||||
//by default we'll use an ascending sort
|
||||
var sortOrder = "asc";
|
||||
//by default, we'll use a textnocase sort
|
||||
var sortType = "textnocase";
|
||||
//by default, use ascii character 30 as the delim
|
||||
var delim = ".";
|
||||
//make an array to hold the sort stuff
|
||||
var sortArray = arraynew(1);
|
||||
//make an array to return
|
||||
var returnArray = arraynew(1);
|
||||
//grab the number of elements in the array (used in the loops)
|
||||
var count = arrayLen(aOfS);
|
||||
//make a variable to use in the loop
|
||||
var ii = 1;
|
||||
//if there is a 3rd argument, set the sortOrder
|
||||
if(arraylen(arguments) GT 2)
|
||||
sortOrder = arguments[3];
|
||||
//if there is a 4th argument, set the sortType
|
||||
if(arraylen(arguments) GT 3)
|
||||
sortType = arguments[4];
|
||||
//if there is a 5th argument, set the delim
|
||||
if(arraylen(arguments) GT 4)
|
||||
delim = arguments[5];
|
||||
//loop over the array of structs, building the sortArray
|
||||
for(ii = 1; ii lte count; ii = ii + 1)
|
||||
sortArray[ii] = aOfS[ii][key] & delim & ii;
|
||||
//now sort the array
|
||||
arraySort(sortArray,sortType,sortOrder);
|
||||
//now build the return array
|
||||
for(ii = 1; ii lte count; ii = ii + 1)
|
||||
returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
|
||||
//return the array
|
||||
return returnArray;
|
||||
}
|
||||
</cfscript>
|
||||
@@ -0,0 +1,15 @@
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<!--- <cfloop collection=#CALLER# item="item">
|
||||
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||
</cfloop> --->
|
||||
|
||||
<cfassociate basetag="cf_column" datacollection="td"/>
|
||||
<cfset attributeList="field"/>
|
||||
<cfparam name="ATTRIBUTES.field" default=""/>
|
||||
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||
</cfif>
|
||||
<!---<cfparam name="ATTRIBUTES.lang" type="string" default=""/>
|
||||
<cfparam name="request.language" type="string" default=""/>
|
||||
<cfif len(ATTRIBUTES.lang) AND #ATTRIBUTES.lang# NEQ #request.language#>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>--->
|
||||
@@ -0,0 +1,20 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset value=#ATTRIBUTES.value#/>
|
||||
<cfelse>
|
||||
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||
<cfparam name="ATTRIBUTES.size" default=""/>
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfset attributeList="name,value,container,suffix,id,size,disabled"/>
|
||||
|
||||
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#value#"<cfif len(ATTRIBUTES.size)> size="#ATTRIBUTES.size#"</cfif><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,6 @@
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<cfassociate basetag="cf_column" datacollection="th"/>
|
||||
<cfset attributeList="field"/>
|
||||
<cfparam name="ATTRIBUTES.field" default=""/>
|
||||
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||
</cfif>
|
||||
@@ -0,0 +1,21 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset value=#ATTRIBUTES.value#/>
|
||||
<cfelse>
|
||||
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||
<cfparam name="ATTRIBUTES.format" default="HH:MM"/>
|
||||
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.format)#"/>
|
||||
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||
<cfset attributeList="name,value,container,suffix,id,format,size,disabled"/>
|
||||
|
||||
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#timeFormat(value, ATTRIBUTES.format)#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||
passthrough additional attributes
|
||||
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,401 @@
|
||||
<!---
|
||||
bean v0.96
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested with CF8 and Railo
|
||||
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||
2014-12-26 POST-GET read-pass-save
|
||||
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||
2015-10-02 поддержка составных ключей
|
||||
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||
2015-11-26 добавлен mode=skip
|
||||
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||
child tag: param
|
||||
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||
2024-09-05 added status.recordExists (*** при вставке принудительно устанавливаем в true, веря, что если insert прошел, то запись появилась)
|
||||
--->
|
||||
|
||||
<!---
|
||||
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||
--->
|
||||
|
||||
<cfif thisTag.ExecutionMode is 'end'>
|
||||
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.output" type="string"/><!--- имя переменной (структуры) области CALLER, в которую возвращаются данные. Обычно "d" --->
|
||||
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.status" type="string"/><!--- имя переменной (структуры) области CALLER, в которую возвращается статуc. Обычно "status" --->
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||
|
||||
<cfif ATTRIBUTES.mode EQ "skip">
|
||||
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip",false))/><!--- *** можно было не создавать компонент, он все равно не имеет логики, это map из нескольких полей. С другой стороны, хоть поля декларированы. Не очень удобно, что в отдельном файле --->
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfset validationOk="true" />
|
||||
<cfset errorState="false" />
|
||||
<cfset errorMessage="" />
|
||||
<cfset warningState="false" />
|
||||
<cfset warningMessage="" />
|
||||
<!---<cfset keyField=""/>
|
||||
<cfset keyValue=""/>--->
|
||||
<cfset hasAutoIncrement="false"/>
|
||||
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||
<cfset recordExists = "false"/>
|
||||
<cfset keyInvalid = "false"/>
|
||||
|
||||
<!--- разбор метаданных - поиск ключа --->
|
||||
<!--- поддерживаются только простые целочисленные ключи --->
|
||||
<cfset keys=arrayNew(1)/>
|
||||
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!---<cfdump var=#param#/>--->
|
||||
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||
<cfset key = structNew()/>
|
||||
<cfset ArrayAppend(keys, key)/>
|
||||
|
||||
<cfset key.name = param.param />
|
||||
<cfset key.field = param.field />
|
||||
<cfset key.type = param.type />
|
||||
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||
<cfset key.null = param.null />
|
||||
|
||||
<cfif param.errorState OR !param.hasInput>
|
||||
<cfset keyInvalid="true"/>
|
||||
<cfelse>
|
||||
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||
</cfif>
|
||||
|
||||
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||
<cfif structFind(param, "autoincrement")>
|
||||
<cfset hasAutoIncrement = true/>
|
||||
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<!---<cfdump var=#keys#/>--->
|
||||
|
||||
<cfif !keyInvalid>
|
||||
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||
select count(*) as cnt
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||
<cfelse>
|
||||
<cfset recordExists = "false"/>
|
||||
</cfif>
|
||||
|
||||
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||
<cfset processingMode=ATTRIBUTES.mode/>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<!--- ищем команду принудительного перечитывания --->
|
||||
<cfset doRead = false/>
|
||||
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doRead = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<!--- ищем команду сохранения --->
|
||||
<cfset doSave = false/>
|
||||
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doSave = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- ищем команду проброса --->
|
||||
<cfset doPass = false/>
|
||||
<cfif !doRead AND !doSave>
|
||||
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doPass = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- определяем режим --->
|
||||
<cfif doRead>
|
||||
<cfset processingMode="read"/>
|
||||
<cfelseif doSave>
|
||||
<cfset processingMode="save"/>
|
||||
<cfelse>
|
||||
<cfif doPass OR !recordExists>
|
||||
<cfset processingMode="pass"/>
|
||||
<cfelse>
|
||||
<cfset processingMode="read"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfset initialProcessingMode = processingMode/>
|
||||
|
||||
|
||||
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||
<!--- ---------------------------------------------------------- --->
|
||||
|
||||
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||
--->
|
||||
|
||||
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
|
||||
<cfif !param.key>
|
||||
<cfif param.warningState>
|
||||
<cfif NOT warningState><!--- catch the first fault --->
|
||||
<cfset warningMessage=param.warningMessage />
|
||||
</cfif>
|
||||
<cfset warningState="true" />
|
||||
</cfif>
|
||||
|
||||
<cfif param.errorState>
|
||||
<cfif NOT errorState><!--- catch the first fault --->
|
||||
<cfset errorMessage=param.errorMessage />
|
||||
</cfif>
|
||||
<cfset errorState="true" />
|
||||
<cfset validationOk="false"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif errorState>
|
||||
<!--- clear warning state --->
|
||||
<cfset warningMessage="" />
|
||||
<cfset warningState=false />
|
||||
<!--- finish lookup --->
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
|
||||
<cfif validationOk>
|
||||
<!---<cfdump var=#param#/> --->
|
||||
<cftry>
|
||||
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||
update #ATTRIBUTES.table# set
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- не обновляем ключевые поля --->
|
||||
<cfif NOT (StructFind(param, "key")
|
||||
OR StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipUpdate")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<!---<cfdump var=#qSaveResult#/>--->
|
||||
<cfelse><!--- insert --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||
insert into #ATTRIBUTES.table#
|
||||
(
|
||||
<cfset i=0 />
|
||||
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
values
|
||||
(
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
<!--- /*MSSQL syntax*/
|
||||
<cfif hasAutoIncrement>
|
||||
select scope_identity() as id;
|
||||
</cfif>
|
||||
--->
|
||||
<!---*** PostgreSQL incompatible hack follows--->
|
||||
<cfif hasAutoIncrement>
|
||||
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||
</cfif>;
|
||||
</cfquery>
|
||||
<!--- <cfdump var=#qSave#/> --->
|
||||
<cfif hasAutoIncrement>
|
||||
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||
</cfif>
|
||||
<!--- здесь можно установить recordExists=true, если твердо верить, что вставка прошла, если нет исключения --->
|
||||
<cfset recordExists=true/>
|
||||
</cfif>
|
||||
<!--- recordExists --->
|
||||
|
||||
|
||||
<cfset processingMode="read"/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset errorState="true" />
|
||||
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||
<cfset processingMode="pass" />
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfelse><!--- (not) validationOk --->
|
||||
<cfset processingMode="pass"/>
|
||||
</cfif><!--- validationOk --->
|
||||
|
||||
</cfif><!--- processingMode EQ "save" --->
|
||||
|
||||
<!--- экспортируем данные --->
|
||||
<cfswitch expression=#processingMode#>
|
||||
<cfcase value="read">
|
||||
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||
select
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset i=i+1/>
|
||||
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||
</cfloop>
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
/*#sqlType(key.type)#*/
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
|
||||
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- <cfdump var=#param#/>--->
|
||||
<cfif param.hasInput>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||
<cfelse>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode,recordExists))/>
|
||||
|
||||
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||
|
||||
<!--- quick and dirty --->
|
||||
<cffunction name="sqlType">
|
||||
<cfargument name="type"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="checkDataFormat">
|
||||
<cfargument name="type"/>
|
||||
<cfargument name="value"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- //quick and dirty
|
||||
function sqlType(type) {
|
||||
switch (type) {
|
||||
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||
default: return uCase("CF_SQL_#type#");
|
||||
}
|
||||
}
|
||||
|
||||
function checkDataFormat(type,value) {
|
||||
switch (type) {
|
||||
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||
default: return true;
|
||||
}
|
||||
} --->
|
||||
<cfscript>
|
||||
//readonly может транслироваться в pass!
|
||||
function translateMode(readonly) {
|
||||
if (readonly) {
|
||||
return "read";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</cfscript>
|
||||
@@ -0,0 +1,400 @@
|
||||
<!---
|
||||
bean v0.95
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested with CF8 and Railo
|
||||
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||
2014-12-26 POST-GET read-pass-save
|
||||
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||
2015-10-02 поддержка составных ключей
|
||||
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||
2015-11-26 добавлен mode=skip
|
||||
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||
child tag: param
|
||||
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||
--->
|
||||
|
||||
<!---
|
||||
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||
--->
|
||||
|
||||
<cfif thisTag.ExecutionMode is 'end'>
|
||||
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||
|
||||
<cfif ATTRIBUTES.mode EQ "skip">
|
||||
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfset validationOk="true" />
|
||||
<cfset errorState="false" />
|
||||
<cfset errorMessage="" />
|
||||
<cfset warningState="false" />
|
||||
<cfset warningMessage="" />
|
||||
<!---<cfset keyField=""/>
|
||||
<cfset keyValue=""/>--->
|
||||
<cfset hasAutoIncrement="false"/>
|
||||
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||
<cfset recordExists = "false"/>
|
||||
<cfset keyInvalid = "false"/>
|
||||
|
||||
<!--- разбор метаданных - поиск ключа --->
|
||||
<!--- поддерживаются только простые целочисленные ключи --->
|
||||
<cfset keys=arrayNew(1)/>
|
||||
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!---<cfdump var=#param#/>--->
|
||||
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||
<cfset key = structNew()/>
|
||||
<cfset ArrayAppend(keys, key)/>
|
||||
|
||||
<cfset key.name = param.param />
|
||||
<cfset key.field = param.field />
|
||||
<cfset key.type = param.type />
|
||||
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||
<cfset key.null = param.null />
|
||||
|
||||
<cfif param.errorState OR !param.hasInput>
|
||||
<cfset keyInvalid="true"/>
|
||||
<cfelse>
|
||||
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||
</cfif>
|
||||
|
||||
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||
<cfif structFind(param, "autoincrement")>
|
||||
<cfset hasAutoIncrement = true/>
|
||||
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<!---<cfdump var=#keys#/>--->
|
||||
|
||||
<cfif !keyInvalid>
|
||||
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||
select count(*) as cnt
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||
<cfelse>
|
||||
<cfset recordExists = "false"/>
|
||||
</cfif>
|
||||
|
||||
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||
<cfset processingMode=ATTRIBUTES.mode/>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<!--- ищем команду принудительного перечитывания --->
|
||||
<cfset doRead = false/>
|
||||
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doRead = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<!--- ищем команду сохранения --->
|
||||
<cfset doSave = false/>
|
||||
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doSave = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- ищем команду проброса --->
|
||||
<cfset doPass = false/>
|
||||
<cfif !doRead AND !doSave>
|
||||
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doPass = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- определяем режим --->
|
||||
<cfif doRead>
|
||||
<cfset processingMode="read"/>
|
||||
<cfelseif doSave>
|
||||
<cfset processingMode="save"/>
|
||||
<cfelse>
|
||||
<cfif doPass OR !recordExists>
|
||||
<cfset processingMode="pass"/>
|
||||
<cfelse>
|
||||
<cfset processingMode="read"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfset initialProcessingMode = processingMode/>
|
||||
|
||||
|
||||
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||
<!--- ---------------------------------------------------------- --->
|
||||
|
||||
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||
--->
|
||||
|
||||
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
|
||||
<cfif !param.key>
|
||||
<cfif param.warningState>
|
||||
<cfif NOT warningState><!--- catch the first fault --->
|
||||
<cfset warningMessage=param.warningMessage />
|
||||
</cfif>
|
||||
<cfset warningState="true" />
|
||||
</cfif>
|
||||
|
||||
<cfif param.errorState>
|
||||
<cfif NOT errorState><!--- catch the first fault --->
|
||||
<cfset errorMessage=param.errorMessage />
|
||||
</cfif>
|
||||
<cfset errorState="true" />
|
||||
<cfset validationOk="false"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif errorState>
|
||||
<!--- clear warning state --->
|
||||
<cfset warningMessage="" />
|
||||
<cfset warningState=false />
|
||||
<!--- finish lookup --->
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
|
||||
<cfif validationOk>
|
||||
<!---<cfdump var=#param#/> --->
|
||||
<cftry>
|
||||
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||
update #ATTRIBUTES.table# set
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- не обновляем ключевые поля --->
|
||||
<cfif NOT (StructFind(param, "key")
|
||||
OR StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipUpdate")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<!---<cfdump var=#qSaveResult#/>--->
|
||||
<cfelse><!--- insert --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||
insert into #ATTRIBUTES.table#
|
||||
(
|
||||
<cfset i=0 />
|
||||
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
values
|
||||
(
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
);
|
||||
<!---
|
||||
<cfif hasAutoIncrement>
|
||||
select scope_identity() as id;
|
||||
</cfif>
|
||||
--->
|
||||
<!---
|
||||
*** PostgreSQL incompatible hack
|
||||
--->
|
||||
<cfif hasAutoIncrement>
|
||||
select LASTVAL() as id;
|
||||
</cfif>
|
||||
</cfquery>
|
||||
|
||||
<cfif hasAutoIncrement>
|
||||
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- recordExists --->
|
||||
|
||||
|
||||
<cfset processingMode="read"/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset errorState="true" />
|
||||
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||
<cfset processingMode="pass" />
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfelse><!--- (not) validationOk --->
|
||||
<cfset processingMode="pass"/>
|
||||
</cfif><!--- validationOk --->
|
||||
|
||||
</cfif><!--- processingMode EQ "save" --->
|
||||
|
||||
<!--- экспортируем данные --->
|
||||
<cfswitch expression=#processingMode#>
|
||||
<cfcase value="read">
|
||||
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||
select
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset i=i+1/>
|
||||
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||
</cfloop>
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
/*#sqlType(key.type)#*/
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
|
||||
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- <cfdump var=#param#/>--->
|
||||
<cfif param.hasInput>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||
<cfelse>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||
|
||||
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||
|
||||
<!--- quick and dirty --->
|
||||
<cffunction name="sqlType">
|
||||
<cfargument name="type"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="checkDataFormat">
|
||||
<cfargument name="type"/>
|
||||
<cfargument name="value"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- //quick and dirty
|
||||
function sqlType(type) {
|
||||
switch (type) {
|
||||
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||
default: return uCase("CF_SQL_#type#");
|
||||
}
|
||||
}
|
||||
|
||||
function checkDataFormat(type,value) {
|
||||
switch (type) {
|
||||
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||
default: return true;
|
||||
}
|
||||
} --->
|
||||
<cfscript>
|
||||
//readonly может транслироваться в pass!
|
||||
function translateMode(readonly) {
|
||||
if (readonly) {
|
||||
return "read";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</cfscript>
|
||||
@@ -0,0 +1,398 @@
|
||||
<!---
|
||||
bean v0.95
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested with CF8 and Railo
|
||||
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||
2014-12-26 POST-GET read-pass-save
|
||||
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||
2015-10-02 поддержка составных ключей
|
||||
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||
2015-11-26 добавлен mode=skip
|
||||
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||
child tag: param
|
||||
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||
--->
|
||||
|
||||
<!---
|
||||
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||
--->
|
||||
|
||||
<cfif thisTag.ExecutionMode is 'end'>
|
||||
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||
|
||||
<cfif ATTRIBUTES.mode EQ "skip">
|
||||
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfset validationOk="true" />
|
||||
<cfset errorState="false" />
|
||||
<cfset errorMessage="" />
|
||||
<cfset warningState="false" />
|
||||
<cfset warningMessage="" />
|
||||
<!---<cfset keyField=""/>
|
||||
<cfset keyValue=""/>--->
|
||||
<cfset hasAutoIncrement="false"/>
|
||||
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||
<cfset recordExists = "false"/>
|
||||
<cfset keyInvalid = "false"/>
|
||||
|
||||
<!--- разбор метаданных - поиск ключа --->
|
||||
<!--- поддерживаются только простые целочисленные ключи --->
|
||||
<cfset keys=arrayNew(1)/>
|
||||
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!---<cfdump var=#param#/>--->
|
||||
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||
<cfset key = structNew()/>
|
||||
<cfset ArrayAppend(keys, key)/>
|
||||
|
||||
<cfset key.name = param.param />
|
||||
<cfset key.field = param.field />
|
||||
<cfset key.type = param.type />
|
||||
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||
<cfset key.null = param.null />
|
||||
|
||||
<cfif param.errorState OR !param.hasInput>
|
||||
<cfset keyInvalid="true"/>
|
||||
<cfelse>
|
||||
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||
</cfif>
|
||||
|
||||
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||
<cfif structFind(param, "autoincrement")>
|
||||
<cfset hasAutoIncrement = true/>
|
||||
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<!---<cfdump var=#keys#/>--->
|
||||
|
||||
<cfif !keyInvalid>
|
||||
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||
select count(*) as cnt
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||
<cfelse>
|
||||
<cfset recordExists = "false"/>
|
||||
</cfif>
|
||||
|
||||
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||
<cfset processingMode=ATTRIBUTES.mode/>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<!--- ищем команду принудительного перечитывания --->
|
||||
<cfset doRead = false/>
|
||||
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doRead = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<!--- ищем команду сохранения --->
|
||||
<cfset doSave = false/>
|
||||
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doSave = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- ищем команду проброса --->
|
||||
<cfset doPass = false/>
|
||||
<cfif !doRead AND !doSave>
|
||||
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doPass = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- определяем режим --->
|
||||
<cfif doRead>
|
||||
<cfset processingMode="read"/>
|
||||
<cfelseif doSave>
|
||||
<cfset processingMode="save"/>
|
||||
<cfelse>
|
||||
<cfif doPass OR !recordExists>
|
||||
<cfset processingMode="pass"/>
|
||||
<cfelse>
|
||||
<cfset processingMode="read"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfset initialProcessingMode = processingMode/>
|
||||
|
||||
|
||||
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||
<!--- ---------------------------------------------------------- --->
|
||||
|
||||
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||
--->
|
||||
|
||||
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
|
||||
<cfif !param.key>
|
||||
<cfif param.warningState>
|
||||
<cfif NOT warningState><!--- catch the first fault --->
|
||||
<cfset warningMessage=param.warningMessage />
|
||||
</cfif>
|
||||
<cfset warningState="true" />
|
||||
</cfif>
|
||||
|
||||
<cfif param.errorState>
|
||||
<cfif NOT errorState><!--- catch the first fault --->
|
||||
<cfset errorMessage=param.errorMessage />
|
||||
</cfif>
|
||||
<cfset errorState="true" />
|
||||
<cfset validationOk="false"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif errorState>
|
||||
<!--- clear warning state --->
|
||||
<cfset warningMessage="" />
|
||||
<cfset warningState=false />
|
||||
<!--- finish lookup --->
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
|
||||
<cfif validationOk>
|
||||
<!---<cfdump var=#param#/> --->
|
||||
<cftry>
|
||||
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||
update #ATTRIBUTES.table# set
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- не обновляем ключевые поля --->
|
||||
<cfif NOT (StructFind(param, "key")
|
||||
OR StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipUpdate")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<!---<cfdump var=#qSaveResult#/>--->
|
||||
<cfelse><!--- insert --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||
insert into #ATTRIBUTES.table#
|
||||
(
|
||||
<cfset i=0 />
|
||||
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
values
|
||||
(
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
<!---
|
||||
<cfif hasAutoIncrement>
|
||||
select scope_identity() as id;
|
||||
</cfif>
|
||||
--->
|
||||
<!---*** PostgreSQL incompatible hack follows--->
|
||||
<cfif hasAutoIncrement>
|
||||
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||
</cfif>;
|
||||
</cfquery>
|
||||
<!--- <cfdump var=#qSave#/> --->
|
||||
<cfif hasAutoIncrement>
|
||||
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- recordExists --->
|
||||
|
||||
|
||||
<cfset processingMode="read"/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset errorState="true" />
|
||||
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||
<cfset processingMode="pass" />
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfelse><!--- (not) validationOk --->
|
||||
<cfset processingMode="pass"/>
|
||||
</cfif><!--- validationOk --->
|
||||
|
||||
</cfif><!--- processingMode EQ "save" --->
|
||||
|
||||
<!--- экспортируем данные --->
|
||||
<cfswitch expression=#processingMode#>
|
||||
<cfcase value="read">
|
||||
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||
select
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset i=i+1/>
|
||||
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||
</cfloop>
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
/*#sqlType(key.type)#*/
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
|
||||
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- <cfdump var=#param#/>--->
|
||||
<cfif param.hasInput>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||
<cfelse>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||
|
||||
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||
|
||||
<!--- quick and dirty --->
|
||||
<cffunction name="sqlType">
|
||||
<cfargument name="type"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="checkDataFormat">
|
||||
<cfargument name="type"/>
|
||||
<cfargument name="value"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- //quick and dirty
|
||||
function sqlType(type) {
|
||||
switch (type) {
|
||||
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||
default: return uCase("CF_SQL_#type#");
|
||||
}
|
||||
}
|
||||
|
||||
function checkDataFormat(type,value) {
|
||||
switch (type) {
|
||||
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||
default: return true;
|
||||
}
|
||||
} --->
|
||||
<cfscript>
|
||||
//readonly может транслироваться в pass!
|
||||
function translateMode(readonly) {
|
||||
if (readonly) {
|
||||
return "read";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</cfscript>
|
||||
@@ -0,0 +1,398 @@
|
||||
<!---
|
||||
bean v0.95
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested with CF8 and Railo
|
||||
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||
2014-12-26 POST-GET read-pass-save
|
||||
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||
2015-10-02 поддержка составных ключей
|
||||
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||
2015-11-26 добавлен mode=skip
|
||||
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||
child tag: param
|
||||
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||
--->
|
||||
|
||||
<!---
|
||||
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||
--->
|
||||
|
||||
<cfif thisTag.ExecutionMode is 'end'>
|
||||
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||
|
||||
<cfif ATTRIBUTES.mode EQ "skip">
|
||||
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfset validationOk="true" />
|
||||
<cfset errorState="false" />
|
||||
<cfset errorMessage="" />
|
||||
<cfset warningState="false" />
|
||||
<cfset warningMessage="" />
|
||||
<!---<cfset keyField=""/>
|
||||
<cfset keyValue=""/>--->
|
||||
<cfset hasAutoIncrement="false"/>
|
||||
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||
<cfset recordExists = "false"/>
|
||||
<cfset keyInvalid = "false"/>
|
||||
|
||||
<!--- разбор метаданных - поиск ключа --->
|
||||
<!--- поддерживаются только простые целочисленные ключи --->
|
||||
<cfset keys=arrayNew(1)/>
|
||||
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!---<cfdump var=#param#/>--->
|
||||
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||
<cfset key = structNew()/>
|
||||
<cfset ArrayAppend(keys, key)/>
|
||||
|
||||
<cfset key.name = param.param />
|
||||
<cfset key.field = param.field />
|
||||
<cfset key.type = param.type />
|
||||
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||
<cfset key.null = param.null />
|
||||
|
||||
<cfif param.errorState OR !param.hasInput>
|
||||
<cfset keyInvalid="true"/>
|
||||
<cfelse>
|
||||
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||
</cfif>
|
||||
|
||||
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||
<cfif structFind(param, "autoincrement")>
|
||||
<cfset hasAutoIncrement = true/>
|
||||
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<!---<cfdump var=#keys#/>--->
|
||||
|
||||
<cfif !keyInvalid>
|
||||
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||
select count(*) as cnt
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<!--- <cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop> --->
|
||||
</cfquery>
|
||||
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||
<cfelse>
|
||||
<cfset recordExists = "false"/>
|
||||
</cfif>
|
||||
|
||||
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||
<cfset processingMode=ATTRIBUTES.mode/>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<!--- ищем команду принудительного перечитывания --->
|
||||
<cfset doRead = false/>
|
||||
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doRead = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<!--- ищем команду сохранения --->
|
||||
<cfset doSave = false/>
|
||||
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doSave = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- ищем команду проброса --->
|
||||
<cfset doPass = false/>
|
||||
<cfif !doRead AND !doSave>
|
||||
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<cfset doPass = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<!--- определяем режим --->
|
||||
<cfif doRead>
|
||||
<cfset processingMode="read"/>
|
||||
<cfelseif doSave>
|
||||
<cfset processingMode="save"/>
|
||||
<cfelse>
|
||||
<cfif doPass OR !recordExists>
|
||||
<cfset processingMode="pass"/>
|
||||
<cfelse>
|
||||
<cfset processingMode="read"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfset initialProcessingMode = processingMode/>
|
||||
|
||||
|
||||
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||
<!--- ---------------------------------------------------------- --->
|
||||
|
||||
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||
--->
|
||||
|
||||
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
|
||||
<cfif !param.key>
|
||||
<cfif param.warningState>
|
||||
<cfif NOT warningState><!--- catch the first fault --->
|
||||
<cfset warningMessage=param.warningMessage />
|
||||
</cfif>
|
||||
<cfset warningState="true" />
|
||||
</cfif>
|
||||
|
||||
<cfif param.errorState>
|
||||
<cfif NOT errorState><!--- catch the first fault --->
|
||||
<cfset errorMessage=param.errorMessage />
|
||||
</cfif>
|
||||
<cfset errorState="true" />
|
||||
<cfset validationOk="false"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif errorState>
|
||||
<!--- clear warning state --->
|
||||
<cfset warningMessage="" />
|
||||
<cfset warningState=false />
|
||||
<!--- finish lookup --->
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
|
||||
<cfif validationOk>
|
||||
<!---<cfdump var=#param#/> --->
|
||||
<cftry>
|
||||
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||
update #ATTRIBUTES.table# set
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- не обновляем ключевые поля --->
|
||||
<cfif NOT (StructFind(param, "key")
|
||||
OR StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipUpdate")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<!---<cfdump var=#qSaveResult#/>--->
|
||||
<cfelse><!--- insert --->
|
||||
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||
insert into #ATTRIBUTES.table#
|
||||
(
|
||||
<cfset i=0 />
|
||||
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
#param.field#
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
values
|
||||
(
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfif NOT (StructFind(param, "autoincrement")
|
||||
OR StructFind(param, "readonly")
|
||||
OR StructFind(param, "skipInsert")
|
||||
)><cfif i++ GT 0>,</cfif>
|
||||
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
)
|
||||
<!---
|
||||
<cfif hasAutoIncrement>
|
||||
select scope_identity() as id;
|
||||
</cfif>
|
||||
--->
|
||||
<!---*** PostgreSQL incompatible hack follows--->
|
||||
<cfif hasAutoIncrement>
|
||||
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||
</cfif>;
|
||||
</cfquery>
|
||||
<!--- <cfdump var=#qSave#/> --->
|
||||
<cfif hasAutoIncrement>
|
||||
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- recordExists --->
|
||||
|
||||
|
||||
<cfset processingMode="read"/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset errorState="true" />
|
||||
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||
<cfset processingMode="pass" />
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfelse><!--- (not) validationOk --->
|
||||
<cfset processingMode="pass"/>
|
||||
</cfif><!--- validationOk --->
|
||||
|
||||
</cfif><!--- processingMode EQ "save" --->
|
||||
|
||||
<!--- экспортируем данные --->
|
||||
<cfswitch expression=#processingMode#>
|
||||
<cfcase value="read">
|
||||
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||
select
|
||||
<cfset i=0 />
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset i=i+1/>
|
||||
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||
</cfloop>
|
||||
from #ATTRIBUTES.table#
|
||||
where 1=1
|
||||
<cfloop array=#keys# index="key">
|
||||
/*#sqlType(key.type)#*/
|
||||
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
|
||||
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<!--- <cfdump var=#param#/>--->
|
||||
<cfif param.hasInput>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||
<cfelse>
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||
<cfloop array=#thistag.params# index="param">
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||
|
||||
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||
|
||||
<!--- quick and dirty --->
|
||||
<cffunction name="sqlType">
|
||||
<cfargument name="type"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="checkDataFormat">
|
||||
<cfargument name="type"/>
|
||||
<cfargument name="value"/>
|
||||
|
||||
<cfswitch expression=#type#>
|
||||
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- //quick and dirty
|
||||
function sqlType(type) {
|
||||
switch (type) {
|
||||
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||
default: return uCase("CF_SQL_#type#");
|
||||
}
|
||||
}
|
||||
|
||||
function checkDataFormat(type,value) {
|
||||
switch (type) {
|
||||
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||
default: return true;
|
||||
}
|
||||
} --->
|
||||
<cfscript>
|
||||
//readonly может транслироваться в pass!
|
||||
function translateMode(readonly) {
|
||||
if (readonly) {
|
||||
return "read";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</cfscript>
|
||||
@@ -0,0 +1,24 @@
|
||||
<cfsilent>
|
||||
<!--- 20181209 v0.3 --->
|
||||
<!---v0.3 добавлен атрибут cfSqlType--->
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||
<cfparam name="ATTRIBUTES.cfSqlType" default="CF_SQL_VARCHAR"/>
|
||||
|
||||
<cfif ATTRIBUTES.expression IS "">
|
||||
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||
</cfif>
|
||||
|
||||
<cfif ATTRIBUTES.name IS "">
|
||||
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||
<cfif arrayLen(matches.pos) GT 1>
|
||||
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,41 @@
|
||||
<cfsilent>
|
||||
<!--- 20181209 v0.3 --->
|
||||
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
|
||||
<!---v0.3 добавлено поле cfSqlType--->
|
||||
<cfif thisTag.executionMode is "end">
|
||||
|
||||
<cfparam name="ATTRIBUTES.titleMapOut"/>
|
||||
<cfparam name="ATTRIBUTES.lengthOut"/>
|
||||
<cfparam name="ATTRIBUTES.listOut" default=""/>
|
||||
|
||||
<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
|
||||
<cfset titleMap=structNew()/>
|
||||
<cfset list=""/>
|
||||
<cfset i=0/>
|
||||
<cfloop array=#thisTag.fieldsArray# index="field">
|
||||
<cfset i=i+1/>
|
||||
<cfset list=listAppend(#list#, #field.expression#)/>
|
||||
<cfif len(#field.name#)>
|
||||
<cfset structInsert(#titleMap#, #field.name#, structNew())/>
|
||||
<cfset "titleMap.#field.name#.ordinal"=#i#/>
|
||||
<cfset "titleMap.#field.name#.title"=#field.title#/>
|
||||
<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfif len(ATTRIBUTES.lengthOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.lengthOut#"=#arrayLen(thisTag.fieldsArray)#/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.titleMapOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.listOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.listOut#"=preserveSingleQuotes(list)/>
|
||||
<cfelse>
|
||||
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,305 @@
|
||||
<!---
|
||||
bean (child tag)
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested on CF8 and Railo
|
||||
2014-01-20 v0.3 added exception for unsupported parameter type
|
||||
2014-10-24 v0.5 ATTRIBUTES.field became default for ATTRIBUTES.param
|
||||
2014-11-18 v0.6 rework
|
||||
2015-07-21 v0.7 EXITTAG moved to the end of file
|
||||
2015-07-22 v0.8 Changed protocol for skipInsert, skipUpdate, key, readonly, autoincrement, required attributes, now we can set value at runtime like skipInsert="#boolean#" instead of just including or omitting the attribute
|
||||
2015-12-02 v0.9 Added preprocessor attribute. Fixed timestamp bug ('01.12.2015' treated as 12 JAN 2015)
|
||||
2015-12-04 v0.12 for dates, submitted value is now back-overwritten
|
||||
2015-12-04 v0.13 bug fix
|
||||
v0.94
|
||||
2017-03-10 v0.15 - scope resolution order changed to CALLER-FORM-URL
|
||||
2018-05-31 v0.16 - strip whitespace from numbers
|
||||
2019-02-09 v0.17 - call preprocessor before checking required field
|
||||
2022-01-06 v0.18 - added BIT type (for PostgreSQL) - в базе представляется boolean!
|
||||
2023-04-17 v0.19 - форматирование, добавлен other к idstamp (для postgres)
|
||||
2023-04-18 v0.20 - добавлен атрибут format, для даты
|
||||
2024-03-22 v0.21 - добавлены синонимы int,boolean,bool. Перенумерованы версии 91->11
|
||||
--->
|
||||
|
||||
<!--- проверка на неподдерживаемые поля --->
|
||||
<cfloop collection=#ATTRIBUTES# item="a">
|
||||
<cfif NOT ListFindNoCase("param,field,type,displayName,default,init,size,scale,skipInsert,skipUpdate,key,readonly,autoincrement,value,null,forNull,eval,required,preprocessor,dtlocale,format", #a#)>
|
||||
<cfthrow type="UnsupportedAttribute" message='Attribute #a# is unsupported by bean/param' detail='Specified attribute "#a#" is not supported by custom tag bean/param'/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfparam name="ATTRIBUTES.default" default=""/><!--- подставляется при сохранении, если параметр не передавался. Важно для чекбоксов, они не передаются, если не отмечены --->
|
||||
<cfparam name="ATTRIBUTES.init" default=#ATTRIBUTES.default#/><!--- передается в форму для новой записи (предзаполнение). Для чекбокса может быть init=1, default=0 - отмеченный по умолчанию --->
|
||||
<cfparam name="ATTRIBUTES.field"/><!--- имя поля в таблице БД --->
|
||||
<cfparam name="ATTRIBUTES.param" default="#ATTRIBUTES.field#"/><!--- имя поля в URL/FORM и экспортируемой переменной --->
|
||||
<cfparam name="ATTRIBUTES.type" default="varchar"/><!--- тип поля в терминах SQL (CF_SQL_*) --->
|
||||
<cfparam name="ATTRIBUTES.displayName" default=#ATTRIBUTES.param#/><!--- для сообщений об ошибках--->
|
||||
<cfparam name="ATTRIBUTES.size" default="0"/><!--- for strings. 0=unlimited --->
|
||||
<cfparam name="ATTRIBUTES.scale" default="1"/> <!--- Множитель. Только для чисел, только при записи. --->
|
||||
<!--- если атрибут записан без значения, он получает значение true! --->
|
||||
<cfparam name="ATTRIBUTES.skipInsert" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.skipUpdate" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.key" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.autoincrement" type="boolean" default="false"/><!--- правильнее autogenerated, потому что GUID тоже бывает генеренным --->
|
||||
<cfparam name="ATTRIBUTES.required" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.dtlocale" type="string" default="ru"/><!--- datetime locale--->
|
||||
<cfparam name="ATTRIBUTES.format" type="string" default=""/><!--- datetime format, normally yyyy-MM-dd, CASE SENSITIVE, NOT YYYY-DD-MM!!!--->
|
||||
|
||||
<!--- ATTRIBUTES.forNull ---><!--- если forNull не задано, параметр считается обязательным ***?--->
|
||||
<!--- ATTRIBUTES.preprocessor ---><!--- функция --->
|
||||
|
||||
<!--- in-out --->
|
||||
<cfparam name="ATTRIBUTES.null" type="boolean" default="false"/>
|
||||
<!--- ATTRIBUTES.value - входной-выходной параметр. --->
|
||||
<!---/in-out --->
|
||||
|
||||
<!--- *** выходные параметры. Странно выглядит. Хоть как-то в имени это отразить, что выходные... --->
|
||||
<cfparam name="ATTRIBUTES.hasInput" type="boolean" default="false" />
|
||||
<cfparam name="ATTRIBUTES.submittedValue" type="string" default=""/><!--- необработанное входное значение --->
|
||||
<cfparam name="ATTRIBUTES.errorState" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.errorMessage" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.warningState" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.warningMessage" type="string" default=""/>
|
||||
|
||||
<cfassociate basetag="cf_bean" datacollection="params" />
|
||||
|
||||
<!--- Поле "только для чтения" не обрабатываем никак, если только оно не ключевое. Зачем может понадобиться readonly ключевое поле - непонятно, но вдруг --->
|
||||
<cfif NOT (ATTRIBUTES.key)>
|
||||
<cfif (ATTRIBUTES.readonly)>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- Если атрибуту явно указан NULL, дальше ничего не делаем --->
|
||||
<cfif ATTRIBUTES.null>
|
||||
<cfset ATTRIBUTES.value="0"/><!--- значение не используется, но переменная должна быть определена --->
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
|
||||
<!--- принимаем значение из передаваемых данных или подставляем значение по умолчанию --->
|
||||
<!--- можно было бы попробовать cfparam CALLER.#ATTRIBUTES.param# и обрабатывать исключение, но так обходимся без исключений, хотя и не изящно --->
|
||||
<cfif StructKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset submittedValue=ATTRIBUTES.value/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(CALLER, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(CALLER, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(FORM, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(FORM, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(URL, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(URL, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
|
||||
<cfelse>
|
||||
<!--- если параметр отсутствует в передаваемых данных --->
|
||||
<cfset submittedValue=ATTRIBUTES.default />
|
||||
</cfif>
|
||||
|
||||
<cfif !StructKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset ATTRIBUTES.value="" /><!---*** dirty fix--->
|
||||
</cfif>
|
||||
|
||||
<cfset ATTRIBUTES.submittedValue=submittedValue />
|
||||
|
||||
|
||||
<cfif isDefined("ATTRIBUTES.preprocessor")>
|
||||
<cfset submittedValue=#ATTRIBUTES.preprocessor(submittedValue)#/>
|
||||
</cfif>
|
||||
|
||||
<!--- check required fields --->
|
||||
<cfif StructFind(ATTRIBUTES,"required")>
|
||||
<cfif submittedValue EQ "">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Не заполнено обязательное поле #ATTRIBUTES.displayName# "/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif StructKeyExists(ATTRIBUTES, "forNull")>
|
||||
<cfif #submittedValue# EQ #ATTRIBUTES.forNull#>
|
||||
<cfset ATTRIBUTES.null="true" />
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfswitch expression=#ATTRIBUTES.type#>
|
||||
<!--- для integer, bigint наличие атрибутa autoincrement подавляет сообщение об ошибке --->
|
||||
<!--- *** можно было сделать для всех, но тогда надо обрабатывать исключения --->
|
||||
<cfcase value="integer,int,bigint"><!--- *** формат с экспонентой не будет работать --->
|
||||
<cfset s=reReplace(submittedValue, "[[:space:]]", "", "ALL")/>
|
||||
<cfif FindOneOf(",.",s)>
|
||||
<cfset s=ListFirst(s,",.")/>
|
||||
<cfset ATTRIBUTES.warningState = "true"/>
|
||||
<cfset ATTRIBUTES.warningMessage = "Number is truncated #ATTRIBUTES.displayName#: #submittedValue#->#s#"/>
|
||||
</cfif>
|
||||
|
||||
<!---*** не слишком явно - подавление ошибки для автоинкрементного поля --->
|
||||
<cfif (NOT isNumeric(s))>
|
||||
<!---<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement"))>
|
||||
<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement")) AND (NOT structKeyExists(ATTRIBUTES, "key"))> --->
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
|
||||
<!--- по-хорошему, выбрасывать исключение нужно только при попытке сохранения
|
||||
Но можно и не разбирать в некоторых режимах
|
||||
--->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="guid,uuid,idstamp">
|
||||
<!--- guid = PostgreSQL hack --->
|
||||
<cfif NOT isValid("guid", submittedValue) AND len(attributes.submittedvalue) GT 0>
|
||||
<cfset ATTRIBUTES.errorState = "true" />
|
||||
<cfset ATTRIBUTES.errorMessage = "[Error]: Value is not a GUID type." />
|
||||
<!--- <cfthrow type="UnsupportedParameterType"
|
||||
message="[Error]: Value is not a GUID type."
|
||||
detail='Value is: #attributes.submittedvalue#'/> --->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value = submittedValue />
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="numeric,decimal,float,real,double">
|
||||
<cfset s=replace(reReplace(submittedValue, "[[:space:]]", "", "ALL"), ",", ".", "ALL")/>
|
||||
<cfif NOT isNumeric(s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="bit,boolean,bool"><!--- потому что транслируем примитивно в CF_SQL_BIT--->
|
||||
<cfset s=trim(submittedValue)/> <!--- нужно ли это? пробелы действительно дают ошибку --->
|
||||
<cfif NOT isBoolean(s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid boolean (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<cfelse>
|
||||
<!---<cfif s>
|
||||
<cfset ATTRIBUTES.value=true/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=false/>
|
||||
</cfif>--->
|
||||
<cfset ATTRIBUTES.value=s/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="date,time,timestamp">
|
||||
<cftry>
|
||||
<!--- <cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#")/> --->
|
||||
<cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#","#ATTRIBUTES.format#")/>
|
||||
<cfset ATTRIBUTES.submittedValue=ATTRIBUTES.value/><!--- *** dirty fix--->
|
||||
<cfcatch type="ANY">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid datetime" detail="Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<!--- пробрасывать сырое значение при ошибке разбора плохо, потому что функция форматирования даты споткнется--->
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="varchar">
|
||||
<cfset submittedValue=trim(submittedValue) />
|
||||
<cfparam name="ATTRIBUTES.default" type="string" default=""/>
|
||||
<cfset ATTRIBUTES.value=submittedValue/>
|
||||
<cfif ATTRIBUTES.size GT 0>
|
||||
<cfif len(submittedValue) GT ATTRIBUTES.size>
|
||||
<!--- truncate input --->
|
||||
<cfset ATTRIBUTES.value=left(submittedValue, ATTRIBUTES.size)/>
|
||||
<cfset ATTRIBUTES.warningState = "true"/>
|
||||
<cfset ATTRIBUTES.warningMessage = "String is truncated (#ATTRIBUTES.displayName#: #len(submittedValue)#->#ATTRIBUTES.size#)"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<!---<cfcase value="guid,uuid,uniqueidentifier,idstamp">--->
|
||||
|
||||
<cfcase value="other">
|
||||
<!--- other = old PostgreSQL hack (incompatibility) --->
|
||||
<!--- validation skipped --->
|
||||
<cfset ATTRIBUTES.value = submittedValue />
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="varbinary">
|
||||
<cfset ATTRIBUTES.value=submittedValue/>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
<cfset ATTRIBUTES.value=""/>
|
||||
<cfthrow type="UnsupportedParameterType" message="Specified parameter type is unsupported by current version of bean/param" detail='Parameter type "#ATTRIBUTES.type#" is not supported by custom tags bean/param'/>
|
||||
</cfdefaultcase>
|
||||
|
||||
<!---<cfcase value="hex">
|
||||
<cfset s=submittedValue/>
|
||||
<cfif reFindNoCase("[^0-9A-F]", s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid HEXADECIMAL number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<cfelse>
|
||||
<cftry>
|
||||
<cfset ATTRIBUTES.value=inputBaseN(s, 16)/>
|
||||
<cfcatch type="any">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Error converting from HEXADECIMAL format (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
</cfcase>--->
|
||||
</cfswitch>
|
||||
|
||||
<cfexit method="EXITTAG" />
|
||||
|
||||
<!--- это искусственный прием
|
||||
<cfif ATTRIBUTES.errorState AND NOT structKeyExists(ATTRIBUTES, "autoincrement") AND NOT structKeyExists(ATTRIBUTES, "key")>
|
||||
<cfset getBaseTagData("cf_bean").validationOk="false"/>
|
||||
</cfif>
|
||||
--->
|
||||
<!---
|
||||
CF_SQL_BIGINT
|
||||
CF_SQL_BIT
|
||||
CF_SQL_CHAR
|
||||
CF_SQL_BLOB
|
||||
CF_SQL_CLOB
|
||||
CF_SQL_DATE
|
||||
CF_SQL_DECIMAL
|
||||
CF_SQL_DOUBLE
|
||||
CF_SQL_FLOAT
|
||||
CF_SQL_IDSTAMP
|
||||
CF_SQL_INTEGER
|
||||
CF_SQL_LONGVARCHAR
|
||||
CF_SQL_MONEY
|
||||
CF_SQL_MONEY4
|
||||
CF_SQL_NUMERIC
|
||||
CF_SQL_REAL
|
||||
CF_SQL_REFCURSOR
|
||||
CF_SQL_SMALLINT
|
||||
CF_SQL_TIME
|
||||
CF_SQL_TIMESTAMP
|
||||
CF_SQL_TINYINT
|
||||
CF_SQL_VARCHAR
|
||||
*** CF_SQL_OTHER - используется с Postgre для GUID и jsonb
|
||||
--->
|
||||
<!--- при ошибочном вводе, например, некорректном формате, возможно два пути
|
||||
- сохранить, подставив дефолт (в том числе нулл). Можно генерировать ворнинг, цепляя его прямо к полю. В этом случае надо требовать корректные дефолты
|
||||
- можно ввести раздельные понятия дефолт и инит.
|
||||
Разница между дефолтом и инитом. При инициализации новой
|
||||
|
||||
Как дефолт, так и инит имеют целевой тип (число, дата...)
|
||||
Дефолт и инит используются для начального заполнения полей нового объекта. Для строки дефолт-дефолт - пустая, для остальных нулл
|
||||
Рассмотреть обязательные и необязательные параметры?
|
||||
Можно
|
||||
- выгрузить некорректный набор данных для редактирования. При втором варианте нужно все выгружать в стринги? Тогда как будут работать функции форматирования в теле страницы, ожидающе не строку? Скажем, дату? По ближайшей догадке? Пустую строку?
|
||||
Попытка вставить нулл в колонку NOT NULL должна пресекаться заранее (до ошибки БД)
|
||||
Нулловые строки можно не рассматривать, приводя к пустой строке.
|
||||
|
||||
Тэг принимает параметр от формы, парсит и отдает в атрибуте value. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
|
||||
Остальное оставить вызывающему тегу?
|
||||
--->
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<cfsilent>
|
||||
<!--- v0.4---><!---9:50 06.10.2020--->
|
||||
<!--- v0.5---><!---2024-12-11 15:10:07--->
|
||||
<!--- DEL.CFM - унифицированный шаблон операции удаления записи --->
|
||||
<cfimport prefix="m" taglib="../../lib"/>
|
||||
<cfimport prefix="m" taglib="../../lib/controls"/>
|
||||
</cfsilent><cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag><m:silent silent="No">
|
||||
|
||||
|
||||
|
||||
<cfparam name="ATTRIBUTES.savemarker" type="string" default="delete"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.checkQuery" type="string" default="select 0 as chk"/> <!--- запрос проверки целостности.
|
||||
Простая строка (параметр преобразуется до вызова тега).
|
||||
Если можно удалять, должен вернуть 0 в поле chk --->
|
||||
<cfparam name="ATTRIBUTES.displayDependencies" type="boolean" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.ignoreDependencies" type="boolean" default="No"/><!--- будем удалять, несмотря на наличие зависимостей Предположительно, мы примем меры в запросе удаления, или расчитываем на каскадное удаление. Имеет смысл, видимо, только в комбинации с displayDependencies=TRUE, иначе зачем описывать зависимости и потом их игнорировать. --->
|
||||
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.accessObj" type="string" default="#ATTRIBUTES.entity#"/> <!--- объект доступа, права к которому проверяем --->
|
||||
|
||||
<cfparam name="ATTRIBUTES.deleteQuery" type="string" default=""/> <!--- запрос удаления *** надо бы параметризовать --->
|
||||
<cfparam name="ATTRIBUTES.confirmMessage" type="string"/> <!--- текст для javascript:confirm() --->
|
||||
<cfparam name="ATTRIBUTES.denyMessage" type="string" default="Удаление невозможно, существуют зависимые записи."/> <!---
|
||||
сообщение о невозможности удаления записи (если проверка целостности говорит нельзя) --->
|
||||
|
||||
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string" default="#ATTRIBUTES.entity#_ls.cfm"/>
|
||||
<cfparam name="ATTRIBUTES.output" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.status" type="string" default=""/>
|
||||
|
||||
<cfparam name="thisTag.dependencies" type="array" default=#arrayNew(1)#/>
|
||||
<cfparam name="thisTag.conditions" type="array"/>
|
||||
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.status#"=CreateObject("component","status").init(false,"","")/>
|
||||
|
||||
<cfif len(ATTRIBUTES.queryString) EQ 0>
|
||||
<cfset i = 0 />
|
||||
<cfloop array=#thisTag.conditions# index="condition">
|
||||
<cfif i GT 0>
|
||||
<cfset ATTRIBUTES.queryString="#ATTRIBUTES.queryString#&"/>
|
||||
</cfif>
|
||||
<cfset i = i+1/>
|
||||
<cfset ATTRIBUTES.queryString="#ATTRIBUTES.queryString##condition.field#=#condition.value#"/>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<m:track
|
||||
thisURL="#request.thisPage#?#ATTRIBUTES.queryString#"
|
||||
defaultBackUrl="#ATTRIBUTES.defaultBackUrl#"
|
||||
output="tr"
|
||||
/>
|
||||
|
||||
<cfquery name="qCheck" datasource="#request.DS#">
|
||||
#preserveSingleQuotes(ATTRIBUTES.checkQuery)#
|
||||
</cfquery>
|
||||
|
||||
<cfset sDenyMessage=""/>
|
||||
|
||||
<cfoutput query="qCheck">
|
||||
<cfset sDenyMessage=evaluate("""#ATTRIBUTES.denyMessage#""")/>
|
||||
</cfoutput>
|
||||
|
||||
<!--- быстрая проверка --->
|
||||
<cfset dependentRecordsExist = false/>
|
||||
<cfloop array=#thisTag.dependencies# index="dep">
|
||||
<cfif dep.query.recordCount GT 0>
|
||||
<cfset dependentRecordsExist = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<!--- do delete --->
|
||||
<m:ac obj="#ATTRIBUTES.accessObj#" act="#request.PERMISSION_WRITE#" output="writePermitted">
|
||||
|
||||
<cfset doSave = false/>
|
||||
<cfif !ATTRIBUTES.readonly>
|
||||
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#")><!--- удаление можно делать только постом --->
|
||||
<cfset doSave = true/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
|
||||
<cfif doSave AND qCheck.chk EQ 0>
|
||||
<cftry> <!--- <cfoutput> <cfset i = 0 />
|
||||
<cfloop list=#ATTRIBUTES.deleteQuery# delimiters="?" index="queryPart">
|
||||
|
||||
<cfif i GT 0>
|
||||
cfqueryparam
|
||||
cfsqltype='#condition.cfsqltype#'
|
||||
value='#condition.value#'
|
||||
null='#condition.null#'/
|
||||
</cfif><cfset i = i + 1/>*#queryPart#* #i#
|
||||
</cfloop>
|
||||
|
||||
</cfoutput> --->
|
||||
|
||||
|
||||
<cfquery name="qDel" datasource="#request.DS#" result="result">
|
||||
|
||||
<cfif len(ATTRIBUTES.deleteQuery)>
|
||||
<!--- запрос удаления должен быть сформулирован так,
|
||||
чтобы условия удаления были в конце --->
|
||||
<cfset i = 0 />
|
||||
<cfloop list=#ATTRIBUTES.deleteQuery# delimiters="?" index="queryPart">
|
||||
<cfif i GT 0>
|
||||
<cfqueryparam
|
||||
cfsqltype='#condition.cfsqltype#'
|
||||
value='#condition.value#'
|
||||
null='#condition.null#'/>
|
||||
</cfif><cfset i = i + 1/>#queryPart#
|
||||
</cfloop>
|
||||
<cfelse>
|
||||
delete from #ATTRIBUTES.entity# where /**/
|
||||
<cfset i = 0 />
|
||||
<cfloop array=#thisTag.conditions# index="condition">
|
||||
<cfif i GT 0> AND </cfif>
|
||||
<cfset i = i + 1/>
|
||||
#condition.expression#=<cfqueryparam
|
||||
cfsqltype='#condition.cfsqltype#'
|
||||
value='#condition.value#'
|
||||
null='#condition.null#'/>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
</cfquery>
|
||||
|
||||
<!--- *** неуклюжий костыль --->
|
||||
<cfset CALLER.saveAndClose = true/><!--- сигнал для закрытия экрана --->
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfset "CALLER.#ATTRIBUTES.status#.errorState"=true/>
|
||||
<cfset "CALLER.#ATTRIBUTES.status#.errorMessage"="Ошибка при обращении к базе данных. #cfcatch.message# #cfcatch.detail# #cfcatch.tagcontext[1].template# line #cfcatch.tagcontext[1].line#"/>
|
||||
<!--- <cfrethrow/> --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
</m:ac>
|
||||
|
||||
<cfif !writePermitted><!--- перетирания cfcatch не будет --->
|
||||
<cfset "CALLER.#ATTRIBUTES.status#.errorState"=true/>
|
||||
<cfset "CALLER.#ATTRIBUTES.status#.errorMessage"="Ваших полномочий недостаточно для выполнения данной операции."/>
|
||||
</cfif>
|
||||
|
||||
|
||||
<!--- print output --->
|
||||
<cfsavecontent variable="markup">
|
||||
<cfoutput>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<br>
|
||||
<form name="frmDel" action="#request.thisPage#" method="POST">
|
||||
<cfif NOT writePermitted>
|
||||
<!--- ситуация возможна при прямом заходе по URL. Надо высказать юзарю свое неодобрение. --->
|
||||
<!--- <h4 class="text-danger">Ваших полномочий недостаточно для выполнения данной операции.</h4>
|
||||
<br/> --->
|
||||
|
||||
<cfelseif qCheck.chk GT 0>
|
||||
<h4 class="text-danger">#ATTRIBUTES.denyMessage#</h4>
|
||||
<br/>
|
||||
|
||||
<cfelseif dependentRecordsExist>
|
||||
<cfif ATTRIBUTES.displayDependencies>
|
||||
|
||||
<h4 class="text-danger">#ATTRIBUTES.denyMessage#</h4>
|
||||
<br/>
|
||||
<cftry>
|
||||
<cfloop array=#thisTag.dependencies# index="dep">
|
||||
<cfif dep.query.recordCount GT 0>
|
||||
|
||||
<h5>#dep.title#</h5>
|
||||
|
||||
<cfif dep.showDetails>
|
||||
<table class="worktable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="1%" class="c"></th>
|
||||
<cfloop array=#dep.fields# item="fld">
|
||||
<cfif len(fld.title)>
|
||||
<th>#fld.title#</th>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<th width="1%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<cfoutput query=#dep.query#>
|
||||
<cfset urlQueryString=""/>
|
||||
<cfloop array=#dep.keys# index="keyName">
|
||||
<cfif len(urlQueryString)>
|
||||
<cfset urlQueryString="#urlQueryString#&"/><!---***--->
|
||||
</cfif>
|
||||
<cfset urlQueryString="#urlQueryString##keyName#=#dep.query[keyName][currentRow]#"/>
|
||||
</cfloop>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#dep.page#?#urlQueryString#&#tr.fwx#" <!---class="far fa-edit"--->><img src="img/edit.gif"/></a>
|
||||
</td>
|
||||
<cfloop array=#dep.fields# item="fld">
|
||||
<cfif len(fld.title)>
|
||||
<td>
|
||||
<cfif isDefined("fld.formatter")>
|
||||
#fld.formatter(dep.query[fld.name][currentRow])#
|
||||
<cfelse>
|
||||
#dep.query[fld.name][currentRow]#
|
||||
</cfif>
|
||||
</td>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<td>
|
||||
<a href="#dep.delete_page#?#urlQueryString#&#tr.fwx#" <!---class="far fa-trash-alt text-danger"--->><img src="img/del.gif"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
</cfoutput>
|
||||
</table>
|
||||
<cfelse><!---dep.showDetails--->
|
||||
<cfoutput><p>#dep.denyMessage# (#dep.query.recordCount#)</p></cfoutput>
|
||||
</cfif><!---dep.showDetails--->
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfcatch type="any">
|
||||
<cfoutput>#cfcatch.message# #cfcatch.detail# #cfcatch.tagcontext[1].template# line #cfcatch.tagcontext[1].line#/></cfoutput><cfrethrow/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfelse><!---ATTRIBUTES.displayDependencies--->
|
||||
<cfoutput>#ATTRIBUTES.denyMessage#</cfoutput>
|
||||
</cfif><!---ATTRIBUTES.displayDependencies--->
|
||||
</cfif>
|
||||
|
||||
<cfif NOT dependentRecordsExist OR ATTRIBUTES.ignoreDependencies><!--- qCheck.chk GT 0 | dependentRecordsExist --->
|
||||
<cfif writePermitted>
|
||||
<input type="hidden" name="track" value="#tr.self#">
|
||||
<cfloop array=#thisTag.conditions# index="condition">
|
||||
<!---<input type="hidden" name="#condition.expression#" value="#evaluate('#condition.expression#')#"/>--->
|
||||
<input type="hidden" name="#condition.field#" value="#evaluate('#condition.field#')#"/><!--- по замыслу, берется из скоупа FORM (*** слишком неявно)--->
|
||||
</cfloop>
|
||||
<button type="submit" class="maincontrol btn btn-secondary" name="delete" onclick="if(!confirm('#evaluate("""#ATTRIBUTES.confirmMessage#""")#')) return false;">Удалить</button>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<button type="button" class="maincontrol btn btn-secondary" name="close" onclick="document.location.href='#tr.backURL#'">
|
||||
Закрыть
|
||||
</button>
|
||||
|
||||
<!--- <cfdump var=#dependentRecordsExist#/>
|
||||
<cfdump var=#ATTRIBUTES.ignoreDependencies#/>
|
||||
<cfdump var=#NOT dependentRecordsExist OR ATTRIBUTES.ignoreDependencies#/> --->
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</cfoutput>
|
||||
</cfsavecontent>
|
||||
|
||||
</m:silent><cfif len(ATTRIBUTES.output)><cfset "CALLER.#ATTRIBUTES.output#" = #markup#/><cfelse>
|
||||
<cfoutput>#markup#</cfoutput>
|
||||
</cfif></cfif>
|
||||
<!--- пример монструозной конструкции Postgre для каскадного удаления --->
|
||||
<!--- with
|
||||
inst as (
|
||||
delete from instance
|
||||
where instance_uid='e283e337-0ddc-459c-88b8-57d1963b4a7d'
|
||||
returning instance_uid
|
||||
),
|
||||
st as (
|
||||
delete from instance_state
|
||||
where instance_uid in (select instance_uid from inst)
|
||||
returning instance_state_uid
|
||||
),
|
||||
op as (
|
||||
delete from instance_operation
|
||||
where instance_uid in (select instance_uid from inst)
|
||||
returning instance_operation_uid
|
||||
),
|
||||
par as (
|
||||
delete from instance_operation_param
|
||||
where instance_operation_uid in (select instance_operation_uid from op)
|
||||
returning instance_operation_param_uid
|
||||
),
|
||||
cfs as (
|
||||
delete from instance_operation_cfs_param
|
||||
where instance_operation_uid in (select instance_operation_uid from op)
|
||||
returning instance_operation_cfs_param_uid
|
||||
)
|
||||
select * from inst
|
||||
left outer join st on (inst.instance_uid=st.instance_uid)
|
||||
left outer join op on (inst.instance_uid=op.instance_uid)
|
||||
left outer join par on (op.instance_operation_uid=par.instance_operation_uid)
|
||||
left outer join cfs on (op.instance_operation_uid=cfs.instance_operation_uid)
|
||||
; --->
|
||||
<!--- такой вопрос: точно ли будут задействованы все CTE, если я на них не сошлюсь? На месте оптимизатора я бы проигнорил незадействованные. Наверное, надо все включить в джойн для уверенности? --->
|
||||
@@ -0,0 +1,19 @@
|
||||
<!---v0.2 9:50 06.10.2020
|
||||
---><cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_del" datacollection="conditions" />
|
||||
<cfparam name="ATTRIBUTES.field"/><!---имя параметра формы --->
|
||||
<cfparam name="ATTRIBUTES.value"/>
|
||||
<cfparam name="ATTRIBUTES.expression" default=""/><!---левая сторона выражения во where, обычно имя поля--->
|
||||
<cfparam name="ATTRIBUTES.cfsqltype" default="cf_sql_integer"/>
|
||||
<cfparam name="ATTRIBUTES.null" type="boolean" default="false"/>
|
||||
|
||||
<cfif ATTRIBUTES.expression IS "">
|
||||
<cfif len(thisTag.generatedContent)>
|
||||
<cfset ATTRIBUTES.expression=#thisTag.generatedContent#/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.expression=#ATTRIBUTES.field#/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,26 @@
|
||||
<cfsilent>
|
||||
<cfset out=""/>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="fieldsArray" />
|
||||
|
||||
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||
|
||||
<cfif ATTRIBUTES.expression IS "">
|
||||
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||
</cfif>
|
||||
|
||||
<cfif ATTRIBUTES.name IS "">
|
||||
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||
<cfif arrayLen(matches.pos) GT 1>
|
||||
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfif arrayLen(getBaseTagData("cf_dependency",1).thisTag.fieldsArray) GT 0>
|
||||
<cfset thisTag.generatedContent=", #thisTag.generatedContent#"/>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,9 @@
|
||||
<cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="keys" />
|
||||
|
||||
<cfparam name="ATTRIBUTES.field"/>
|
||||
<cfparam name="ATTRIBUTES.cfsqltype default="cf_sql_integer"/>
|
||||
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,56 @@
|
||||
<cfsilent>
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<cfassociate basetag="cf_del" datacollection="dependencies" />
|
||||
|
||||
<!--- following are tag attribute placeholders. Coorresponding data are used by parent tags!--->
|
||||
<cfparam name="ATTRIBUTES.entity" default=""/>
|
||||
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||
<cfparam name="ATTRIBUTES.page" default="#ATTRIBUTES.entity#.cfm"/>
|
||||
<cfparam name="ATTRIBUTES.delete_page" default="#ATTRIBUTES.entity#_del.cfm"/>
|
||||
<cfparam name="ATTRIBUTES.showDetails" type="boolean" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.denyMessage" type="string" default="Необходимо удалить соответствующие записи"/><!--- displayed instead of details when ATTRIBUTES.showDetails IS "No"--->
|
||||
<!---<cfparam name="ATTRIBUTES.ondelete" default="restrict"/> implement 1-level Cascade? --->
|
||||
|
||||
<cfparam name="thisTag.fields" type="array"/>
|
||||
<cfparam name="thisTag.from" type="array"/>
|
||||
<cfparam name="thisTag.conditions" type="array"/>
|
||||
<cfparam name="thisTag.order_by" type="array"/>
|
||||
|
||||
<cfset ATTRIBUTES.fields=thisTag.fields/>
|
||||
<cfset ATTRIBUTES.keys=arrayNew(1)/>
|
||||
|
||||
<cfset i=0/>
|
||||
<cfloop array=#thisTag.fields# index="field">
|
||||
<cfif len(field.name)>
|
||||
<cfif structKeyExists(field, "key")>
|
||||
<cfset arrayAppend(ATTRIBUTES.keys, #field.name#)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfquery name="qDependency" datasource="#request.DS#">
|
||||
select
|
||||
<cfset i = 0 />
|
||||
<cfloop array=#thisTag.fields# index="field">
|
||||
<cfif i GT 0>, </cfif><cfset i = i+1/>#field.expression#
|
||||
</cfloop>
|
||||
from
|
||||
<cfloop array=#thisTag.from# index="from">
|
||||
#from.expression#
|
||||
</cfloop>
|
||||
where
|
||||
<cfset i = 0 />
|
||||
<cfloop array=#thisTag.conditions# index="condition">
|
||||
<cfif i GT 0> AND </cfif><cfset i = i+1/>
|
||||
#condition.expression#=<cfqueryparam cfsqltype='#condition.cfsqltype#' value='#condition.value#' null='#condition.null#'/>
|
||||
</cfloop>
|
||||
order by
|
||||
<cfset i = 0 />
|
||||
<cfloop array=#thisTag.order_by# index="order">
|
||||
<cfif i GT 0>, </cfif><cfset i = i+1/>#order.expression#
|
||||
</cfloop>
|
||||
</cfquery>
|
||||
<cfset ATTRIBUTES.query=#qDependency#/>
|
||||
</cfif>
|
||||
<cfset thisTag.generatedContent=""/>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,9 @@
|
||||
<cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="conditions" />
|
||||
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent#/>
|
||||
<cfparam name="ATTRIBUTES.value"/>
|
||||
<cfparam name="ATTRIBUTES.cfsqltype" default="cf_sql_integer"/>
|
||||
<cfparam name="ATTRIBUTES.null" type="boolean" default="No"/>
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,22 @@
|
||||
<cfsilent></cfsilent>
|
||||
<cfset out=""/>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="fields" />
|
||||
|
||||
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent#/>
|
||||
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||
|
||||
<cfif ATTRIBUTES.name IS "">
|
||||
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||
<cfif arrayLen(matches.pos) GT 1>
|
||||
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif arrayLen(getBaseTagData("cf_dependency",1).thisTag.fields) GT 1>
|
||||
<cfset thisTag.generatedContent=", #thisTag.generatedContent#"/>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
@@ -0,0 +1,6 @@
|
||||
<cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="from" />
|
||||
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent# />
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,6 @@
|
||||
<cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_dependency" datacollection="order_by" />
|
||||
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent# />
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,66 @@
|
||||
<cfsilent>
|
||||
<!--- 20181209 v0.3 --->
|
||||
<!---v0.3 добавлен атрибут cfSqlType--->
|
||||
<!---v0.4 добавлен атрибут container for json--->
|
||||
<!---v0.5 добавлен атрибут type и трансляция в CF_SQL_--->
|
||||
<!--- 20210326 v0.8 formatter --->
|
||||
|
||||
<cffunction name="passThrough"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="just return argument">
|
||||
<cfargument name="x" type="ANY" required="true"/>
|
||||
<cfreturn #ARGUMENTS.x#/>
|
||||
</cffunction>
|
||||
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||
<cfparam name="ATTRIBUTES.type" default="string"/>
|
||||
<cfparam name="ATTRIBUTES.cfSqlType" default="#getCfSqlType(ATTRIBUTES.type)#"/>
|
||||
<cfparam name="ATTRIBUTES.container" default=""/>
|
||||
<cfparam name="ATTRIBUTES.formatter" default=#passThrough#/>
|
||||
|
||||
<cfif ATTRIBUTES.expression IS "">
|
||||
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||
</cfif>
|
||||
|
||||
<cfif ATTRIBUTES.name IS "">
|
||||
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||
<cfif arrayLen(matches.pos) GT 1>
|
||||
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cffunction name="getCfSqlType">
|
||||
<!---Translate CF type understandable by isValid to CF_SQL_*--->
|
||||
<cfargument name="type"/>
|
||||
|
||||
<cfswitch expression=#ARGUMENTS.type#>
|
||||
<cfcase value="string">
|
||||
<cfreturn "CF_SQL_VARCHAR"/>
|
||||
</cfcase>
|
||||
<cfcase value="integer">
|
||||
<cfreturn "CF_SQL_INTEGER"/>
|
||||
</cfcase>
|
||||
<cfcase value="boolean">
|
||||
<cfreturn "CF_SQL_BIT"/>
|
||||
</cfcase>
|
||||
<cfcase value="numeric">
|
||||
<cfreturn "CF_SQL_NUMERIC"/>
|
||||
</cfcase>
|
||||
<cfcase value="date,time">
|
||||
<cfreturn "CF_SQL_TIMESTAMP"/>
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfthrow type="custom" message="Unsupported type" detail="Type #ARGUMENTS.type# not supported"/>
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
|
||||
</cffunction>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,68 @@
|
||||
<cfsilent>
|
||||
<!--- 20181209 v0.3 добавлено поле cfSqlType--->
|
||||
<!--- 20201029 v0.5 все атрибуты необязательные--->
|
||||
<!--- 20201114 v0.6 добавлен атрибут "container" for json--->
|
||||
<!--- 20201201 v0.7 исправлен listOut--->
|
||||
<!--- 20210326 v0.8 formatter --->
|
||||
<!--- 20210326 v0.9 listOutKeepContent --->
|
||||
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
|
||||
|
||||
<cfif thisTag.executionMode is "end">
|
||||
|
||||
<cfparam name="ATTRIBUTES.titleMapOut" default=""/>
|
||||
<cfparam name="ATTRIBUTES.lengthOut" default=""/>
|
||||
<cfparam name="ATTRIBUTES.listOut" default=""/><!---возвращаемый список полей через запятую для селекта. Если не задан (чаще всего так), этот список возвращается в виде контента тега --->
|
||||
<cfparam name="ATTRIBUTES.listOutKeepContent" default=false/><!--- указывает listOut не очищать контент --->
|
||||
<cfparam name="ATTRIBUTES.fieldsToInclude" default=""/><!--- список нужных полей, если не пустой - выбрасывать все поля, кроме тех, что в списке --->
|
||||
|
||||
<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
|
||||
|
||||
<cfset titleMap=structNew("linked")/><!---railo syntax--->
|
||||
<cfset list=""/>
|
||||
<cfset i=0/>
|
||||
<cfloop array=#thisTag.fieldsArray# index="field">
|
||||
<cfif len(#field.name#) EQ 0
|
||||
OR listLen(ATTRIBUTES.fieldsToInclude) EQ 0
|
||||
OR listFind(ATTRIBUTES.fieldsToInclude,field.name)>
|
||||
<!--- пытаемся сохранить прежнюю логику - поля без имени не выбрасываются, но не попадают в titleMap --->
|
||||
<cfset i=i+1/>
|
||||
<cfset list=listAppend(#list#, #field.expression#)/>
|
||||
|
||||
<cfif len(#field.name#)>
|
||||
<cfset structInsert(#titleMap#, #field.name#, structNew())/>
|
||||
<cfset "titleMap.#field.name#.ordinal"=#i#/>
|
||||
<cfset "titleMap.#field.name#.title"=#field.title#/>
|
||||
<cfset "titleMap.#field.name#.type"=#field.type#/>
|
||||
<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
|
||||
<cfset "titleMap.#field.name#.container"=#field.container#/>
|
||||
<cfset "titleMap.#field.name#.formatter"=#field.formatter#/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfif len(ATTRIBUTES.lengthOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.lengthOut#"= i/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.titleMapOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
|
||||
</cfif>
|
||||
|
||||
<!--- <cfif len(ATTRIBUTES.listOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.listOut#"=preserveSingleQuotes(list)/>
|
||||
<cfset thisTag.generatedContent=""/>
|
||||
</cfif> --->
|
||||
|
||||
<cfif len(ATTRIBUTES.listOut)><!--- *** некрасиво --->
|
||||
<cfset "CALLER.#ATTRIBUTES.listOut#"=list/>
|
||||
<cfif ATTRIBUTES.listOutKeepContent>
|
||||
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||
<cfelse>
|
||||
<cfset thisTag.generatedContent=""/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,354 @@
|
||||
<!---
|
||||
bean (child tag)
|
||||
msyu@mail.ru
|
||||
2013-08-29 v0.1
|
||||
2013-09-04 v0.2 tested on CF8 and Railo
|
||||
2014-01-20 v0.3 added exception for unsupported parameter type
|
||||
2014-10-24 v0.5 ATTRIBUTES.field became default for ATTRIBUTES.param
|
||||
2014-11-18 v0.6 rework
|
||||
2015-07-21 v0.7 EXITTAG moved to the end of file
|
||||
2015-07-22 v0.8 Changed protocol for skipInsert, skipUpdate, key, readonly, autoincrement, required attributes, now we can set value at runtime like skipInsert="#boolean#" instead of just including or omitting the attribute
|
||||
2015-12-02 v0.9 Added preprocessor attribute. Fixed timestamp bug ('01.12.2015' treated as 12 JAN 2015)
|
||||
2015-12-04 v0.12 for dates, submitted value is now back-overwritten
|
||||
2015-12-04 v0.13 bug fix
|
||||
v0.94
|
||||
2017-03-10 v0.15 - scope resolution order changed to CALLER-FORM-URL
|
||||
2018-05-31 v0.16 - strip whitespace from numbers
|
||||
2019-02-09 v0.17 - call preprocessor before checking required field
|
||||
2022-01-06 v0.18 - added BIT type (for PostgreSQL) - в базе представляется boolean!
|
||||
2023-04-17 v0.19 - форматирование, добавлен other к idstamp (для postgres)
|
||||
2023-04-18 v0.20 - добавлен атрибут format, для даты
|
||||
2024-03-22 v0.21 - добавлены синонимы int,boolean,bool. Перенумерованы версии 91->11
|
||||
2024-10-16 v0.23 - добавлены атрибуты minlength, maxlength, regex, validate
|
||||
--->
|
||||
|
||||
<!--- проверка на неподдерживаемые поля --->
|
||||
<cfloop collection=#ATTRIBUTES# item="a">
|
||||
<cfif NOT ListFindNoCase("param,field,type,displayName,default,init,size,scale,skipInsert,skipUpdate,key,readonly,autoincrement,value,null,forNull,eval,required,preprocessor,dtlocale,format,maxlength,minlength,regex,validate", #a#)>
|
||||
<cfthrow type="UnsupportedAttribute" message='Attribute #a# is unsupported by bean/param' detail='Specified attribute "#a#" is not supported by custom tag bean/param'/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
<cfparam name="ATTRIBUTES.default" default=""/><!--- подставляется при сохранении, если параметр не передавался. Важно для чекбоксов, они не передаются, если не отмечены --->
|
||||
<cfparam name="ATTRIBUTES.init" default=#ATTRIBUTES.default#/><!--- передается в форму для новой записи (предзаполнение). Для чекбокса может быть init=1, default=0 - отмеченный по умолчанию --->
|
||||
<cfparam name="ATTRIBUTES.field"/><!--- имя поля в таблице БД --->
|
||||
<cfparam name="ATTRIBUTES.param" default="#ATTRIBUTES.field#"/><!--- имя поля в URL/FORM и экспортируемой переменной --->
|
||||
<cfparam name="ATTRIBUTES.type" default="varchar"/><!--- тип поля в терминах SQL (CF_SQL_*) --->
|
||||
<cfparam name="ATTRIBUTES.displayName" default=#ATTRIBUTES.param#/><!--- для сообщений об ошибках--->
|
||||
<cfparam name="ATTRIBUTES.size" default="0"/><!--- for strings, soft limit (causes truncation). 0=unlimited --->
|
||||
<cfparam name="ATTRIBUTES.scale" default="1"/> <!--- Множитель. Только для чисел, только при записи. --->
|
||||
<!--- если атрибут записан без значения, он получает значение true! --->
|
||||
<cfparam name="ATTRIBUTES.skipInsert" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.skipUpdate" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.key" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.autoincrement" type="boolean" default="false"/><!--- правильнее autogenerated, потому что GUID тоже бывает генеренным --->
|
||||
<cfparam name="ATTRIBUTES.required" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.dtlocale" type="string" default="ru"/><!--- datetime locale--->
|
||||
<cfparam name="ATTRIBUTES.format" type="string" default=""/><!--- datetime format, normally yyyy-MM-dd, CASE SENSITIVE, NOT YYYY-DD-MM!!!--->
|
||||
<cfparam name="ATTRIBUTES.maxlength" default="0"/><!--- max length hard limit, throws exception unlike ATTRIBUTES.size. 0=unlimited, специально не указываем тип - то есть это по умолчанию строка - чтобы не ломалось от пустой строки--->
|
||||
<cfparam name="ATTRIBUTES.minlength" default="0"/><!--- min length hard limit, throws exception unlike ATTRIBUTES.size. 0=unlimited, специально не указываем тип - то есть это по умолчанию строка - чтобы не ломалось от пустой строки--->
|
||||
<!--- <cfparam name="ATTRIBUTES.regex" type="string" default=""/> мы его закомментировали и оставили, чтобы про него не забыть, а дальше мы проверяем его наличие в скоупе атрибутов ---><!--- validation regex --->
|
||||
<cfparam name="ATTRIBUTES.validate" type="any" default=#function(x){return ""}#/><!--- non-empty return indicates error --->
|
||||
|
||||
<!--- ATTRIBUTES.forNull ---><!--- если forNull не задано, параметр считается обязательным ***?--->
|
||||
<!--- ATTRIBUTES.preprocessor ---><!--- функция --->
|
||||
|
||||
<!--- in-out --->
|
||||
<cfparam name="ATTRIBUTES.null" type="boolean" default="false"/>
|
||||
<!--- ATTRIBUTES.value - входной-выходной параметр. --->
|
||||
<!---/in-out --->
|
||||
|
||||
<!--- *** выходные параметры. Странно выглядит. Хоть как-то в имени это отразить, что выходные... --->
|
||||
<cfparam name="ATTRIBUTES.hasInput" type="boolean" default="false" />
|
||||
<cfparam name="ATTRIBUTES.submittedValue" type="string" default=""/><!--- необработанное входное значение --->
|
||||
<cfparam name="ATTRIBUTES.errorState" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.errorMessage" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.warningState" type="boolean" default="false"/>
|
||||
<cfparam name="ATTRIBUTES.warningMessage" type="string" default=""/>
|
||||
|
||||
<!--- Тут намешано 2 стиля, и оба не вызывают восторга
|
||||
Атрибут декларируется с дефолтом (потому что, в отличие от аргумента функции, мы не можем декларировать необязательный атрибут, так как используем cfparam)
|
||||
Или атрибут не декларируется, а проверяется его наличие в скоупе атрибутов, что не улучшает читаемость кода --->
|
||||
|
||||
<cfassociate basetag="cf_bean" datacollection="params" />
|
||||
|
||||
<!--- Поле "только для чтения" не обрабатываем никак, если только оно не ключевое. Зачем может понадобиться readonly ключевое поле - непонятно, но вдруг --->
|
||||
<cfif NOT (ATTRIBUTES.key)>
|
||||
<cfif (ATTRIBUTES.readonly)>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- Если атрибуту явно указан NULL, дальше ничего не делаем --->
|
||||
<cfif ATTRIBUTES.null>
|
||||
<cfset ATTRIBUTES.value="0"/><!--- значение не используется, но переменная должна быть определена --->
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
|
||||
<!--- принимаем значение из передаваемых данных или подставляем значение по умолчанию --->
|
||||
<!--- можно было бы попробовать cfparam CALLER.#ATTRIBUTES.param# и обрабатывать исключение, но так обходимся без исключений, хотя и не изящно --->
|
||||
<cfif StructKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset submittedValue=ATTRIBUTES.value/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(CALLER, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(CALLER, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(FORM, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(FORM, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
<cfelseif StructKeyExists(URL, ATTRIBUTES.param)>
|
||||
<cfset submittedValue=structFind(URL, ATTRIBUTES.param)/>
|
||||
<cfset ATTRIBUTES.hasInput="true"/>
|
||||
|
||||
<cfelse>
|
||||
<!--- если параметр отсутствует в передаваемых данных --->
|
||||
<cfset submittedValue=ATTRIBUTES.default />
|
||||
</cfif>
|
||||
|
||||
<cfif !StructKeyExists(ATTRIBUTES,"value")>
|
||||
<cfset ATTRIBUTES.value="" /><!---*** dirty fix--->
|
||||
</cfif>
|
||||
|
||||
<cfset ATTRIBUTES.submittedValue=submittedValue />
|
||||
|
||||
|
||||
<cfif StructKeyExists(ATTRIBUTES,"preprocessor")>
|
||||
<cfset submittedValue=#ATTRIBUTES.preprocessor(submittedValue)#/>
|
||||
</cfif>
|
||||
|
||||
<!--- check required fields --->
|
||||
<cfif ATTRIBUTES.required>
|
||||
<cfif submittedValue EQ "">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Не заполнено обязательное поле #ATTRIBUTES.displayName# "/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif StructKeyExists(ATTRIBUTES, "forNull")>
|
||||
<cfif #submittedValue# EQ #ATTRIBUTES.forNull#>
|
||||
<cfset ATTRIBUTES.null="true" />
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- type validation --->
|
||||
<cfswitch expression=#ATTRIBUTES.type#>
|
||||
<!--- для integer, bigint наличие атрибутa autoincrement подавляет сообщение об ошибке --->
|
||||
<!--- *** можно было сделать для всех, но тогда надо обрабатывать исключения --->
|
||||
<cfcase value="integer,int,bigint"><!--- *** формат с экспонентой не будет работать --->
|
||||
<cfset s=reReplace(submittedValue, "[[:space:]]", "", "ALL")/>
|
||||
<cfif FindOneOf(",.",s)>
|
||||
<cfset s=ListFirst(s,",.")/>
|
||||
<cfset ATTRIBUTES.warningState = "true"/>
|
||||
<cfset ATTRIBUTES.warningMessage = "Number is truncated #ATTRIBUTES.displayName#: #submittedValue#->#s#"/>
|
||||
</cfif>
|
||||
|
||||
<!---*** не слишком явно - подавление ошибки для автоинкрементного поля --->
|
||||
<cfif (NOT isNumeric(s))>
|
||||
<!---<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement"))>
|
||||
<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement")) AND (NOT structKeyExists(ATTRIBUTES, "key"))> --->
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
|
||||
<!--- по-хорошему, выбрасывать исключение нужно только при попытке сохранения
|
||||
Но можно и не разбирать в некоторых режимах
|
||||
--->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="guid,uuid,idstamp">
|
||||
<!--- guid = PostgreSQL hack --->
|
||||
<cfif NOT isValid("guid", submittedValue) AND len(attributes.submittedvalue) GT 0>
|
||||
<cfset ATTRIBUTES.errorState = "true" />
|
||||
<cfset ATTRIBUTES.errorMessage = "[Error]: Value is not a GUID type." />
|
||||
<!--- <cfthrow type="UnsupportedParameterType"
|
||||
message="[Error]: Value is not a GUID type."
|
||||
detail='Value is: #attributes.submittedvalue#'/> --->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value = submittedValue />
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="numeric,decimal,float,real,double">
|
||||
<cfset s=replace(reReplace(submittedValue, "[[:space:]]", "", "ALL"), ",", ".", "ALL")/>
|
||||
<cfif NOT isNumeric(s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="bit,boolean,bool"><!--- потому что транслируем примитивно в CF_SQL_BIT--->
|
||||
<cfset s=trim(submittedValue)/> <!--- нужно ли это? пробелы действительно дают ошибку --->
|
||||
<cfif NOT isBoolean(s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid boolean (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<cfelse>
|
||||
<!---<cfif s>
|
||||
<cfset ATTRIBUTES.value=true/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=false/>
|
||||
</cfif>--->
|
||||
<cfset ATTRIBUTES.value=s/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="date,time,timestamp">
|
||||
<cftry>
|
||||
<!--- <cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#")/> --->
|
||||
<cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#","#ATTRIBUTES.format#")/>
|
||||
<cfset ATTRIBUTES.submittedValue=ATTRIBUTES.value/><!--- *** dirty fix--->
|
||||
<cfcatch type="ANY">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<!---<cfthrow type="custom" message="Invalid datetime" detail="Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<!--- пробрасывать сырое значение при ошибке разбора плохо, потому что функция форматирования даты споткнется--->
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="varchar">
|
||||
<cfset submittedValue=trim(submittedValue) />
|
||||
<cfparam name="ATTRIBUTES.default" type="string" default=""/>
|
||||
<cfset ATTRIBUTES.value=submittedValue/>
|
||||
|
||||
<cfif ATTRIBUTES.maxlength GT 0>
|
||||
<cfif len(submittedValue) GT ATTRIBUTES.maxlength>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Длина #ATTRIBUTES.displayName# превышает #ATTRIBUTES.maxlength#"/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif ATTRIBUTES.minlength GT 0>
|
||||
<cfif len(submittedValue) LT ATTRIBUTES.minlength>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Длина #ATTRIBUTES.displayName# меньше #ATTRIBUTES.minlength#"/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif ATTRIBUTES.size GT 0>
|
||||
<cfif len(submittedValue) GT ATTRIBUTES.size>
|
||||
<!--- truncate input --->
|
||||
<cfset ATTRIBUTES.value=left(submittedValue, ATTRIBUTES.size)/>
|
||||
<cfset ATTRIBUTES.warningState = "true"/>
|
||||
<cfset ATTRIBUTES.warningMessage = "String is truncated (#ATTRIBUTES.displayName#: #len(submittedValue)#->#ATTRIBUTES.size#)"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<!---<cfcase value="guid,uuid,uniqueidentifier,idstamp">--->
|
||||
|
||||
<cfcase value="other">
|
||||
<!--- other = old PostgreSQL hack (incompatibility) --->
|
||||
<!--- validation skipped --->
|
||||
<cfset ATTRIBUTES.value = submittedValue />
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="varbinary">
|
||||
<cfset ATTRIBUTES.value=submittedValue/>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
<cfset ATTRIBUTES.value=""/>
|
||||
<cfthrow type="UnsupportedParameterType" message="Specified parameter type is unsupported by current version of bean/param" detail='Parameter type "#ATTRIBUTES.type#" is not supported by custom tags bean/param'/>
|
||||
</cfdefaultcase>
|
||||
|
||||
<!---<cfcase value="hex">
|
||||
<cfset s=submittedValue/>
|
||||
<cfif reFindNoCase("[^0-9A-F]", s)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Invalid HEXADECIMAL number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
<cfelse>
|
||||
<cftry>
|
||||
<cfset ATTRIBUTES.value=inputBaseN(s, 16)/>
|
||||
<cfcatch type="any">
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = "Error converting from HEXADECIMAL format (#ATTRIBUTES.displayName#: #submittedValue#)"/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
</cfcase>--->
|
||||
</cfswitch>
|
||||
|
||||
<!--- regular expression validation --->
|
||||
<cfif StructKeyExists(ATTRIBUTES,"regex")>
|
||||
<cfif reFind(ATTRIBUTES.regex,submittedValue) EQ 0>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = 'Pattern "#ATTRIBUTES.regex#" does not match "#submittedValue#"'/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
<!--- custom validation --->
|
||||
<cfif StructKeyExists(ATTRIBUTES,"validate")>
|
||||
<cfset validationMessage=ATTRIBUTES.validate(submittedValue)/>
|
||||
<cfif len(validationMessage)>
|
||||
<cfset ATTRIBUTES.errorState = "true"/>
|
||||
<cfset ATTRIBUTES.errorMessage = validationMessage/>
|
||||
<cfset ATTRIBUTES.value="" />
|
||||
<cfexit method="exittag" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfexit method="EXITTAG" />
|
||||
|
||||
<!--- это искусственный прием
|
||||
<cfif ATTRIBUTES.errorState AND NOT structKeyExists(ATTRIBUTES, "autoincrement") AND NOT structKeyExists(ATTRIBUTES, "key")>
|
||||
<cfset getBaseTagData("cf_bean").validationOk="false"/>
|
||||
</cfif>
|
||||
--->
|
||||
<!---
|
||||
CF_SQL_BIGINT
|
||||
CF_SQL_BIT
|
||||
CF_SQL_CHAR
|
||||
CF_SQL_BLOB
|
||||
CF_SQL_CLOB
|
||||
CF_SQL_DATE
|
||||
CF_SQL_DECIMAL
|
||||
CF_SQL_DOUBLE
|
||||
CF_SQL_FLOAT
|
||||
CF_SQL_IDSTAMP
|
||||
CF_SQL_INTEGER
|
||||
CF_SQL_LONGVARCHAR
|
||||
CF_SQL_MONEY
|
||||
CF_SQL_MONEY4
|
||||
CF_SQL_NUMERIC
|
||||
CF_SQL_REAL
|
||||
CF_SQL_REFCURSOR
|
||||
CF_SQL_SMALLINT
|
||||
CF_SQL_TIME
|
||||
CF_SQL_TIMESTAMP
|
||||
CF_SQL_TINYINT
|
||||
CF_SQL_VARCHAR
|
||||
*** CF_SQL_OTHER - используется с Postgre для GUID и jsonb
|
||||
--->
|
||||
<!--- при ошибочном вводе, например, некорректном формате, возможно два пути
|
||||
- сохранить, подставив дефолт (в том числе нулл). Можно генерировать ворнинг, цепляя его прямо к полю. В этом случае надо требовать корректные дефолты
|
||||
- можно ввести раздельные понятия дефолт и инит.
|
||||
Разница между дефолтом и инитом. При инициализации новой
|
||||
|
||||
Как дефолт, так и инит имеют целевой тип (число, дата...)
|
||||
Дефолт и инит используются для начального заполнения полей нового объекта. Для строки дефолт-дефолт - пустая, для остальных нулл
|
||||
Рассмотреть обязательные и необязательные параметры?
|
||||
Можно
|
||||
- выгрузить некорректный набор данных для редактирования. При втором варианте нужно все выгружать в стринги? Тогда как будут работать функции форматирования в теле страницы, ожидающе не строку? Скажем, дату? По ближайшей догадке? Пустую строку?
|
||||
Попытка вставить нулл в колонку NOT NULL должна пресекаться заранее (до ошибки БД)
|
||||
Нулловые строки можно не рассматривать, приводя к пустой строке.
|
||||
|
||||
Тэг принимает параметр от формы, парсит и отдает в атрибуте value. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
|
||||
Остальное оставить вызывающему тегу?
|
||||
--->
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<cfcomponent displayname="Bean Status" output="false">
|
||||
<!--- v 0.03 2024-09-05 12:36:40 added recordExists --->
|
||||
|
||||
<cfproperty name="errorState" type="boolean"/>
|
||||
<cfproperty name="errorMessage" type="string"/>
|
||||
<cfproperty name="processingMode" type="string"/>
|
||||
<cfproperty name="recordExists" type="boolean"/>
|
||||
|
||||
<cffunction name="init">
|
||||
<cfset this.errorState=false/>
|
||||
<cfset this.errorMessage=""/>
|
||||
<cfset this.processingMode=""/>
|
||||
<cfset this.recordExists=false/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="init">
|
||||
<cfargument name="errorState" type="boolean"/>
|
||||
<cfargument name="errorMessage" type="string"/>
|
||||
<cfargument name="processingMode" type="string"/>
|
||||
|
||||
<cfset this.errorState=arguments.errorState/>
|
||||
<cfset this.errorMessage=arguments.errorMessage/>
|
||||
<cfset this.processingMode=arguments.processingMode/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="init">
|
||||
<cfargument name="errorState" type="boolean"/>
|
||||
<cfargument name="errorMessage" type="string"/>
|
||||
<cfargument name="processingMode" type="string"/>
|
||||
<cfargument name="recordExists" type="boolean"/>
|
||||
|
||||
<cfset this.errorState=arguments.errorState/>
|
||||
<cfset this.errorMessage=arguments.errorMessage/>
|
||||
<cfset this.processingMode=arguments.processingMode/>
|
||||
<cfset this.recordExists=arguments.recordExists/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,59 @@
|
||||
<cfif thisTag.ExecutionMode is 'end'>
|
||||
<cfparam name="ATTRIBUTES.output" type="string">
|
||||
<cfparam name="ATTRIBUTES.outputType" type="string" default="map">
|
||||
<cfparam name="thisTag.params" type="array"/>
|
||||
|
||||
|
||||
<cffunction name="paramTypeToColumnType">
|
||||
<cfargument name="paramType" type="string"/>
|
||||
|
||||
<cfswitch expression=#ARGUMENTS.paramType#>
|
||||
<cfcase value="integer">
|
||||
<cfreturn "integer"/>
|
||||
</cfcase>
|
||||
<cfcase value="numeric">
|
||||
<cfreturn "decimal"/><!---*** --->
|
||||
</cfcase>
|
||||
<cfcase value="datetime">
|
||||
<cfreturn "timestamp"/>
|
||||
</cfcase>
|
||||
<cfcase value="string">
|
||||
<cfreturn "varchar"/>
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfreturn "varchar"/>
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
|
||||
|
||||
|
||||
<cfswitch expression=#ATTRIBUTES.outputType#>
|
||||
|
||||
<cfcase value="map">
|
||||
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||
<cfset "CALLER.#ATTRIBUTES.output#.#thisTag.params[i].name#"=thisTag.params[i].value/>
|
||||
</cfloop>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="query">
|
||||
<cfset columnList=""/>
|
||||
<cfset columnTypeList=""/>
|
||||
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||
<cfset columnList=listAppend(columnList, thisTag.params[i].name)/>
|
||||
<cfset columnTypeList=listAppend(columnTypeList, paramTypeToColumnType(thisTag.params[i].type))/>
|
||||
</cfloop>
|
||||
<cfset qOut=queryNew(columnList,columnTypeList)/>
|
||||
<cfset queryAddRow(qOut)/>
|
||||
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||
<cfset querySetCell(qOut, thisTag.params[i].name, thisTag.params[i].value)/>
|
||||
</cfloop>
|
||||
<cfset "CALLER.#ATTRIBUTES.output#"=#qOut#/>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
<cfthrow type="custom" message="Unsupported output type '#ATTRIBUTES.outputType#'" detail=""/>
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
</cfif>
|
||||
@@ -0,0 +1,69 @@
|
||||
<cfsilent></cfsilent>
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
|
||||
<cfassociate basetag="cf_submit_collection" datacollection="params" />
|
||||
|
||||
<cfparam name="ATTRIBUTES.name"/>
|
||||
<cfparam name="ATTRIBUTES.type" default="string"/>
|
||||
<cfparam name="ATTRIBUTES.default" default=""/><!--- не валидируется, что позволяет использовать, например, пустую строку для индикации нулла в числовой колонке --->
|
||||
|
||||
<!--- output params --->
|
||||
<cfparam name="ATTRIBUTES.submittedValue" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.value" type="any" default=""/>
|
||||
|
||||
|
||||
<cfif structKeyExists(CALLER.VARIABLES, "#ATTRIBUTES.name#")>
|
||||
<cfset ATTRIBUTES.submittedValue=CALLER.VARIABLES["#ATTRIBUTES.name#"]/>
|
||||
<cfelseif structKeyExists(FORM, "#ATTRIBUTES.name#")>
|
||||
<cfset ATTRIBUTES.submittedValue=FORM["#ATTRIBUTES.name#"]/>
|
||||
<cfelseif structKeyExists(URL, "#ATTRIBUTES.name#")>
|
||||
<cfset ATTRIBUTES.submittedValue=URL["#ATTRIBUTES.name#"]/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.submittedValue=#ATTRIBUTES.default#/>
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"preprocessor")>
|
||||
<cfset preprocessedValue=#ATTRIBUTES.preprocessor(ATTRIBUTES.submittedValue)#/>
|
||||
<cfelse>
|
||||
<cfset preprocessedValue=#ATTRIBUTES.submittedValue#/>
|
||||
</cfif>
|
||||
|
||||
|
||||
<cfswitch expression=#ATTRIBUTES.type#>
|
||||
<cfcase value="integer">
|
||||
<cfif isValid('integer', preprocessedValue)>
|
||||
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||
<cfelseif isValid('number', preprocessedValue)>
|
||||
<cfset ATTRIBUTES.value=round(preprocessedValue)/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="numeric">
|
||||
<cfif isValid('numeric', preprocessedValue)>
|
||||
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||
<cfelse>
|
||||
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="datetime">
|
||||
<cfparam name="ATTRIBUTES.locale" default="ru"/>
|
||||
<cfparam name="ATTRIBUTES.format" default=""/>
|
||||
|
||||
<cftry>
|
||||
<cfset ATTRIBUTES.value=LSParseDateTime(preprocessedValue, #ATTRIBUTES.locale#, #ATTRIBUTES.format#)/>
|
||||
<cfcatch type="ANY">
|
||||
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="string">
|
||||
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||
</cfcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
</cfif>
|
||||
@@ -0,0 +1,33 @@
|
||||
<cfcomponent extends="form_page_info" displayname="information container for detail page" output="true">
|
||||
|
||||
<cfproperty name="formName" type="string"/>
|
||||
<cfproperty name="formAction" type="string"/>
|
||||
<cfproperty name="formMethod" type="string"/>
|
||||
<cfproperty name="formEncType" type="string"/>
|
||||
|
||||
<cffunction name="init">
|
||||
<cfargument name="entity" type="string" required="true"/>
|
||||
<cfargument name="key" type="string" default="#arguments.entity#_id"/>
|
||||
<cfargument name="track" type="struct" required="true"/>
|
||||
<cfargument name="permission" type="number" required="true"/>
|
||||
<cfargument name="formName" type="string" default=""/>
|
||||
<cfargument name="formAction" type="string" default=""/>
|
||||
<cfargument name="formMethod" type="string" default=""/>
|
||||
<cfargument name="formEncType" type="string" default=""/>
|
||||
|
||||
<cfset super.init(arguments.entity, arguments.key, arguments.track, arguments.formName, arguments.formAction, arguments.formMethod,arguments.formEncType)/>
|
||||
|
||||
<cfset this.permission=arguments.permission/>
|
||||
<cfset this.formName=arguments.formName/>
|
||||
<cfset this.formAction=arguments.formAction/>
|
||||
<cfset this.formMethod=arguments.formMethod/>
|
||||
<cfset this.formEncType=arguments.formEncType/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="getType" returntype="string">
|
||||
<cfreturn "detail"/>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,59 @@
|
||||
<cfsilent><!--- dispatch --->
|
||||
<cfimport prefix="m" taglib="../lib"/>
|
||||
<cfimport prefix="c" taglib="../lib/controls"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.usePRG" type="boolean" default="Yes"/>
|
||||
<cfparam name="ATTRIBUTES.pageInfo" type="struct"/>
|
||||
<cfparam name="ATTRIBUTES.status" type="struct"/>
|
||||
|
||||
|
||||
<cfif structKeyExists(ATTRIBUTES,"id")>
|
||||
<cfset id=#ATTRIBUTES.id#/>
|
||||
<cfelse>
|
||||
<cfparam name="ATTRIBUTES.data" type="struct"/>
|
||||
<cfset id=#ATTRIBUTES.data[ATTRIBUTES.pageInfo.entity &"_id"]#/>
|
||||
</cfif>
|
||||
|
||||
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string" default="#ATTRIBUTES.pageInfo.entity#_ls.cfm"/>
|
||||
<cfparam name="ATTRIBUTES.queryString" type="string" default="#ATTRIBUTES.pageInfo.key#=#id#"/><!---works only for entities with simple integer primary key--->
|
||||
<cfparam name="ATTRIBUTES.trackOut" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.idAttributesOut" type="string" default=""/><!---works only for entities with simple integer primary key--->
|
||||
|
||||
<m:track
|
||||
thisURL="#request.thisPage#?#ATTRIBUTES.queryString#"
|
||||
defaultBackUrl="#ATTRIBUTES.defaultBackUrl#"
|
||||
output="tr"
|
||||
/>
|
||||
|
||||
<!--- route --->
|
||||
<cfif ATTRIBUTES.usePRG AND !ATTRIBUTES.status.errorState>
|
||||
<!--- PRG pattern--->
|
||||
<cfif structKeyExists(CALLER, "save")>
|
||||
<m:location url="#tr.selfUrl#"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(CALLER, "saveAndClose") AND !ATTRIBUTES.status.errorState>
|
||||
<m:location url="#tr.backUrl#"/>
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(CALLER, "saveAndNew") AND !ATTRIBUTES.status.errorState>
|
||||
<m:location url="#tr.selfUrl#&doClone"/><!--- должен содержать Query String, чтобы указать ключ, иначе смысла нет--->
|
||||
</cfif>
|
||||
<!---/route --->
|
||||
|
||||
<cfset ATTRIBUTES.pageInfo.track=#tr#/>
|
||||
<cfset ATTRIBUTES.pageInfo.status=#ATTRIBUTES.status#/><!---Передаем статус в pageInfo--->
|
||||
|
||||
<cfif len(ATTRIBUTES.trackOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.idAttributesOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.cfsqltype"="cf_sql_integer"/>
|
||||
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.value"=#id#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.null"=#!isNumeric(id)#/>
|
||||
</cfif>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,131 @@
|
||||
<cfsilent>
|
||||
<!--- build query filter string --->
|
||||
<!--- пустые и некорректные значения параметров вызвыают пропуск параметра --->
|
||||
<cfparam name="ATTRIBUTES.filter" type="struct"/>
|
||||
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
|
||||
<cfset queryFilter="">
|
||||
<cfloop collection=#ATTRIBUTES.filter# item="item">
|
||||
|
||||
<cftry>
|
||||
<cfset fltr=structFind(ATTRIBUTES.filter,item)>
|
||||
|
||||
<!--- checks and preformatting --->
|
||||
<cfset skip=false><!--- *** неуклюжая имитация continue --->
|
||||
|
||||
<cfswitch expression=#fltr.ftype#>
|
||||
<cfcase value="numeric">
|
||||
<cfif NOT isNumeric(fltr.val)><cfset skip=true></cfif>
|
||||
<cfset val=fltr.val>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="date">
|
||||
<cfif NOT isDate(fltr.val)><cfset skip=true></cfif>
|
||||
<cfset val=fltr.val>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="string">
|
||||
<!--- special case --->
|
||||
<!--- escape single quotes for security--->
|
||||
<cfset val=replace(fltr.val,"'","''", "ALL")>
|
||||
<!--- elclose in single quotes --->
|
||||
<cfif NOT fltr.compare EQ "LIKE%"><!---*** криво --->
|
||||
<cfset val="'#val#'">
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="list"><!--- *** для простоты листы разных типов не различаются, что не есть хорошо, повышается риск инъекциии --->
|
||||
<!--- данные списка хранятся в виде массива, чтобы исключить проблемы с запятыми--->
|
||||
<cfif arrayLen(fltr.val) EQ 0>
|
||||
<cfset skip=true>
|
||||
<cfelse>
|
||||
<cfset isNumericList=true>
|
||||
|
||||
<!--- test: maybe list is of numbers--->
|
||||
<cfloop array=#fltr.val# index="item">
|
||||
<cfif NOT isNumeric(#item#)>
|
||||
<cfset isNumericList=false/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<!---<cfoutput>#isNumericList#</cfoutput>--->
|
||||
|
||||
<cfset lst="">
|
||||
<cfif isNumericList>
|
||||
<cfloop array=#fltr.val# index="item">
|
||||
<cfset lst=listAppend(lst, item)><!---<cfoutput>**#item#</cfoutput> --->
|
||||
</cfloop>
|
||||
<cfelse>
|
||||
<cfloop array=#fltr.val# index="item"><!--- escape single quotes to prevent SQL injection --->
|
||||
<cfset cleanItem=replace(item, "'", "''", "ALL")>
|
||||
<cfset lst=listAppend(lst, "'#cleanItem#'")><cfoutput>#item#</cfoutput>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
<cfset val=lst>
|
||||
<!---<cfoutput>#val#</cfoutput>--->
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
<cfset skip=true>
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
|
||||
<!--- query building --->
|
||||
<!--- потенциально опасно --->
|
||||
<!--- *** протестировать на sql injection --->
|
||||
<cfif NOT skip>
|
||||
<cfif structKeyExists(fltr,"expression") AND len(fltr.expression)>
|
||||
<cfset cmpExpr=replace(fltr.expression,"?","#val#","ALL")/>
|
||||
<cfelse>
|
||||
<cfswitch expression=#fltr.compare#>
|
||||
<cfcase value="EQ">
|
||||
<cfset cmpExpr="#fltr.field#=#val#">
|
||||
</cfcase>
|
||||
<cfcase value="NE,NEQ">
|
||||
<cfset cmpExpr="NOT #fltr.field#=#val#">
|
||||
</cfcase>
|
||||
<cfcase value="LE">
|
||||
<cfset cmpExpr="#fltr.field#<=#val#">
|
||||
</cfcase>
|
||||
<cfcase value="LT">
|
||||
<cfset cmpExpr="#fltr.field#<#val#">
|
||||
</cfcase>
|
||||
<cfcase value="GE">
|
||||
<cfset cmpExpr="#fltr.field#>=#val#">
|
||||
</cfcase>
|
||||
<cfcase value="GT">
|
||||
<cfset cmpExpr="#fltr.field#>#val#">
|
||||
</cfcase>
|
||||
<cfcase value="IN">
|
||||
<cfset cmpExpr="#fltr.field# IN (#val#)">
|
||||
</cfcase>
|
||||
<cfcase value="LIKE">
|
||||
<cfset cmpExpr="#fltr.field# LIKE #val#">
|
||||
</cfcase>
|
||||
<cfcase value="LIKE%">
|
||||
<cfset cmpExpr="#fltr.field# LIKE '%#val#%'">
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfset skip=true>
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfif NOT skip>
|
||||
<cfset queryFilter="#queryFilter# AND #cmpExpr#">
|
||||
</cfif>
|
||||
|
||||
<cfcatch type="ANY"></cfcatch>
|
||||
</cftry>
|
||||
</cfloop>
|
||||
|
||||
<cfif isDefined("ATTRIBUTES.output")>
|
||||
<cfset "CALLER.#ATTRIBUTES.output#"=preserveSingleQuotes(queryFilter)/>
|
||||
<cfelse>
|
||||
<cfset thisTag.generatedContent=preserveSingleQuotes(queryFilter)/>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--- version 2.01---><!---15:25 31.01.2019--->
|
||||
<!--- build query filter string --->
|
||||
<!--- пустые и некорректные значения параметров вызвыают пропуск параметра --->
|
||||
<cfparam name="ATTRIBUTES.filter" type="struct"/>
|
||||
|
||||
<cfloop collection=#ATTRIBUTES.filter# item="item">
|
||||
<cfsilent>
|
||||
|
||||
<cfset fltr=structFind(ATTRIBUTES.filter,item)>
|
||||
<cfparam name="fltr.val" default=""/>
|
||||
<cfparam name="fltr.prefix" default=""/>
|
||||
<cfparam name="fltr.suffix" default=""/>
|
||||
<cfparam name="fltr.list" default="No"/>
|
||||
</cfsilent> <!---
|
||||
|
||||
---> AND <cfif structKeyExists(fltr,"expression") AND len(fltr.expression)><!---
|
||||
---><cfset len=listLen(#fltr.expression#,"?")><!---
|
||||
---><cfloop from=1 to=#len# index="i"><!---
|
||||
---><cfoutput>#listGetAt(fltr.expression,i,"?")#</cfoutput><!---
|
||||
---><cfif i LT #len#><cfqueryparam cfsqltype="#getCfSqLType(fltr.ftype)#" list="#fltr.list#" value="#fltr.prefix##fltr.val##fltr.suffix#"/></cfif><!---
|
||||
---></cfloop><!---
|
||||
---><cfelse><!---
|
||||
---><cfoutput>#fltr.field#</cfoutput><cfswitch expression=#fltr.compare#><cfcase value="EQ"> = </cfcase><cfcase value="NE,NEQ"> <> </cfcase><cfcase value="LE"> <= </cfcase><cfcase value="LT"> < </cfcase><cfcase value="GE"> >= </cfcase><cfcase value="GT"> > </cfcase><cfcase value="LIKE"> LIKE </cfcase><cfcase value="NOTLIKE"> NOT LIKE </cfcase><cfcase value="LIKE%"> LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><cfcase value="NOTLIKE%"> NOT LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><cfcase value="IN"> IN (<cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# list="#fltr.list#" value="#fltr.val#"/>)<cfcontinue/></cfcase><cfdefaultcase> = <cfoutput> #fltr.field#</cfoutput><cfcontinue/><!---***криво---></cfdefaultcase></cfswitch><cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="#fltr.val#"/><cfcontinue/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
|
||||
<cffunction name="getCfSqlType" output="No">
|
||||
<cfargument name="fieldType" default=""/>
|
||||
<cfswitch expression=#ARGUMENTS.fieldType#>
|
||||
<cfcase value="numeric">
|
||||
<cfreturn "CF_SQL_NUMERIC"/>
|
||||
</cfcase>
|
||||
<cfcase value="date,time,datetime">
|
||||
<cfreturn "CF_SQL_TIMESTAMP"/>
|
||||
</cfcase>
|
||||
<cfcase value="string">
|
||||
<cfreturn "CF_SQL_VARCHAR"/>
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfreturn "CF_SQL_VARCHAR"/>
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
</cffunction>
|
||||
@@ -0,0 +1,9 @@
|
||||
<cfcomponent extends="form_page_info" displayname="information container for form page" output="false">
|
||||
|
||||
<cfproperty name="settings" type="struct"/>
|
||||
|
||||
<cffunction name="getType" returntype="string">
|
||||
<cfreturn "filter"/>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,26 @@
|
||||
<cfsilent></cfsilent>
|
||||
<!--- version 2.01--->
|
||||
<cfimport prefix="m" taglib="."/>
|
||||
<cfparam name="ATTRIBUTES.target" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.scopeName" type="string" default="CALLER"/>
|
||||
<cfparam name="ATTRIBUTES.structName" type="string" default="filter"/>
|
||||
<cfparam name="ATTRIBUTES.markerList" type="string" default="applyFilter,saveAndClose"/>
|
||||
|
||||
<cfif thisTag.executionMode IS "start">
|
||||
<cfset key="#ATTRIBUTES.target#.filter"/>
|
||||
|
||||
<cfif isDefined("reset") or isDefined("resetAndClose")>
|
||||
<m:settings action="clear" key="#key#"/>
|
||||
</cfif>
|
||||
|
||||
<m:settings action="get" key="#key#" output="filter"/>
|
||||
<cfset "#ATTRIBUTES.scopeName#.#ATTRIBUTES.structName#"=#filter#/>
|
||||
<cfelse>
|
||||
<!--- in fact, for any action --->
|
||||
<cfloop list=#ATTRIBUTES.markerList# index="marker">
|
||||
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||
<m:settings action="sync" key="#key#" obj=#CALLER.filter#>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
@@ -0,0 +1,173 @@
|
||||
<!--- version 2.01---><!---15:25 31.01.2019--->
|
||||
<!--- 2017.06.16 исправлен баг с очисткой при числовом значении по умолчанию --->
|
||||
<!---v0.5 10:02 02.01.2019--->
|
||||
<cfassociate basetag="cf_filter_settings"/>
|
||||
<cfparam name="ATTRIBUTES.filter" type="struct" default=#getBaseTagData("CF_FILTER_SETTINGS",1).filter#/><!--- *** possible Railo bug, getBaseTagData works only with second parameter --->
|
||||
<cfparam name="ATTRIBUTES.param" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.default" type="string" default=""/><!--- список: значения, установленные по умолчанию. Обычно состоит из 1 значения даже для списка. В случае кодирования base64 этот список НЕ кодируется (проблемы с запятыми остаются на совести разработчика)--->
|
||||
<!---*** И вообще Base64 нельзя использовать непосредственно, он не URLsafe --->
|
||||
<cfparam name="ATTRIBUTES.ftype" type="string" default="string"/>
|
||||
<cfparam name="ATTRIBUTES.list" type="boolean" default="No"/>
|
||||
<cfparam name="ATTRIBUTES.compare" type="string" default="EQ"/>
|
||||
<cfparam name="ATTRIBUTES.field" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.base64" type="boolean" default="No"/><!--- означает, что параметр нужно раскодировать из base64---><!---*** Переделать на URL-safe base64--->
|
||||
|
||||
<!---
|
||||
CFQUERYPARAM CFSQLTYPE="CF_SQL_VARCHAR" ставит апострофы вокруг значения параметра.
|
||||
Поэтому приходится любые wildcards засовывать в значение bind variable. Запрос должен иметь вид
|
||||
SELECT A,B FROM C WHERE A LIKE ?
|
||||
(без всяких апострофов), а значение параметра будет, например,
|
||||
"%#submitted_string#%"
|
||||
--->
|
||||
<cfparam name="ATTRIBUTES.prefix" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.suffix" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.expression" type="string" default=""/>
|
||||
|
||||
<!---
|
||||
Хранение данных для списков
|
||||
переделано обратно с массива на лист.
|
||||
При этом случилась забавная, но неприятная и трудноуловимая ошибка
|
||||
arrayAppend(array,item) механически заменил на listAppend(list,item)
|
||||
а надо было на list=listAppend(list,item)
|
||||
--->
|
||||
|
||||
<!---<cfif ATTRIBUTES.list>
|
||||
<cfset def = "">
|
||||
<cfloop list=#ATTRIBUTES.default# index="item">
|
||||
<cfset listAppend(def, item)>Список значений по умолчанию НЕ должен быть кодирован в base64 при ATTRIBUTES.base64
|
||||
</cfloop>
|
||||
<cfelse>
|
||||
|
||||
</cfif>--->
|
||||
<cfset def=ATTRIBUTES.default/>
|
||||
<!---<cfoutput>#ATTRIBUTES.param#:</cfoutput>--->
|
||||
|
||||
<!---<cfif evaluate("isDefined('CALLER.#ATTRIBUTES.param#')")>
|
||||
<cfset evaluate("val=(CALLER.#ATTRIBUTES.param#)")>--->
|
||||
|
||||
<cfif structKeyExists(CALLER, '#ATTRIBUTES.param#')>
|
||||
<cfset val=structFind(CALLER, '#ATTRIBUTES.param#')/>
|
||||
<!--- parse --->
|
||||
|
||||
<cfset to=1/>
|
||||
<cfset values = ""/>
|
||||
|
||||
<cfif ATTRIBUTES.list>
|
||||
<cfset to=listLen(val)/>
|
||||
|
||||
</cfif>
|
||||
|
||||
<cfloop from="1" to=#to# index="i">
|
||||
|
||||
<cfif ATTRIBUTES.list>
|
||||
<cfset element=listGetAt(val,i)/>
|
||||
<cfelse>
|
||||
<cfset element=val/>
|
||||
</cfif>
|
||||
|
||||
<cfswitch expression=#ATTRIBUTES.ftype#>
|
||||
|
||||
<cfcase value="numeric">
|
||||
<cfset element=trim(element)>
|
||||
<cfif NOT isNumeric(element)>
|
||||
<cfset element=def/>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="date">
|
||||
<cftry>
|
||||
<cfset element=CreateDateTime(ListGetAt(element,3,'.'), ListGetAt(element,2,'.'), ListGetAt(element,1,'.'),0,0,0)>
|
||||
<cfcatch type="Any">
|
||||
<!--- <cfoutput>#cfcatch.message# #cfcatch.detail#</cfoutput> --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="string">
|
||||
<cfif ATTRIBUTES.base64>
|
||||
<cfset element=ToString(BinaryDecode(element , "base64"))>
|
||||
</cfif>
|
||||
<cfset element=safeStr(element)/>
|
||||
</cfcase>
|
||||
|
||||
<cfdefaultcase>
|
||||
<cfthrow message="Filterparam: unsupported ftype value '#ATTRIBUTES.ftype#'" detail="Supported values are 'numeric', 'date', 'string'">
|
||||
</cfdefaultcase>
|
||||
|
||||
</cfswitch>
|
||||
|
||||
<cfif ATTRIBUTES.list>
|
||||
<cfset values=listAppend(values, element)/>
|
||||
<cfelse>
|
||||
<cfset val=element/>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
|
||||
<cfif ATTRIBUTES.list>
|
||||
<cfset val=values/><!---*** все это выглядит ужасно. Переиспользование переменных вперед-назад, IF вокруг листа--->
|
||||
</cfif>
|
||||
|
||||
<!---/parse --->
|
||||
|
||||
<cfelseif structKeyExists(ATTRIBUTES.filter, ATTRIBUTES.param)><!--- если параметр есть в сохраненном фильтре, взять оттуда --->
|
||||
|
||||
<cfset filterItem = structFind(ATTRIBUTES.filter, ATTRIBUTES.param)>
|
||||
<cfif isStruct(filterItem)>
|
||||
<cfset val=filterItem.val>
|
||||
<cfelse><!---not struct <cfoutput>#ATTRIBUTES.param#</cfoutput>--->
|
||||
<cfset val=def>
|
||||
</cfif>
|
||||
|
||||
<cfelse>
|
||||
|
||||
<cfset val=def>
|
||||
|
||||
</cfif>
|
||||
|
||||
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.param#", "")/>
|
||||
<cftry>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.param#",#val#)/>
|
||||
<cfcatch type="Any"></cfcatch>
|
||||
</cftry>
|
||||
|
||||
|
||||
<cfif shouldRemoveValue(val, def)>
|
||||
<cfset structDelete(ATTRIBUTES.filter, ATTRIBUTES.param)/>
|
||||
<cfelse>
|
||||
<cfset fltItem=structNew()>
|
||||
<cfset fltItem.ftype=ATTRIBUTES.ftype>
|
||||
<cfset fltItem.list=ATTRIBUTES.list>
|
||||
<cfset fltItem.field=ATTRIBUTES.field>
|
||||
<cfset fltItem.compare=ATTRIBUTES.compare>
|
||||
<cfset fltItem.val=val>
|
||||
<cfset fltItem.prefix=ATTRIBUTES.prefix/>
|
||||
<cfset fltItem.suffix=ATTRIBUTES.suffix/>
|
||||
<cfset fltItem.expression=ATTRIBUTES.expression/>
|
||||
<cfset structInsert(ATTRIBUTES.filter,ATTRIBUTES.param,fltItem, "TRUE")>
|
||||
</cfif>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
|
||||
<cffunction name="shouldRemoveValue" returnType="boolean">
|
||||
<cfargument name="val"/>
|
||||
<cfargument name="def"/>
|
||||
<!---<cfdump var=#val#> <cfdump var=#def#>--->
|
||||
<cfscript>
|
||||
if (!IsSimpleValue(val)){
|
||||
if (isArray(val)) {
|
||||
return (arrayLen(val) == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return ((val==def) OR (val==""));
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="safeStr" returnType="string">
|
||||
<cfargument name="val" type="string"/>
|
||||
<cfreturn replaceList(val, "',"",<,>", ",,,")/>
|
||||
</cffunction>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<cfcomponent extends="generic_page_info" displayname="information container for form page" output="true">
|
||||
|
||||
<cfproperty name="formName" type="string"/>
|
||||
<cfproperty name="formAction" type="string"/>
|
||||
<cfproperty name="formMethod" type="string"/>
|
||||
<cfproperty name="formEncType" type="string"/>
|
||||
|
||||
<cffunction name="init">
|
||||
<cfargument name="entity" type="string" required="true"/>
|
||||
<cfargument name="key" type="string" default="#arguments.entity#_id"/><!--- *** некрасиво как выполнен проброс --->
|
||||
<cfargument name="track" type="struct" required="true"/>
|
||||
<cfargument name="formName" type="string" default=""/>
|
||||
<cfargument name="formAction" type="string" default=""/>
|
||||
<cfargument name="formMethod" type="string" default=""/>
|
||||
<cfargument name="formEncType" type="string" default=""/>
|
||||
|
||||
<cfset super.init(arguments.entity, arguments.key, arguments.track)/>
|
||||
|
||||
<cfset this.formName=arguments.formName/>
|
||||
<cfset this.formAction=arguments.formAction/>
|
||||
<cfset this.formMethod=arguments.formMethod/>
|
||||
<cfset this.formEncType=arguments.formEncType/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="getType" returntype="string">
|
||||
<cfreturn "form"/>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,39 @@
|
||||
<cfcomponent displayname="Generic Page Info" output="true">
|
||||
<cfproperty name="entity" type="string"/>
|
||||
<cfproperty name="key" type="string"/>
|
||||
<cfproperty name="status" type="struct"/>
|
||||
<cfproperty name="track" type="struct"/>
|
||||
<cfproperty name="permission" type="integer"/>
|
||||
|
||||
<cfset this.PERMISSION_NONE=0/><!---duplicated in request scope--->
|
||||
<cfset this.PERMISSION_READ=1/><!---duplicated in request scope--->
|
||||
<cfset this.PERMISSION_WRITE=2/><!---duplicated in request scope--->
|
||||
|
||||
<cffunction name="init" access="public">
|
||||
<cfargument name="entity" type="string" required="true"/>
|
||||
<cfargument name="key" type="string" default="#arguments.entity#_id"/>
|
||||
<cfargument name="track" type="struct" required="true"/>
|
||||
|
||||
<cfset this.entity=arguments.entity/>
|
||||
<cfset this.key=arguments.key/>
|
||||
<cfset this.track=arguments.track/>
|
||||
<cfset this.permission=this.PERMISSION_NONE/>
|
||||
<cfset this.status.errorState=false/>
|
||||
<cfset this.status.errorMessage=""/>
|
||||
|
||||
<cfreturn this>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="readPermitted" returntype="boolean">
|
||||
<cfreturn (this.permission GE this.PERMISSION_READ)/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="writePermitted" returntype="boolean">
|
||||
<cfreturn (this.permission GE this.PERMISSION_WRITE)/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="getType" returntype="string">
|
||||
<cfreturn "generic"/>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,25 @@
|
||||
<cfparam name="ATTRIBUTES.container" type="struct">
|
||||
<cfparam name="ATTRIBUTES.objName" type="string">
|
||||
|
||||
<cfif NOT structKeyExists(ATTRIBUTES.container, ATTRIBUTES.objName)>
|
||||
<cfset structInsert(ATTRIBUTES.container,ATTRIBUTES.objName,structNew())>
|
||||
</cfif>
|
||||
|
||||
<cfset obj=structFind(ATTRIBUTES.container,ATTRIBUTES.objName)>
|
||||
|
||||
<!---
|
||||
Раздельная инициализация позволяет "чинить" поврежденный объект,
|
||||
но
|
||||
*** при этом факт повреждения не обнаруживается
|
||||
--->
|
||||
<cfif NOT structKeyExists(obj, "sort")>
|
||||
<cfset structInsert(obj,"sort", structNew())>
|
||||
</cfif>
|
||||
|
||||
<cfif NOT structKeyExists(obj, "filter")>
|
||||
<cfset structInsert(obj,"filter", structNew())>
|
||||
</cfif>
|
||||
|
||||
<cfexit method="EXITTAG" />
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<!--- used instead cflocation which, at least for railo, makes cyrillics in url corrupt --->
|
||||
<cfparam name="ATTRIBUTES.url" type="string"/>
|
||||
<cfheader statuscode="302" statustext="Moved Temporarily"/>
|
||||
<cfheader charset="#GetEncoding('URL')#" name="Location" value="#ATTRIBUTES.url#"/>
|
||||
<cfabort/>
|
||||
@@ -0,0 +1,24 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.catch"/>
|
||||
<cfparam name="ATTRIBUTES.output" default=""/><!--- deprecated, use instead ATTRIBUTES.status --->
|
||||
|
||||
<cfsavecontent variable="message">
|
||||
<cfoutput>
|
||||
Ошибка при обращении к базе данных. Возможно, программа некорректно сформировала условия фильтра или сортировки. В качестве временной меры попробуйте <a href="#request.thisPage#?reset_filter=yes&sort=reset">сбросить условия фильтра и сортировки</a>. Просьба сообщить об ошибке администратору.<br/>
|
||||
#ATTRIBUTES.catch.message#<br/>
|
||||
#ATTRIBUTES.catch.detail#<br/>
|
||||
</cfoutput>
|
||||
</cfsavecontent>
|
||||
|
||||
<!--- deprecated behavior --->
|
||||
<cfif len(ATTRIBUTES.output)>
|
||||
<cfset "CALLER.#ATTRIBUTES.output#"=#message#/>
|
||||
</cfif>
|
||||
|
||||
<!--- modern behavior --->
|
||||
<cfif structKeyExists(ATTRIBUTES,"status")>
|
||||
<cfset ATTRIBUTES.status.errorState="Yes"/>
|
||||
<cfset ATTRIBUTES.status.errorMessage="#message#"/>
|
||||
</cfif>
|
||||
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,10 @@
|
||||
<cfcomponent extends="generic_page_info" displayname="information container for page with form (detail, filter)" output="false">
|
||||
|
||||
<cfproperty name="recordsPerPage" type="integer"/>
|
||||
<cfproperty name="nStart" type="integer"/>
|
||||
<cfproperty name="settings" type="struct"/>
|
||||
|
||||
<cffunction name="getType" returntype="string">
|
||||
<cfreturn "ls"/>
|
||||
</cffunction>
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,24 @@
|
||||
<cfprocessingdirective suppressWhitespace="YES"><cfsilent>
|
||||
|
||||
<cfparam name="ATTRIBUTES.page" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.acl" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.label" type="string" default=""/>
|
||||
|
||||
<cfif structKeyExists(#ATTRIBUTES#,"external")>
|
||||
<!--- <cfset class="menu-item-ext"/>--->
|
||||
<cfset target=' target="_blank"'/>
|
||||
<cfelse>
|
||||
<!--- <cfset class="menu-item"/>--->
|
||||
<cfset target=''/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.queryString)>
|
||||
<cfset pageUrl="#ATTRIBUTES.page#?#ATTRIBUTES.queryString#"/>
|
||||
<cfelse>
|
||||
<cfset pageUrl="#ATTRIBUTES.page#"/>
|
||||
</cfif>
|
||||
|
||||
</cfsilent><!---
|
||||
---><cfmodule template="ac.cfm" obj="#ATTRIBUTES.acl#"><cfoutput><li class="nav-item"><a class="nav-link<cfif (request.thisPage EQ ATTRIBUTES.page)> active</cfif>" href="#pageUrl#"#target#>#ATTRIBUTES.label#</a></li></cfoutput><!---
|
||||
---></cfmodule></cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||
@@ -0,0 +1,11 @@
|
||||
<cfparam name="ATTRIBUTES.mimetype"/>
|
||||
<cfswitch expression=#ATTRIBUTES.mimetype#>
|
||||
<cfcase value="text/plain"><img src="img/mimetype/text.png"/></cfcase>
|
||||
<cfcase value="text/html"><img src="img/mimetype/html.png"/></cfcase>
|
||||
<cfcase value="application/pdf"><img src="img/mimetype/pdf.png"/></cfcase>
|
||||
<cfcase value="image/gif,image/png,image/jpeg"><img src="img/mimetype/image.png"/></cfcase>
|
||||
<cfcase value="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"><img src="img/mimetype/excel.png"/></cfcase>
|
||||
<cfcase value="application/vnd.ms-powerpoint"><img src="img/mimetype/powerpoint.png"/></cfcase>
|
||||
<cfcase value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"><img src="img/mimetype/word.png"/></cfcase>
|
||||
<cfdefaultcase><img src="img/mimetype/document.png"/></cfdefaultcase>
|
||||
</cfswitch><cfexit method="exittag"/>
|
||||
@@ -0,0 +1,44 @@
|
||||
<cfsilent><!--- build query sort string --->
|
||||
<!--- v0.3 ---><!--- 2025-04-29 09:13:19 --->
|
||||
<cfparam name="ATTRIBUTES.sortArray" type="array"/>
|
||||
<!--- <cfparam name="ATTRIBUTES.defaultSortArray" type="array" default=#[]#/> оказывается, это уже было --->
|
||||
<cfparam name="ATTRIBUTES.fieldCount" type="integer" default=0/>
|
||||
<cfparam name="ATTRIBUTES.defaultSortArray" type="array" default=#[]#/><!--- #[{"fld":1,"asc":0}]# --->
|
||||
<cfparam name="ATTRIBUTES.defaultOrder" default="1 desc"/><!--- оба варианта не отображаются в реестре, чтобы отображались - надо работать с массивом вне этого модуля, модуль не модифицирует массив сортировки. В общем, оба варианта дефолтной сортировки не вызывают восторга, но пусть остаются - в конце концов, есть не только реестры --->
|
||||
|
||||
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||
|
||||
<cfif ArrayLen(ATTRIBUTES.sortArray) EQ 0>
|
||||
<cfset ATTRIBUTES.sortArray=ATTRIBUTES.defaultSortArray/>
|
||||
</cfif>
|
||||
|
||||
<cfset querySort="">
|
||||
|
||||
<cfset i=1>
|
||||
<cfloop array=#ATTRIBUTES.sortArray# index="sort"><!--- index is not an ordinal, but the element of array--->
|
||||
<cftry>
|
||||
<cfif (ATTRIBUTES.fieldCount LE 0) OR (sort.fld LE ATTRIBUTES.fieldCount)>
|
||||
<cfif sort.asc>
|
||||
<cfset querySort=listAppend(querySort," #sort.fld# asc")>
|
||||
<cfelse>
|
||||
<cfset querySort=listAppend(querySort," #sort.fld# desc")>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfcatch type="Any"></cfcatch>
|
||||
</cftry>
|
||||
<cfset i=i+1>
|
||||
</cfloop>
|
||||
|
||||
<cfif len(trim(querySort)) EQ 0>
|
||||
<cfset querySort=#ATTRIBUTES.defaultOrder#>
|
||||
</cfif>
|
||||
|
||||
<cfset querySort=trim(querySort)>
|
||||
|
||||
<cfif isDefined("ATTRIBUTES.output")>
|
||||
<cfset "CALLER.#ATTRIBUTES.output#"=querySort/>
|
||||
<cfelse>
|
||||
<cfset thisTag.generatedContent=querySort/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,26 @@
|
||||
<cfsilent></cfsilent><!--- prepare environment ---><!--- v02 17:35 28.06.2018---><!---14:55 29.06.2018 bug fixed--->
|
||||
<cfimport prefix="m" taglib="../lib"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.accessObject" type="string" default=#ATTRIBUTES.entity#/>
|
||||
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.formEncType" type="string" default=""/>
|
||||
<cfparam name="ATTRIBUTES.trackOut" type="string" default="tr"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.thisUrl" default=""/>
|
||||
<cfif len(ATTRIBUTES.thisUrl) EQ 0><!--- наугад используем типовое соглашение--->
|
||||
<cfparam name="#ATTRIBUTES.key#" default="-1"/>
|
||||
<cfset id=evaluate("#ATTRIBUTES.key#")/><!---*** неизящно, зато безопасно---><!--- *** как-то не слишком безопасно --->
|
||||
<cfset ATTRIBUTES.thisUrl="#request.thisPage#?#ATTRIBUTES.key#=#id#"/>
|
||||
</cfif>
|
||||
|
||||
<m:ac obj="#ATTRIBUTES.accessObject#" permissionOut="permission"/>
|
||||
<m:track thisUrl="#ATTRIBUTES.thisUrl#" defaultBackUrl="#ATTRIBUTES.entity#_ls.cfm" output="track"/>
|
||||
|
||||
<cfset pageInfo=CreateObject("component","detail_page_info").init("#ATTRIBUTES.entity#","#ATTRIBUTES.key#",#track#,"#permission#","frm",#request.thisPage#,"POST","#ATTRIBUTES.formEncType#")/>
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#track#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
@@ -0,0 +1,25 @@
|
||||
<cfsilent><!--- prepare environment --->
|
||||
<cfimport prefix="m" taglib="../lib"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.lsPage" type="string" default="#ATTRIBUTES.entity#_ls"/>
|
||||
|
||||
<m:track thisUrl="#request.thisPage#" defaultBackUrl="#ATTRIBUTES.lsPage#.cfm" output="tr"/>
|
||||
|
||||
<cfset pageInfo=CreateObject("component","filter_page_info").init(#ATTRIBUTES.entity#,"#ATTRIBUTES.key#",#tr#,"frm",#request.thisPage#,"POST","")/>
|
||||
|
||||
<m:settings key="#ATTRIBUTES.lsPage#" output="settings" action="get"/>
|
||||
<cfset pageInfo.settings=#settings#/>
|
||||
|
||||
<m:ac obj="#ATTRIBUTES.entity#" act="#request.PERMISSION_READ#" output="readPermitted"/>
|
||||
<cfset pageInfo.readPermitted=#readPermitted#/>
|
||||
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,16 @@
|
||||
<cfsilent><!--- prepare environment --->
|
||||
<cfimport prefix="m" taglib="../lib"/>
|
||||
|
||||
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.action" type="string" default="#request.thisPage#"/><!--- Если подстановка шаблона делается в application.cfc, то request.thisPage будет соответствовать URL, а не шаблону --->
|
||||
|
||||
<m:track thisUrl="#request.thisPage#" defaultBackUrl="." output="tr"/>
|
||||
|
||||
<cfset pageInfo=CreateObject("component","form_page_info").init("","",#tr#,"frm",#ATTRIBUTES.action#,"POST","")/>
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,43 @@
|
||||
<cfsilent><!--- prepare environment --->
|
||||
<!---v0.2 accessObject--->
|
||||
<cfimport prefix="m" taglib="../lib"/></cfsilent><m:silent silent="No">
|
||||
|
||||
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.accessObject" type="string" default=#ATTRIBUTES.entity#/>
|
||||
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||
<cfparam name="ATTRIBUTES.settingsKey" type="string" default="#ATTRIBUTES.entity#_ls"/>
|
||||
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer" default="500"/>
|
||||
|
||||
<cfparam name="nStart" default="1"/>
|
||||
|
||||
<cfif nStart EQ 1>
|
||||
<cfset thisUrl="#request.thisPage#"/>
|
||||
<cfelse>
|
||||
<cfset thisUrl="#request.thisPage#?nStart=#nStart#"/>
|
||||
</cfif>
|
||||
<m:track thisUrl="#thisUrl#" defaultBackUrl="." output="tr"/>
|
||||
|
||||
<cfset pageInfo=CreateObject("component","ls_page_info").init("#ATTRIBUTES.entity#","#ATTRIBUTES.key#",#tr#)/>
|
||||
|
||||
<cfset pageInfo.recordsPerPage=#ATTRIBUTES.recordsPerPage#/>
|
||||
<cfset pageInfo.nStart=#nStart#/>
|
||||
<m:ac obj="#ATTRIBUTES.accessObject#" permissionOut="permission"/>
|
||||
<cfset pageInfo.permission=#permission#/>
|
||||
|
||||
|
||||
<cfif isDefined("CALLER.reset_filter")> <!---*** --->
|
||||
<m:settings action="clear" key="#ATTRIBUTES.settingsKey#.filter"/>
|
||||
</cfif>
|
||||
|
||||
<m:settings key="#ATTRIBUTES.settingsKey#" output="settings"><!---*** грязно: мы работаем с сортировкой, а синхронизируем весь контейнер и получаем его побочным эффектом для дальнейшего использования --->
|
||||
<m:sortparam sort=#settings.sort# sortlength=2/><!---*** вот где устанавливается длина массива сортировки--->
|
||||
</m:settings>
|
||||
<cfset pageInfo.settings=settings/><!--- unlike m:track, output="CALLER.settings" wouldnt't work for m:settings because of some StructFind --->
|
||||
|
||||
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
</m:silent>
|
||||
@@ -0,0 +1,396 @@
|
||||
<cfcomponent
|
||||
displayname="ReST API helper"
|
||||
output="true"
|
||||
hint="Static helper methods for ReST API">
|
||||
<!--- v0.02 2024-10-15 --->
|
||||
<!--- v0.03 2024-11-27 --->
|
||||
|
||||
|
||||
<cffunction name="empty2null"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="replace empty field with null">
|
||||
|
||||
<cfargument name="value" type="any" required="true" />
|
||||
|
||||
<cfif isEmpty(ARGUMENTS.value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
|
||||
<cfreturn javacast('null','')/>
|
||||
<cfelse>
|
||||
<cfreturn ARGUMENTS.value />
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="passThrough"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="just return argument">
|
||||
<cfargument name="x" type="ANY" required="true"/>
|
||||
<cfreturn #ARGUMENTS.x#/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="appendRecord"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="put data into struct, replace empty fields with null">
|
||||
|
||||
<cfargument name="struct" type="struct" required="true" />
|
||||
<cfargument name="key" type="string" required="true" />
|
||||
<cfargument name="fieldMap" type="struct" required="true" />
|
||||
<cfargument name="query" type="query" required="true" />
|
||||
<cfargument name="fieldNameDecorator" type="function" required="false" default=#passThrough#/>
|
||||
|
||||
<cfif len(ARGUMENTS.key)>
|
||||
<cfset ARGUMENTS.struct[ARGUMENTS.key]=structNew()/>
|
||||
<cfset var container=ARGUMENTS.struct[ARGUMENTS.key]/>
|
||||
<cfelse>
|
||||
<cfset var container=ARGUMENTS.struct/>
|
||||
</cfif>
|
||||
|
||||
<cfloop list=#structKeyList(ARGUMENTS.fieldMap)# index="local.col">
|
||||
<cfset var obj=container/>
|
||||
<cfset var formatter = structFind(ARGUMENTS.fieldMap[local.col], "formatter")/>
|
||||
<cfif structKeyExists(ARGUMENTS.fieldMap[#local.col#],"container")>
|
||||
<cfset var subContainerName = structFind(ARGUMENTS.fieldMap[local.col], "container")/>
|
||||
|
||||
<cfif len(subContainerName)>
|
||||
<cfif !structKeyExists(container, subContainerName)>
|
||||
<cfset structInsert(container, subContainerName, structNew())/>
|
||||
</cfif>
|
||||
<cfset obj=container[subContainerName]/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfif queryColumnExists(ARGUMENTS.query, local.col)>
|
||||
<cfset obj[ARGUMENTS.fieldNameDecorator(local.col)]=empty2null(formatter(ARGUMENTS.query[local.col][ARGUMENTS.query.currentRow]))/>
|
||||
</cfif>
|
||||
<!---*** не уверен в надежности конструкции во всех реализациях CF, контекст query передастся ли--->
|
||||
</cfloop>
|
||||
|
||||
<cfreturn ARGUMENTS.struct/>
|
||||
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="parseFilterParams"
|
||||
returntype="struct"
|
||||
output="true"
|
||||
hint="parse and collect filter params, for operators see filter_build">
|
||||
<!---e.g. duration=NEQ:2--->
|
||||
<!---*** add test for multiple values--->
|
||||
<cfargument name="params" type="struct" required="true" />
|
||||
|
||||
<cfset var out=structNew()/>
|
||||
<cfloop collection=#params# index="local.item">
|
||||
|
||||
<cfset var urlParamName=snake2camel(lCase(local.item))/>
|
||||
<cfif structKeyExists(URL,urlParamName)>
|
||||
<cfset var rawValue=URL[urlParamName]/>
|
||||
<cfset var operator="EQ"/>
|
||||
<cfset var value=#rawValue#/>
|
||||
<cfif listLen(rawValue,":") GT 1>
|
||||
<cfset var operator=listGetAt(rawValue,1,":")/>
|
||||
<cfset var value=listGetAt(rawValue,2,":")/>
|
||||
</cfif>
|
||||
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
|
||||
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
|
||||
<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#local.item#"/>
|
||||
<cfelse>
|
||||
<cfset var fieldName=#local.item#/>
|
||||
</cfif>
|
||||
<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#}/><!---*** add list--->
|
||||
<cfset structInsert(out, local.item, rec, true)/>
|
||||
</cfif>
|
||||
|
||||
</cfloop>
|
||||
<cfreturn out/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="parseOrderBy"
|
||||
returntype="struct"
|
||||
output="false"
|
||||
hint="parse and collect sort order param"><!--- deprecated --->
|
||||
|
||||
<cfargument name="params" type="struct" required="true" />
|
||||
<cfargument name="orderBy" type="string" required="true" />
|
||||
|
||||
<cfset var out={}/>
|
||||
|
||||
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
|
||||
<cfif listLen(local.item,".") GT 1>
|
||||
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
|
||||
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
|
||||
<cfcase value="ASC"><cfset var asc=true/></cfcase>
|
||||
<cfcase value="DESC"><cfset var asc=false/></cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<cfelse>
|
||||
<cfset var fld=camel2snake(local.item)/>
|
||||
<cfset var asc=true/>
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(ARGUMENTS.params, fld)>
|
||||
<cfset structInsert(out,"#ARGUMENTS.params[fld].prefix#.#fld#", {fld="#ARGUMENTS.params[fld].prefix#.#fld#",asc=#asc#}, true)/>
|
||||
<cfelse>
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfreturn out/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="parseNumericOrder"
|
||||
returntype="array"
|
||||
output="false"
|
||||
hint="parse and collect sort order param with numeric notation">
|
||||
|
||||
<cfargument name="fieldSet" type="struct" required="true" />
|
||||
<cfargument name="orderBy" type="string" required="true" />
|
||||
|
||||
<cfset var out=[]/>
|
||||
|
||||
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
|
||||
<cfif listLen(local.item,".") GT 1>
|
||||
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
|
||||
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
|
||||
<cfcase value="ASC"><cfset var asc=true/></cfcase>
|
||||
<cfcase value="DESC"><cfset var asc=false/></cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<cfelse>
|
||||
<cfset var fld=camel2snake(local.item)/>
|
||||
<cfset var asc=true/>
|
||||
</cfif>
|
||||
<cfif structKeyExists(ARGUMENTS.fieldSet, fld)>
|
||||
<cfset arrayAppend(out,{fld="#ARGUMENTS.fieldSet[fld].ordinal#",asc=#asc#})/>
|
||||
<cfelse>
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfreturn out/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!---<cffunction name="snake2camel"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="convert snake style name to camel style name">
|
||||
|
||||
<cfargument name="snake" type="string" required="true" />
|
||||
|
||||
<cfset var snake=ARGUMENTS.snake/>
|
||||
<cfset var camel=""/>
|
||||
<cfset var pos=1/>
|
||||
<cfloop condition="true">
|
||||
<cfset next=reFind("(_[a-z])", snake, pos, false)/>
|
||||
<cfif NOT next GT 0>
|
||||
<cfbreak/>
|
||||
</cfif>
|
||||
<cfset camel="#camel##mid(snake,pos,next-pos)##uCase(mid(snake,next+1,1))#"/>
|
||||
<cfset pos=next+2/>
|
||||
</cfloop>
|
||||
<cfset camel="#camel##mid(snake,pos,len(snake))#"/>
|
||||
|
||||
<cfreturn #camel#/>
|
||||
</cffunction>--->
|
||||
|
||||
<!---not used any more--->
|
||||
<cffunction name="query4json" access="public" returntype="any" output="false"
|
||||
hint="Converts query to an array of structs. Key names are lowercase. Empty fields are treated as nulls">
|
||||
|
||||
<cfargument name="Query" type="query" required="true" />
|
||||
|
||||
<cfset var out=arrayNew(1)/>
|
||||
<cfset var i=0/>
|
||||
|
||||
<cfloop query=ARGUMENTS.Query>
|
||||
<cfset i=i+1/>
|
||||
<cfset out[i]=structNew()/>
|
||||
<cfloop list=#ARGUMENTS.Query.ColumnList# index="local.col">
|
||||
<cfset var value=ARGUMENTS.Query[local.col][currentRow]/>
|
||||
<cfif isEmpty(value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
|
||||
<cfset out[i][local.col]=javacast('null','')/>
|
||||
<cfelse>
|
||||
<cfset out[i][local.col]=ARGUMENTS.Query[local.col][currentRow]/>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfloop>
|
||||
|
||||
<cfreturn(out)/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="snake2camel"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="convert snake style name to camel style name">
|
||||
<cfargument name="snake" type="string" required="true" />
|
||||
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="camel2snake"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="convert camel style name to snake style name">
|
||||
<cfargument name="snake" type="string" required="true" />
|
||||
<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="formatMessage"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Exception for display">
|
||||
|
||||
<cfargument name="message" type="string" required="true" />
|
||||
<cfargument name="title" type="string" required="false" />
|
||||
|
||||
<cfset var title=structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.message# />
|
||||
|
||||
<cfreturn {type="about:blank", title="#title#", detail="#ARGUMENTS.message#"}/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="formatException"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Exception for display">
|
||||
|
||||
<cfargument name="ex" type="struct" required="true" />
|
||||
<cfargument name="title" type="string" required="false" />
|
||||
|
||||
<cfset var detail = listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,": " )/>
|
||||
<cfset var title = structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.ex.message# />
|
||||
|
||||
<cfreturn formatMessage(detail, title) />
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="formatBadRequestError"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Bad Request Exception">
|
||||
|
||||
<cfargument name="ex" type="struct" required="true" />
|
||||
|
||||
<cfset var detail=listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,":" )/>
|
||||
|
||||
<cfreturn {type="about:blank", title="Bad Request", detail="#detail#"}/>
|
||||
</cffunction>
|
||||
|
||||
<!---<cffunction name="wrapResultSet"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="format resultset as json">
|
||||
|
||||
<cfargument name="qRead" type="query" required="true" />
|
||||
<cfargument name="titleMap" type="struct" required="true" />
|
||||
<cfargument name="startrow" type="integer" required="false" default="1" />
|
||||
|
||||
<cfset var resultSet=[]/>
|
||||
<cfloop query=#ARGUMENTS.qRead# startRow=#ARGUMENTS.startrow#>
|
||||
<cfset var rec={}/>
|
||||
|
||||
<cfset appendRecord(rec, "", ARGUMENTS.titleMap, ARGUMENTS.qRead, snake2camel)/>
|
||||
<cfset arrayAppend(resultSet, rec)/>
|
||||
</cfloop>
|
||||
|
||||
<cfset var out={
|
||||
pageSize=
|
||||
}/>
|
||||
|
||||
|
||||
<cfcontent type="application/json"/>
|
||||
<cfoutput>{
|
||||
"pageSize":#pageSize#,
|
||||
"page":#page#,
|
||||
"orderBy":"#orderBy#",
|
||||
"size":"#arrayLen(resultSet)#",
|
||||
"total":"#qTotal.cnt#",
|
||||
"results":#serializeJson(resultSet)#,
|
||||
"queryDurationMs":#queryDurationMs#, <cfset runDurationMs=getTickCount()-request.startTickCount/>
|
||||
"runDurationMs":#runDurationMs#
|
||||
}</cfoutput>--->
|
||||
<cffunction name="validateField"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="validate field of a structure, if exisits">
|
||||
|
||||
<cfargument name="struct" type="struct" required=true/>
|
||||
<cfargument name="name" required=true/>
|
||||
<cfargument name="type" default="string"/>
|
||||
<cfargument name="required" type="boolean" default="false"/>
|
||||
|
||||
<cfif structKeyExists(ARGUMENTS.struct, ARGUMENTS.name)>
|
||||
<cfif NOT isValid(ARGUMENTS.type, ARGUMENTS.struct[ARGUMENTS.name])>
|
||||
<cfthrow type="invalidParamValue" message="Parameter validation failed" detail="Supplied value of '#ARGUMENTS.name#' is not a valid #ARGUMENTS.type#"/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfif ARGUMENTS.required>
|
||||
<cfthrow type="invalidParamValue" message="Required field not supplied" detail="Required field #ARGUMENTS.name# not found"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<!--- returns false if the key does not exist and throws exception on invalid format--->
|
||||
<cffunction name="keyExistsAndValid" access="public" returntype="boolean">
|
||||
<cfargument name="struct" type="struct" required=true/>
|
||||
<cfargument name="key" type="string" required=true/>
|
||||
<cfargument name="type" type="string" required=false/>
|
||||
<cfargument name="maxLength" type="numeric" required=false hint="Checked for strings only, negative value means no limit"/>
|
||||
|
||||
<cfif structKeyExists(arguments.struct, arguments.key)>
|
||||
<cfif structKeyExists(arguments, "type")>
|
||||
<cfset var x=arguments.struct[arguments.key]/>
|
||||
<cfif isValid(arguments.type, x)>
|
||||
<cfif lCase(arguments.type) EQ "string">
|
||||
<cfif structKeyExists(arguments, "maxLength")>
|
||||
<cfif ARGUMENTS.maxLength GE 0>
|
||||
<cfif len(x) GT #arguments.maxLength#>
|
||||
<cfthrow type="invalidParamValue" message="Invalid parameter value (string too long)" detail="Parameter #arguments.key# length #len(x)# is greater than #arguments.maxLength#" errorcode=400/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfthrow type="invalidParamValue" message="Invalid parameter value" detail="Parameter #arguments.key# is not a valid #arguments.type#" errorcode=400/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfreturn true/>
|
||||
<cfelse>
|
||||
<cfreturn false/>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<!--- returns false if the key does not exist or has invalid format--->
|
||||
<cffunction name="keyExistsAndValidGraceful" access="public" returntype="boolean">
|
||||
<cfargument name="struct" type="struct" required=true/>
|
||||
<cfargument name="key" type="string" required=true/>
|
||||
<cfargument name="type" type="string" required=false/>
|
||||
<cfargument name="maxLength" type="numeric" required=false default="-1" hint="Checked for strings only"/>
|
||||
|
||||
<cftry>
|
||||
<cfreturn keyExistsAndValid(ARGUMENTS.struct, ARGUMENTS.key, ARGUMENTS.type, ARGUMENTS.maxLength)/>
|
||||
<cfcatch type="ANY"><!---<cfrethrow/>---></cfcatch>
|
||||
</cftry>
|
||||
<cfreturn false/>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
@@ -0,0 +1,216 @@
|
||||
<!--- *** Refactoring required --->
|
||||
<!--- v0.3
|
||||
2017-08-30
|
||||
Настройки вынесены в таблицу usr_settings в связи с миграцией репозитория пользователей в мастер справочники
|
||||
Заодно cfqueryparam
|
||||
|
||||
модуль должен инкапсулировать работу с настройками
|
||||
- хранение в сессии и в БД, синхронность с базой
|
||||
- чтение и модификацию
|
||||
- очистку(сброс)
|
||||
При этом модуль ничего не должен знать о структуре самих настроек (фильтрах, сортировках и т.д.)
|
||||
|
||||
Жизненный цикл контейнера settings
|
||||
1. Живет в сессии
|
||||
2. Если в сессии нет settings - вычитывается из БД
|
||||
3. Если и в БД нет - инициализируется и сразу сохраняется в БД (пустой)
|
||||
4. При модификации синхронизируется с базой (пишет в нее) - нужно либо снаружи вызывать сохранение, либо инкапсулировать запись
|
||||
(инкапсулировать запись - сильно усложнит код)
|
||||
5. Для работы выдает КОПИЮ данных, соответственно, при сохранении должен ее синхронизировать???
|
||||
Пока решили, что будем выдавать ссылку на объект в сессии, при этом нужно блок кода синхронизировать CFLOCK
|
||||
При этом 2 проблемы:
|
||||
- некрасивый код (лок на сессию руками, связь совершенно неявная)
|
||||
- избыточная блокировка
|
||||
- избыточное сохранение (можно было бы проверять на изменение)
|
||||
чтобы действовать без блокировки, можно экспортировать объект в безопасный scope - переменные или реквест
|
||||
Альтернативный синтаксис вызова
|
||||
<m:settings key="page_name" output="settings"><!--- implicit get --->
|
||||
<m:sortparam sort=#settings.sort# fieldcount=9 sortlength=2/><!--- any modification --->
|
||||
</m:settings><!--- implicit sync --->
|
||||
|
||||
|
||||
Разрешение конфликта версий при обновлении софта
|
||||
Конфликт версий: код требует параметр, отсутствующий в сохраненной версии. Например, одна версия фильтра сохранила свои настройки,
|
||||
а другая, более новая, пытается прочесть несуществующий ключ.
|
||||
Другой вариант - несуществующее поле (убранное в очередной версии) - этот конфликт проявится только при запросе к БД.
|
||||
Варианты решения
|
||||
- создание параметра с дефолтными значениями при запросе (во все запросы добавить значения по умолчанию). Недостаток - возможно создание помойки
|
||||
- контроль номера версии. Выглядит сложным в сопровождении
|
||||
*** Возможно, это была плохая идея - разрешить доступ по длинному ключу. Когда доставали и записывали целиком объект, было меньше возможностей для ошибок
|
||||
--->
|
||||
|
||||
<!---*** обработка вызова get-sync c закрывающим тегом --->
|
||||
<cfparam name="ATTRIBUTES.action" type="string" default=""/>
|
||||
<cfparam name="obj" type="struct" default=#structNew()#/>
|
||||
<cfparam name="doSyncOnClose" type="boolean" default="No"/>
|
||||
|
||||
<cfif thisTag.executionMode IS "start">
|
||||
<cfif ATTRIBUTES.action IS "">
|
||||
<cfset ATTRIBUTES.action="get"/>
|
||||
<cfset doSyncOnClose="Yes"/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfif doSyncOnClose>
|
||||
<cfset ATTRIBUTES.action="sync"/>
|
||||
<cfelse>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfset useDB="false"/><!--- --->
|
||||
|
||||
<cfswitch expression=#lCase(ATTRIBUTES.action)#>
|
||||
|
||||
<cfcase value="create"> <!--- конструктор --->
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<cfset session.settings = retrieveSettings()>
|
||||
</cflock>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="sync">
|
||||
<!--- Синхронизировать измененный объект (с сессией и персистенсом) --->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfif doSyncOnClose>
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<cfset obj=StructFind(CALLER, "#ATTRIBUTES.output#")/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfparam name="ATTRIBUTES.obj" type="struct">
|
||||
<cfset obj=#ATTRIBUTES.obj#/>
|
||||
</cfif>
|
||||
<!---<cfdump var=#obj#/>--->
|
||||
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<!--- помещаем объект на свое место в структуре --->
|
||||
<cfset container=#session.settings#>
|
||||
<cfset cnt = listLen("#ATTRIBUTES.key#", ".")>
|
||||
<cfloop index="i" from=1 to=#cnt#>
|
||||
<cfset item = listGetAt(#ATTRIBUTES.key#, i, ".")>
|
||||
<cfif i EQ cnt><!--- last element --->
|
||||
<cfset structInsert(#container#, #item#, #obj#, "YES")>
|
||||
<cfelse>
|
||||
<!--- попытка перебдеть--->
|
||||
<cfif NOT structKeyExists(#container#,#item#)>
|
||||
<cfset structInsert(#container#,#item#,#structNew()#)>
|
||||
</cfif>
|
||||
<cfset container=structFind(container,item)>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cflock>
|
||||
<!--- сохраняем настройки в персистенс --->
|
||||
<cfset saveSettings(container)>
|
||||
</cfcase>
|
||||
|
||||
|
||||
<cfcase value="reset"><!--- *** надо было назвать reset_all или как-то так --->
|
||||
<!--- Сбросить все пользовательские настройки --->
|
||||
<cfset session.settings=structNew()>
|
||||
<cfset saveSettings(session.settings)>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="clear">
|
||||
<!--- Очистить секцию пользовательских настроек (sort или filter)--->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||
<cfset objName = listGetAt(ATTRIBUTES.key,1,".")>
|
||||
<cfif isDefined(targetFullName)>
|
||||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||
<cfset evaluate("structClear(#targetFullName#)")>
|
||||
<cfset obj = evaluate(#targetFullName#)>
|
||||
<cfmodule template="settings.cfm" action="sync" key=#ATTRIBUTES.key# obj=#obj#><!--- синхронизация при очистке используется ---><!--- хорошо ли это? А вообще надо?--->
|
||||
</cflock>
|
||||
</cfif>
|
||||
</cfcase>
|
||||
|
||||
<cfcase value="get">
|
||||
<!---
|
||||
Получить объект или секцию
|
||||
Обязательный параметр key - имя ключа в структуре, начинающееся с имени объекта, через точку
|
||||
В параметре output имя переменной, принимающей результат
|
||||
--->
|
||||
<cfparam name="ATTRIBUTES.key" type="string">
|
||||
<cfparam name="ATTRIBUTES.output" type="string">
|
||||
|
||||
<cfif NOT isDefined("session.settings")><!--- *** container--->
|
||||
<cfmodule template="settings.cfm" action="create"/>
|
||||
</cfif>
|
||||
|
||||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||
<cfset objName = listGetAt(ATTRIBUTES.key, 1, ".")>
|
||||
<cfif (NOT structKeyExists(#session.settings#, objName))
|
||||
OR structIsEmpty(structFind(#session.settings#, objName))>
|
||||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/>
|
||||
<!--- несколько неуклюже, лучше просто возвращать объект и потом класть в сессию --->
|
||||
</cfif>
|
||||
<cftry>
|
||||
<cfset obj=evaluate(targetFullName)>
|
||||
<cfcatch type="ANY">
|
||||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/><!---*** грубо --->
|
||||
<cfset obj=evaluate(targetFullName)>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#", #duplicate(obj)#)>
|
||||
</cfcase>
|
||||
<cfdefaultcase>
|
||||
<cfthrow message="Unsupported action = #ATTRIBUTES.action# in settings.cfm">
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<!---***debug <cfoutput>#ATTRIBUTES.action# #thisTag.executionMode# #thisTag.hasendtag#</cfoutput>--->
|
||||
|
||||
|
||||
|
||||
<!--- --------------------------------------------------------------------------------- --->
|
||||
<!--- private methods --->
|
||||
<cffunction name="retrieveSettings" access="private">
|
||||
<cfif useDB><!--- можно перегрузить методы для разных провайдеров персистенса, но пока смысла в этом не видно, код достаточно примитивен и управляем --->
|
||||
<cfquery name="qGet" datasource="#request.DS#">
|
||||
select settings from usr where usr_id='#request.usr_id#'
|
||||
</cfquery>
|
||||
<cfif len(qGet.settings) GT 0>
|
||||
<cftry>
|
||||
<CFWDDX ACTION='wddx2cfml' input=#qGet.settings# output='settings'/>
|
||||
<cfreturn #settings#/>
|
||||
<cfcatch type="Any">Err:
|
||||
<cfoutput>#cfcatch.message#</cfoutput>
|
||||
<cfabort/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfreturn #structNew()#/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="saveSettings" access="private">
|
||||
<cfargument name=settings>
|
||||
|
||||
<cfif !isNumeric(request.usr_id)
|
||||
OR !(request.usr_id GT 0)
|
||||
OR request.usr_id EQ request.ANONYMOUS_USR_ID
|
||||
OR request.usr_id EQ request.UNDEFINED_USR_ID>
|
||||
<cfreturn />
|
||||
</cfif>
|
||||
|
||||
<cfif useDB>
|
||||
<cfquery name="qCheckExistence" datasource="#request.DS#">
|
||||
select count(*) as cnt from usr_serrings where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||
</cfquery>
|
||||
|
||||
<CFWDDX ACTION='cfml2wddx' input=#session.settings# output='strSettings'/>
|
||||
|
||||
<cfif qCheckExistence.cnt EQ 0>
|
||||
<cfquery name="qSave" datasource="#request.DS#">
|
||||
insert int usr_settings (usr_id, settings)
|
||||
values(<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>, <cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>)
|
||||
</cfquery>
|
||||
<cfelse>
|
||||
<cfquery name="qSave" datasource="#request.DS#">
|
||||
update usr_settings
|
||||
set settings=<cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>
|
||||
where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||
</cfquery>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<!--- do nothing --->
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<cfsilent>
|
||||
<!--- Управляемая имитация cfsilent (с возможностью отключения для отладки) --->
|
||||
<cfif thisTag.executionMode IS "end">
|
||||
<cfparam name="ATTRIBUTES.silent" type="boolean" default="Yes"/>
|
||||
<cfif ATTRIBUTES.silent>
|
||||
<cfset thisTag.generatedContent=""/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,76 @@
|
||||
<cfsilent>
|
||||
<cfparam name="ATTRIBUTES.sort" type="struct">
|
||||
<cfparam name="ATTRIBUTES.fieldcount" type="numeric" default="0">
|
||||
<cfparam name="ATTRIBUTES.sortlength" type="numeric" default="1">
|
||||
|
||||
<cfif NOT isDefined("ATTRIBUTES.sort.sortArray")>
|
||||
<cfset structInsert(ATTRIBUTES.sort,"sortArray",arrayNew(1),"TRUE")>
|
||||
</cfif>
|
||||
<cfset sortArray=ATTRIBUTES.sort.sortArray>
|
||||
|
||||
<cfif evaluate("isDefined('CALLER.sort')")>
|
||||
<cfset evaluate("val=(CALLER.sort)")>
|
||||
<!--- parse --->
|
||||
<cfif val EQ "reset">
|
||||
<cfset ATTRIBUTES.sort.sortArray=arrayNew(1)>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
<cfset pos=find("a",val)>
|
||||
<cfif pos LE 0>
|
||||
<cfset pos=find("d",val)>
|
||||
</cfif>
|
||||
|
||||
<cfif pos LE 1>
|
||||
<cfexit>
|
||||
<cfelseif pos GT 1>
|
||||
<cfset fldNum=left(val,pos-1)>
|
||||
<cfset fldAsc=mid(val,pos,1)>
|
||||
<cfelse>
|
||||
<cfset fldNum=val>
|
||||
<cfset fldAsc="a">
|
||||
</cfif>
|
||||
<!---/parse --->
|
||||
<!--- validate --->
|
||||
<cfif NOT isNumeric(fldNum)>
|
||||
<cfexit>
|
||||
</cfif>
|
||||
<cfif (ATTRIBUTES.fieldcount GT 0) AND (fldNum GT ATTRIBUTES.fieldcount)>
|
||||
<cfexit>
|
||||
</cfif>
|
||||
<!---/validate --->
|
||||
<cfelse>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cfset sortItem=structNew()>
|
||||
<cfset structInsert(sortItem,"fld",fldNum)>
|
||||
<cfset structInsert(sortItem,"asc",(fldAsc EQ "a"))>
|
||||
|
||||
<!--- drop all occurences of this field --->
|
||||
<cfset i=1>
|
||||
<cfloop condition="i LE arrayLen(sortArray)">
|
||||
<cftry>
|
||||
<cfif sortArray[i].fld EQ sortItem.fld>
|
||||
<cfset arrayDeleteAt(sortArray,i)>
|
||||
</cfif>
|
||||
<cfcatch type="Any">
|
||||
<cfset arrayDeleteAt(sortArray,i)>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfset i=i+1>
|
||||
</cfloop>
|
||||
|
||||
<!--- insert item --->
|
||||
<cfset arrayPrepend(sortArray, sortItem)>
|
||||
|
||||
<!--- truncate array--->
|
||||
<cfloop condition="arrayLen(sortArray) GT ATTRIBUTES.sortlength">
|
||||
<cfset arrayDeleteAt(sortArray,arrayLen(sortArray))>
|
||||
</cfloop>
|
||||
|
||||
<!--- save --->
|
||||
<cfset ATTRIBUTES.sort.sortArray=sortArray>
|
||||
<!---/save --->
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
</cfsilent>
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<cfsilent><!--- утилита для передачи стека вызовов через URL --->
|
||||
<!---v1.01 14:00 03.04.2019 added output param thisURL--->
|
||||
<!---v1.02 06:16 26.12.2019 symbols -._ are not encoded --->
|
||||
|
||||
<cfparam name="ATTRIBUTES.thisUrl" type="string" default=""><!--- текущий URL без трэка --->
|
||||
<cfparam name="ATTRIBUTES.output" type="string" default="tr">
|
||||
<cfparam name="ATTRIBUTES.input" type="string" default="track"><!--- имя параметра в URL или FORM, в которой лежит текущий трэк --->
|
||||
<cfparam name="ATTRIBUTES.urlParamName" type="string" default="#ATTRIBUTES.input#">
|
||||
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string">
|
||||
|
||||
<!---<cffunction name="urlSafeWrap">
|
||||
<cfargument name="s" type="string"/>
|
||||
<cfreturn replace(URLEncodedFormat(s), "%", "!", "ALL")/>
|
||||
</cffunction>--->
|
||||
<!---<cffunction name="urlSafeWrap">
|
||||
<cfargument name="s" type="string"/>
|
||||
<cfreturn
|
||||
replace(
|
||||
replaceList(
|
||||
URLEncodedFormat(s),
|
||||
"#URLEncodedFormat('_')#,#URLEncodedFormat('.')#,#URLEncodedFormat('-')#,#URLEncodedFormat('~')#,#URLEncodedFormat('=')#",
|
||||
"_,.,-,~,="
|
||||
),
|
||||
"%", "!", "ALL"
|
||||
)/>
|
||||
</cffunction>--->
|
||||
|
||||
<cffunction name="urlSafeWrap">
|
||||
<cfargument name="s" type="string"/>
|
||||
<cfreturn
|
||||
replace(
|
||||
replaceList(
|
||||
URLEncodedFormat(s),
|
||||
"#URLEncodedFormat('_')#,#URLEncodedFormat('.')#,#URLEncodedFormat('-')#",
|
||||
"_,.,-"
|
||||
),
|
||||
"%", "!", "ALL"
|
||||
)/><!---unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" https://www.ietf.org/rfc/rfc3986.txt--->
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="urlSafeUnWrap">
|
||||
<cfargument name="s" type="string"/>
|
||||
<cfreturn URLDecode(replace(s, "!", "%","ALL"))/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cfif StructKeyExists(form, ATTRIBUTES.input)>
|
||||
<cfset track=structFind(form, ATTRIBUTES.input)/>
|
||||
<cfelseif StructKeyExists(url, ATTRIBUTES.input)>
|
||||
<cfset track=structFind(url, ATTRIBUTES.input)/>
|
||||
<cfelse>
|
||||
<cfset track=""/>
|
||||
</cfif>
|
||||
|
||||
<!--- для предохранения спецсимволов (особенно запятых) в урле используется заворачивание в URLEncodedFormat. ColdFusion (по крайней мере старые версии) не умеет возвращать множественные значения иначе, как списком через запятую. Чтобы избежать непрошенного разворачивания URLEncodedFormat при отправке GETом, мы заменяем % на !.
|
||||
Реализовал вариант с заворачиванием в GZIP, работает неплохо, но вариант с URLEncodedFormat сохраняет хоть какую-то читабельность.
|
||||
Можно было бы кодировать в base64. Или вообще хранить на сервере в сессии, а передавать хэш - но непонятно, когда можно забыть этот урл, а накапливать их зря в сессии не хочется.
|
||||
Попробовал Base64 - очень плохо, все размножается при повторном кодировании и начинает виснуть.
|
||||
--->
|
||||
|
||||
<cfset trackBack="">
|
||||
|
||||
<cfif listLen(track) GT 0>
|
||||
<cfset trackBack=ListDeleteAt(track, listLen(track))>
|
||||
</cfif>
|
||||
|
||||
<cfset trackForward=ListAppend(track, urlSafeWrap(ATTRIBUTES.thisUrl))/>
|
||||
<cfset backUrlBase=urlSafeUnWrap(listLast(track))/>
|
||||
|
||||
<cfif len(backUrlBase) GT 0>
|
||||
<cfif len(trackBack) GT 0>
|
||||
<cfif find("?", backUrlBase) GT 0>
|
||||
<!--- has query string --->
|
||||
<cfset backUrl="#backUrlBase#&#ATTRIBUTES.urlParamName#=#trackBack#"/>
|
||||
<cfelse>
|
||||
<!--- no query string --->
|
||||
<cfset backUrl="#backUrlBase#?#ATTRIBUTES.urlParamName#=#trackBack#"/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset backUrl="#backUrlBase#"/>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfset backUrl="#ATTRIBUTES.defaultBackUrl#"/>
|
||||
</cfif>
|
||||
|
||||
<cfif NOT (find("?", ATTRIBUTES.thisUrl) GT 0)>
|
||||
<cfset selfUrl="#ATTRIBUTES.thisUrl#?#ATTRIBUTES.urlParamName#=#track#"/>
|
||||
<cfelse>
|
||||
<cfset selfUrl="#ATTRIBUTES.thisUrl#&#ATTRIBUTES.urlParamName#=#track#"/>
|
||||
</cfif>
|
||||
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#", structNew())/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fw", trackForward)/><!--- значение стэка: &track=#tr.fw# --->
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fwx", "#ATTRIBUTES.urlParamName#=#trackForward#")/><!--- готовый суффикс для URL: &#tr.fwx#--->
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.bk", trackBack)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.backUrl", backUrl)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.self", track)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.selfUrl", selfUrl)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.thisUrl", ATTRIBUTES.thisUrl)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.len", listLen(track))/>
|
||||
|
||||
<cfexit method="exitTag"/>
|
||||
</cfsilent>
|
||||
@@ -0,0 +1,40 @@
|
||||
<cfparam name="ATTRIBUTES.field"/>
|
||||
<cfparam name="ATTRIBUTES.output"/>
|
||||
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.ok", "No")/>
|
||||
|
||||
<cfif !structKeyExists(FORM, ATTRIBUTES.field)>
|
||||
<cfexit method="exittag"/>
|
||||
</cfif>
|
||||
|
||||
<cftry>
|
||||
<cffile
|
||||
action="upload"
|
||||
filefield="#ATTRIBUTES.field#"
|
||||
destination="#GetTempDirectory()#/"
|
||||
nameconflict="MAKEUNIQUE"
|
||||
/>
|
||||
<cfcatch>
|
||||
<!---<cfdump var=#cfcatch#/><cfrethrow/>--->
|
||||
<cfexit method="exittag"/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cfset pathOnServer="#cffile.ServerDirectory#/#cffile.ServerFile#"/>
|
||||
<cfset contentType="#cffile.ContentType#/#cffile.ContentSubType#"/>
|
||||
<cfset fileName="#cffile.ClientFile#"/>
|
||||
|
||||
<cffile
|
||||
action="ReadBinary"
|
||||
file="#pathOnServer#"
|
||||
variable="binaryData"
|
||||
/>
|
||||
|
||||
<cffile action="DELETE" file="#pathOnServer#"/>
|
||||
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fileName", "#fileName#")/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.contentType", "#contentType#")/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.binaryData", #binaryData#)/>
|
||||
<cfset setVariable("CALLER.#ATTRIBUTES.output#.ok", "Yes")/>
|
||||
|
||||
<cfexit method="exittag"/>
|
||||
Reference in New Issue
Block a user