SMAM (short for Send Me A Mail) is a free (as in freedom) contact form embedding software.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Consts for readability
  2. const REQUIRED = true;
  3. const NOT_REQUIRED = false;
  4. var prefix = 'form'
  5. var items = {
  6. name: 'name',
  7. addr: 'addr',
  8. subj: 'subj',
  9. text: 'text',
  10. };
  11. var DOMFields = {};
  12. var server = getServer();
  13. var token = "";
  14. var labels = true;
  15. var lang = [];
  16. var customFields = {};
  17. var xhr = {
  18. customFields: new XMLHttpRequest(),
  19. lang: new XMLHttpRequest(),
  20. token: new XMLHttpRequest(),
  21. send: new XMLHttpRequest()
  22. }
  23. // XHR callbacks
  24. xhr.customFields.onreadystatechange = function() {
  25. if(xhr.customFields.readyState == XMLHttpRequest.DONE) {
  26. customFields = JSON.parse(xhr.customFields.responseText);
  27. for(let field in customFields) {
  28. customFields[field].name = field;
  29. }
  30. }
  31. };
  32. xhr.token.onreadystatechange = function() {
  33. if(xhr.token.readyState == XMLHttpRequest.DONE) {
  34. token = xhr.token.responseText;
  35. }
  36. };
  37. xhr.lang.onreadystatechange = function() {
  38. if(xhr.lang.readyState == XMLHttpRequest.DONE) {
  39. let response = JSON.parse(xhr.lang.responseText);
  40. lang = response.translations;
  41. labels = response.labels;
  42. }
  43. };
  44. xhr.send.onreadystatechange = function() {
  45. if(xhr.send.readyState == XMLHttpRequest.DONE) {
  46. let status = document.getElementById('form_status');
  47. status.setAttribute('class', '');
  48. if(xhr.send.status === 200) {
  49. cleanForm();
  50. status.setAttribute('class', 'success');
  51. status.innerHTML = lang.send_status_success;
  52. } else {
  53. status.setAttribute('class', 'failure');
  54. status.innerHTML = lang.send_status_failure;
  55. }
  56. }
  57. };
  58. // Returns the server's base URI based on the user's script tag
  59. // return: the SMAM server's base URI
  60. function getServer() {
  61. var scripts = document.getElementsByTagName('script');
  62. // Parsing all the <script> tags to find the URL to our file
  63. for(var i = 0; i < scripts.length; i++) {
  64. let script = scripts[i];
  65. if(script.src) {
  66. let url = script.src;
  67. // This should be our script
  68. if(url.match(/form\.js$/)) {
  69. // Port has been found
  70. return url.match(/^(https?:\/\/[^\/]+)/)[1];
  71. }
  72. }
  73. }
  74. }
  75. // Creates a form
  76. // id: HTML identifier of the document's block to create the form into
  77. // return: nothing
  78. function generateForm(id) {
  79. // Get translated strings
  80. getLangSync();
  81. // Get custom fields if defined in the configuration
  82. getCustomFieldsSync();
  83. var el = document.getElementById(id);
  84. // Set the form's behaviour
  85. el.setAttribute('onsubmit', 'sendForm(); return false;');
  86. // Add an empty paragraph for status
  87. var status = document.createElement('p');
  88. status.setAttribute('id', 'form_status');
  89. el.appendChild(status);
  90. DOMFields = {
  91. name: getField({
  92. name: items.name,
  93. label: lang.form_name_label,
  94. type: 'text'
  95. }, REQUIRED),
  96. addr: getField({
  97. name: items.addr,
  98. label: lang.form_addr_label,
  99. type: 'email'
  100. }, REQUIRED),
  101. subj: getField({
  102. name: items.subj,
  103. label: lang.form_subj_label,
  104. type: 'text'
  105. }, REQUIRED),
  106. text: getField({
  107. name: items.text,
  108. label: lang.form_mesg_label,
  109. type: 'textarea'
  110. }, REQUIRED)
  111. };
  112. // Adding custom fields
  113. for(let fieldName in customFields) {
  114. let field = customFields[fieldName];
  115. DOMFields[fieldName] = getField(field, NOT_REQUIRED);
  116. }
  117. // Adding nodes to document
  118. for(let field in DOMFields) {
  119. el.appendChild(DOMFields[field]);
  120. }
  121. // Adding submit button
  122. el.appendChild(getSubmitButton('form_subm', lang.form_subm_label));
  123. // Retrieve the token from the server
  124. getToken();
  125. }
  126. function getField(fieldInfos, required) {
  127. var block = document.createElement('div');
  128. block.setAttribute('id', fieldInfos.name);
  129. let field = {};
  130. switch(fieldInfos.type) {
  131. case 'text': field = getTextField(fieldInfos, required);
  132. break;
  133. case 'textarea': field = getTextarea(fieldInfos, required);
  134. break;
  135. case 'email': field = getEmailField(fieldInfos, required);
  136. break;
  137. case 'select': field = getSelectField(fieldInfos, required);
  138. break;
  139. }
  140. if(labels) {
  141. block.appendChild(getLabel(fieldInfos.label, field.id));
  142. }
  143. block.appendChild(field);
  144. return block;
  145. }
  146. // Returns a label
  147. // content: label's inner content
  148. // id: field HTML identifier
  149. // return: a label node the field's description
  150. function getLabel(content, id) {
  151. var label = document.createElement('label');
  152. label.setAttribute('for', id);
  153. label.innerHTML = content;
  154. return label;
  155. }
  156. function getSelectField(fieldInfos, required) {
  157. let field = document.createElement('select');
  158. if(required) {
  159. field.setAttribute('required', 'required');
  160. }
  161. field.setAttribute('id', prefix + '_' + fieldInfos.name + '_select');
  162. let index = 0;
  163. // Add all options to select
  164. for(let choice of fieldInfos.options) {
  165. let option = document.createElement('option');
  166. option.setAttribute('value', index);
  167. option.innerHTML = choice;
  168. field.appendChild(option);
  169. index++;
  170. }
  171. return field
  172. }
  173. function getTextField(fieldInfos, required) {
  174. return getBaseInputField(fieldInfos, required, 'text');
  175. }
  176. function getEmailField(fieldInfos, required) {
  177. return getBaseInputField(fieldInfos, required, 'email');
  178. }
  179. function getBaseInputField(fieldInfos, required, type) {
  180. let field = getBaseField(fieldInfos, required, 'input')
  181. field.setAttribute('type', type);
  182. return field;
  183. }
  184. function getTextarea(fieldInfos, required) {
  185. return getBaseField(fieldInfos, required, 'textarea');
  186. }
  187. function getBaseField(fieldInfos, required, tag) {
  188. let field = document.createElement(tag);
  189. if(required) {
  190. field.setAttribute('required', 'required');
  191. }
  192. field.setAttribute('placeholder', fieldInfos.label);
  193. field.setAttribute('id', prefix + '_' + fieldInfos.name + '_' + tag);
  194. return field;
  195. }
  196. // Returns a submit button
  197. // id: button HTML identifier
  198. // text: button text
  199. // return: a div node containing the button
  200. function getSubmitButton(id, text) {
  201. var submit = document.createElement('div');
  202. submit.setAttribute('id', id);
  203. var button = document.createElement('button');
  204. button.setAttribute('type', 'submit');
  205. button.setAttribute('id', id + '_btn');
  206. button.innerHTML = text;
  207. submit.appendChild(button);
  208. return submit;
  209. }
  210. // Send form data through the XHR object
  211. // return: nothing
  212. function sendForm() {
  213. // Clear status
  214. let status = document.getElementById('form_status');
  215. status.setAttribute('class', 'sending');
  216. status.innerHTML = lang.send_status_progress;
  217. xhr.send.open('POST', server + '/send');
  218. xhr.send.setRequestHeader('Content-Type', 'application/json');
  219. xhr.send.send(JSON.stringify(getFormData()));
  220. // Get a new token
  221. getToken();
  222. }
  223. // Fetch form inputs from HTML elements
  224. // return: an object containing all the user's input
  225. function getFormData() {
  226. let data = {};
  227. data.token = token;
  228. data.custom = {};
  229. // Custom fields
  230. // Select the field
  231. let index = 0;
  232. if(labels) {
  233. index = 1;
  234. }
  235. for(let field in DOMFields) {
  236. let el = DOMFields[field].children[index];
  237. if(field in customFields) {
  238. data.custom[field] = el.value;
  239. } else {
  240. data[field] = el.value;
  241. }
  242. }
  243. return data;
  244. }
  245. // Empties the form fields
  246. // return: nothing
  247. function cleanForm() {
  248. document.getElementById(prefix + '_' + items.name + '_input').value = '';
  249. document.getElementById(prefix + '_' + items.addr + '_input').value = '';
  250. document.getElementById(prefix + '_' + items.subj + '_input').value = '';
  251. document.getElementById(prefix + '_' + items.text + '_textarea').value = '';
  252. }
  253. // Asks the server for a token
  254. // return: nothing
  255. function getToken() {
  256. xhr.token.open('GET', server + '/register');
  257. xhr.token.send();
  258. }
  259. // Asks the server for translated strings to display
  260. // return: notghing
  261. function getLangSync() {
  262. xhr.lang.open('GET', server + '/lang', false);
  263. xhr.lang.send();
  264. }
  265. // Asks the server for the custom fields if there's one or more set in the
  266. // configuration file
  267. // return: nothing
  268. function getCustomFieldsSync() {
  269. xhr.customFields.open('GET', server + '/fields', false);
  270. xhr.customFields.send();
  271. }