├── .gitignore ├── README.md ├── demo.png ├── docs ├── aliases.html └── package.html ├── dscanner.ini ├── dub.json ├── source └── webconfig │ ├── aliases.d │ └── package.d └── styles ├── material.css └── material.min.css /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | dub.selections.json 7 | *.dll 8 | *.exe 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-config 2 | 3 | [![Dub version](https://img.shields.io/dub/v/web-config.svg)](https://code.dlang.org/packages/web-config) 4 | [![Dub downloads](https://img.shields.io/dub/dt/web-config.svg)](https://code.dlang.org/packages/web-config) 5 | 6 | A HTML settings/properties generator using D's Compile Time magic. 7 | 8 | By default there is **no CSS**. Inputs will be ugly because the default generator does not emit any line breaks. You could make a custom generator and call the `DefaultInputGenerator` methods and add a line break but the recommended way is by making the ``.format(name.encode, 933 | type.encode, value.encode, raw) ~ errorString(success); 934 | } 935 | 936 | /// Called for textareas 937 | static string textarea(string name, string value, string raw, bool success, string[] classes) 938 | { 939 | if (!success) 940 | classes ~= "error"; 941 | const className = classes.length ? ` class="` ~ classes.join(" ") ~ `"` : ""; 942 | return `%s%s`.format(name.encode, 943 | raw, value.encode) ~ errorString(success); 944 | } 945 | 946 | /// Called for boolean values 947 | static string checkbox(string name, bool checked, string raw, bool success, string[] classes) 948 | { 949 | classes ~= "checkbox"; 950 | if (!success) 951 | classes ~= "error"; 952 | const className = ` class="` ~ classes.join(" ") ~ `"`; 953 | return `%s`.format(checked 954 | ? "checked" : "", raw, name.encode) ~ errorString(success); 955 | } 956 | 957 | /// Called for enums disabled as select (you need to iterate over the enum members) 958 | static string dropdownList(Enum, translations...)(string name, Enum value, 959 | string raw, bool success, string[] classes) 960 | { 961 | classes ~= "select"; 962 | if (!success) 963 | classes ~= "error"; 964 | const className = ` class="` ~ classes.join(" ") ~ `"`; 965 | string ret = `` ~ name.encode ~ ``; 966 | foreach (member; __traits(allMembers, Enum)) 967 | ret ~= ``; 971 | return ret ~ "" ~ errorString(success); 972 | } 973 | 974 | /// Called for enums displayed as list of radio boxes (you need to iterate over the enum members) 975 | static string optionList(Enum, translations...)(string name, Enum value, 976 | string raw, bool success, string[] classes) 977 | { 978 | classes ~= "checkbox options"; 979 | if (!success) 980 | classes ~= "error"; 981 | const className = ` class="` ~ classes.join(" ") ~ `"`; 982 | string ret = `` ~ name.encode ~ ""; 983 | foreach (member; __traits(allMembers, Enum)) 984 | ret ~= checkbox(__traits(getMember, Enum, member).translateEnum!(Enum, 985 | translations)(member.makeHumanName), value == __traits(getMember, Enum, member), 986 | raw ~ ` value="` ~ (cast(OriginalType!Enum) __traits(getMember, Enum, 987 | member)).to!string.encode ~ `"`, true, classes).replace(`type="checkbox"`, 988 | `type="radio"`); 989 | return ret ~ `` ~ errorString(success); 990 | } 991 | 992 | /// Called for BitFlags displayed as list of checkboxes. 993 | static string checkboxList(Enum, translations...)(string name, 994 | BitFlags!Enum value, string raw, bool success, string[] classes) 995 | { 996 | classes ~= "checkbox flags"; 997 | if (!success) 998 | classes ~= "error"; 999 | const className = ` class="` ~ classes.join(" ") ~ `"`; 1000 | string ret = `` ~ name.encode ~ ""; 1001 | foreach (member; __traits(allMembers, Enum)) 1002 | ret ~= checkbox(__traits(getMember, Enum, member).translateEnum!(Enum, 1003 | translations)(member.makeHumanName), !!(value & __traits(getMember, Enum, 1004 | member)), raw ~ ` value="` ~ (cast(OriginalType!Enum) __traits(getMember, 1005 | Enum, member)).to!string.encode ~ `"`, true, classes); 1006 | return ret ~ `` ~ errorString(success); 1007 | } 1008 | } 1009 | 1010 | /// Adds type="email" to string types 1011 | enum emailSetting; 1012 | /// Adds type="url" to string types 1013 | enum urlSetting; 1014 | /// Adds type="password" to string types 1015 | enum passwordSetting; 1016 | /// Makes string types textareas 1017 | enum multilineSetting; 1018 | /// Adds type="range" to numeric types 1019 | enum rangeSetting; 1020 | /// Adds type="time" to string types 1021 | enum timeSetting; 1022 | /// Adds type="week" to string types 1023 | enum weekSetting; 1024 | /// Adds type="month" to string types 1025 | enum monthSetting; 1026 | /// Adds type="datetime-local" to string types 1027 | enum datetimeLocalSetting; 1028 | /// Adds type="date" to string types 1029 | enum dateSetting; 1030 | /// Adds type="color" to string types 1031 | enum colorSetting; 1032 | /// Adds disabled to any input 1033 | enum disabledSetting; 1034 | /// Adds required to any input 1035 | enum requiredSetting; 1036 | /// Disables automatic JS saving when changing the input 1037 | enum nonAutomaticSetting; 1038 | /// Changes a dropdown to a radio button list 1039 | enum optionsSetting; 1040 | 1041 | /// Changes the min="" attribute for numerical values 1042 | struct settingMin 1043 | { 1044 | /// 1045 | double min; 1046 | } 1047 | 1048 | /// Changes the max="" attribute for numerical values 1049 | struct settingMax 1050 | { 1051 | /// 1052 | double max; 1053 | } 1054 | 1055 | /// Changes the step="" attribute for numerical values 1056 | struct settingStep 1057 | { 1058 | /// 1059 | double step; 1060 | } 1061 | 1062 | /// Changes the min="" and max="" attribute for numerical values 1063 | struct settingRange 1064 | { 1065 | /// 1066 | double min, max; 1067 | } 1068 | 1069 | /// Changes the minlength="" and maxlength="" attribute for string values 1070 | struct settingLength 1071 | { 1072 | /// 1073 | int max, min; 1074 | } 1075 | 1076 | /// Changes the pattern="regex" attribute 1077 | struct settingPattern 1078 | { 1079 | /// 1080 | string regex; 1081 | } 1082 | 1083 | /// Changes the title="" attribute for custom error messages & tooltips 1084 | struct settingTitle 1085 | { 1086 | /// 1087 | string title; 1088 | } 1089 | 1090 | /// Overrides the label of the input 1091 | struct settingLabel 1092 | { 1093 | /// 1094 | string label; 1095 | } 1096 | 1097 | /// Sets the number of rows of a textarea 1098 | struct settingRows 1099 | { 1100 | /// 1101 | int count; 1102 | } 1103 | 1104 | /// Changes the label if the current language (using a WebInterface translation context) matches the given one. 1105 | /// You need at least vibe-d v0.8.1-alpha.3 to use this UDA. 1106 | struct settingTranslation 1107 | { 1108 | /// 1109 | string language; 1110 | /// 1111 | string label; 1112 | } 1113 | 1114 | /// Relables all enum member names for a language. Give `null` as first argument to change the default language 1115 | struct enumTranslation 1116 | { 1117 | /// 1118 | string language; 1119 | /// 1120 | string[] translations; 1121 | } 1122 | 1123 | /// Inserts raw HTML code before an element. 1124 | struct settingHTML 1125 | { 1126 | /// 1127 | string raw; 1128 | } 1129 | 1130 | /// Inserts raw CSS class name for an element. 1131 | struct settingClass 1132 | { 1133 | /// 1134 | string className; 1135 | } 1136 | 1137 | /// Changes how the form HTML template looks 1138 | struct formTemplate 1139 | { 1140 | /// Contains the std.format formattable template code. $(BR) 1141 | /// Arguments in order are: string action, string method, string formArguments, string html $(BR) 1142 | /// html is last so you can embed it using %4$s without throwing an orphan arguments exception. 1143 | string code; 1144 | } 1145 | 1146 | /// Sets the placeholder attribute for elements that support it 1147 | struct settingPlaceholder 1148 | { 1149 | /// 1150 | string placeholder; 1151 | } 1152 | 1153 | string translateEnum(T, translations...)(T value, string fallback) @safe 1154 | if (is(T == enum)) 1155 | { 1156 | static if (translations.length) 1157 | { 1158 | static if (is(typeof(language) == string)) 1159 | auto lang = (() @trusted => language)(); 1160 | enum NumEnumMembers = [EnumMembers!T].length; 1161 | foreach (i, other; EnumMembers!T) 1162 | { 1163 | if (other == value) 1164 | { 1165 | string ret = null; 1166 | foreach (translation; translations) 1167 | { 1168 | static assert(translation.translations.length == NumEnumMembers, 1169 | "Translation missing some values. Set them to null to skip"); 1170 | if (translation.language is null && ret is null) 1171 | ret = translation.translations[i]; 1172 | else static if (is(typeof(language) == string)) 1173 | { 1174 | if (translation.language == lang) 1175 | ret = translation.translations[i]; 1176 | } 1177 | } 1178 | return ret is null ? fallback : ret; 1179 | } 1180 | } 1181 | } 1182 | else static if (__traits(compiles, __traits(getAttributes, 1183 | __traits(getMember, T, __traits(allMembers, T)[0])))) 1184 | { 1185 | foreach (i, other; __traits(allMembers, T)) 1186 | { 1187 | if (__traits(getMember, T, other) == value) 1188 | { 1189 | static if (is(typeof(language) == string)) 1190 | auto lang = (() @trusted => language)(); 1191 | string ret = null; 1192 | foreach (attr; __traits(getAttributes, __traits(getMember, T, other))) 1193 | { 1194 | static if (is(typeof(attr) == settingTranslation)) 1195 | { 1196 | if (attr.language is null && ret is null) 1197 | ret = attr.label; 1198 | else static if (is(typeof(language) == string)) 1199 | { 1200 | if (attr.language == lang) 1201 | ret = attr.label; 1202 | } 1203 | } 1204 | } 1205 | return ret is null ? fallback : ret; 1206 | } 1207 | } 1208 | } 1209 | return fallback; 1210 | } 1211 | 1212 | /// Contains a updateSetting(input) function which automatically sends changes to the server. 1213 | enum DefaultJavascriptCode = q{}; 1304 | 1305 | enum DefaultFormTemplate = `
%s
`; 1306 | -------------------------------------------------------------------------------- /styles/material.css: -------------------------------------------------------------------------------- 1 | .settings label { 2 | display: block; 3 | padding: 8px 4px; 4 | } 5 | 6 | .settings label:not(.checkbox) > span { 7 | font-size: 0.7em; 8 | display: block; 9 | color: rgba(0, 0, 0, 0.54); 10 | } 11 | 12 | .settings label:not(.checkbox) > input { 13 | border: none; 14 | background: transparent; 15 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 16 | margin-bottom: 1px; 17 | transition: border-bottom 0.1s ease-out, margin-bottom 0.1s ease-out, color 0.1s ease-out; 18 | display: block; 19 | width: 100%; 20 | padding: 8px; 21 | } 22 | 23 | .settings label:not(.checkbox) > input[type=color], .settings label:not(.checkbox) > input[type=color]:disabled, .settings label:not(.checkbox) > input[type=color]:focus, .settings label.error:not(.checkbox) > input[type=color] { 24 | padding: 0; 25 | border-bottom: none; 26 | margin-bottom: 0; 27 | width: 50px; 28 | } 29 | 30 | .settings label:not(.checkbox) > input[type=color]:disabled { 31 | opacity: 0.5; 32 | } 33 | 34 | .settings label:not(.checkbox) > input:disabled { 35 | color: rgba(0, 0, 0, 0.38); 36 | border-bottom: 1px dashed rgba(0, 0, 0, 0.12); 37 | background: transparent; 38 | } 39 | 40 | .settings label:not(.checkbox) > input:focus { 41 | border-bottom: 2px solid #3F51B5; 42 | margin-bottom: 0; 43 | outline: none; 44 | } 45 | 46 | .settings label.error:not(.checkbox) > input { 47 | border-bottom: 2px solid red; 48 | margin-bottom: 0; 49 | outline: none; 50 | } 51 | 52 | .settings label.error, .settings label > span.error { 53 | color: red; 54 | } 55 | 56 | .settings input[type=submit] { 57 | padding: 8px 32px; 58 | margin: 8px; 59 | } -------------------------------------------------------------------------------- /styles/material.min.css: -------------------------------------------------------------------------------- 1 | .settings label{display:block;padding:8px 4px}.settings label:not(.checkbox)>span{font-size:.7em;display:block;color:rgba(0,0,0,.54)}.settings label:not(.checkbox)>input{border:none;background:0 0;border-bottom:1px solid rgba(0,0,0,.12);margin-bottom:1px;transition:border-bottom .1s ease-out,margin-bottom .1s ease-out,color .1s ease-out;display:block;width:100%;padding:8px}.settings label.error:not(.checkbox)>input[type=color],.settings label:not(.checkbox)>input[type=color],.settings label:not(.checkbox)>input[type=color]:disabled,.settings label:not(.checkbox)>input[type=color]:focus{padding:0;border-bottom:none;margin-bottom:0;width:50px}.settings label:not(.checkbox)>input[type=color]:disabled{opacity:.5}.settings label:not(.checkbox)>input:disabled{color:rgba(0,0,0,.38);border-bottom:1px dashed rgba(0,0,0,.12);background:0 0}.settings label:not(.checkbox)>input:focus{border-bottom:2px solid #3F51B5;margin-bottom:0;outline:0}.settings label.error:not(.checkbox)>input{border-bottom:2px solid red;margin-bottom:0;outline:0}.settings label.error,.settings label>span.error{color:red}.settings input[type=submit]{padding:8px 32px;margin:8px} --------------------------------------------------------------------------------