initial
This commit is contained in:
@@ -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. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
|
||||
Остальное оставить вызывающему тегу?
|
||||
--->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user