initial draft

This commit is contained in:
msyu
2025-06-02 16:16:51 +03:00
commit 529334d03b
272 changed files with 22684 additions and 0 deletions
+398
View File
@@ -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>
+398
View File
@@ -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>
+24
View File
@@ -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>
+41
View File
@@ -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>
+305
View File
@@ -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#-&gt;#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)#-&gt;#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. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
Остальное оставить вызывающему тегу?
--->