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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. var prefix = 'form'
  2. var items = {
  3. name: 'name',
  4. addr: 'addr',
  5. subj: 'subj',
  6. text: 'text',
  7. };
  8. var DOMFields = {};
  9. var server = getServer();
  10. var token = "";
  11. var labels = true;
  12. var lang = [];
  13. var customFields = {};
  14. var xhr = {
  15. customFields: new XMLHttpRequest(),
  16. lang: new XMLHttpRequest(),
  17. token: new XMLHttpRequest(),
  18. send: new XMLHttpRequest()
  19. }
  20. // XHR callbacks
  21. xhr.customFields.onreadystatechange = function() {
  22. if(xhr.customFields.readyState == XMLHttpRequest.DONE) {
  23. customFields = JSON.parse(xhr.customFields.responseText);
  24. for(let field in customFields) {
  25. customFields[field].name = field;
  26. }
  27. }
  28. };
  29. xhr.token.onreadystatechange = function() {
  30. if(xhr.token.readyState == XMLHttpRequest.DONE) {
  31. token = xhr.token.responseText;
  32. }
  33. };
  34. xhr.lang.onreadystatechange = function() {
  35. if(xhr.lang.readyState == XMLHttpRequest.DONE) {
  36. let response = JSON.parse(xhr.lang.responseText);
  37. lang = response.translations;
  38. labels = response.labels;
  39. }
  40. };
  41. xhr.send.onreadystatechange = function() {
  42. if(xhr.send.readyState == XMLHttpRequest.DONE) {
  43. let status = document.getElementById('form_status');
  44. status.setAttribute('class', '');
  45. if(xhr.send.status === 200) {
  46. cleanForm();
  47. status.setAttribute('class', 'success');
  48. status.innerHTML = lang.send_status_success;
  49. } else {
  50. status.setAttribute('class', 'failure');
  51. status.innerHTML = lang.send_status_failure;
  52. }
  53. }
  54. };
  55. // Returns the server's base URI based on the user's script tag
  56. // return: the SMAM server's base URI
  57. function getServer() {
  58. var scripts = document.getElementsByTagName('script');
  59. // Parsing all the <script> tags to find the URL to our file
  60. for(var i = 0; i < scripts.length; i++) {
  61. let script = scripts[i];
  62. if(script.src) {
  63. let url = script.src;
  64. // This should be our script
  65. if(url.match(/form\.js$/)) {
  66. // Port has been found
  67. return url.match(/^(https?:\/\/[^\/]+)/)[1];
  68. }
  69. }
  70. }
  71. }
  72. // Creates a form
  73. // id: HTML identifier of the document's block to create the form into
  74. // return: nothing
  75. function generateForm(id) {
  76. // Get translated strings
  77. getLangSync();
  78. // Get custom fields if defined in the configuration
  79. getCustomFieldsSync();
  80. var el = document.getElementById(id);
  81. // Set the form's behaviour
  82. el.setAttribute('onsubmit', 'sendForm(); return false;');
  83. // Add an empty paragraph for status
  84. var status = document.createElement('p');
  85. status.setAttribute('id', 'form_status');
  86. el.appendChild(status);
  87. // Default fields
  88. DOMFields = {
  89. name: getField({
  90. name: items.name,
  91. label: lang.form_name_label,
  92. type: 'text',
  93. required: true
  94. }),
  95. addr: getField({
  96. name: items.addr,
  97. label: lang.form_addr_label,
  98. type: 'email',
  99. required: true
  100. }),
  101. subj: getField({
  102. name: items.subj,
  103. label: lang.form_subj_label,
  104. type: 'text',
  105. required: true
  106. }),
  107. text: getField({
  108. name: items.text,
  109. label: lang.form_mesg_label,
  110. type: 'textarea',
  111. required: true
  112. })
  113. };
  114. // Adding custom fields
  115. for(let fieldName in customFields) {
  116. let field = customFields[fieldName];
  117. DOMFields[fieldName] = getField(field);
  118. }
  119. // Adding all nodes to document
  120. for(let field in DOMFields) {
  121. el.appendChild(DOMFields[field]);
  122. }
  123. // Adding submit button
  124. el.appendChild(getSubmitButton('form_subm', lang.form_subm_label));
  125. // Retrieve the token from the server
  126. getToken();
  127. }
  128. // Get the HTML element for a given field
  129. // fieldInfos: object describing the field
  130. // required: boolean on whether the field is required or optional
  131. // return: a block containing the field and a label describing it (if enabled)
  132. function getField(fieldInfos) {
  133. var block = document.createElement('div');
  134. block.setAttribute('id', fieldInfos.name);
  135. // Declare the variable first
  136. let field = {};
  137. // Easily add new supported input types
  138. switch(fieldInfos.type) {
  139. case 'textarea': field = getTextarea(fieldInfos);
  140. break;
  141. case 'select': field = getSelectField(fieldInfos);
  142. break;
  143. default: field = getInputField(fieldInfos);
  144. break;
  145. }
  146. // We need the input field's ID to bind it to the label, so we generate the
  147. // field first
  148. if(labels) {
  149. block.appendChild(getLabel(fieldInfos.label, field.id));
  150. }
  151. // Assemble the block and return it
  152. block.appendChild(field);
  153. return block;
  154. }
  155. // Returns a label
  156. // content: label's inner content
  157. // id: field HTML identifier
  158. // return: a label node the field's description
  159. function getLabel(content, id) {
  160. var label = document.createElement('label');
  161. label.setAttribute('for', id);
  162. label.innerHTML = content;
  163. return label;
  164. }
  165. // Returns a <select> HTML element
  166. // fieldInfos: object describing the field
  167. // required: boolean on whether the field is required or optional
  168. // return: a <select> element corresponding to the info passed as input
  169. function getSelectField(fieldInfos) {
  170. let field = document.createElement('select');
  171. // Set attributes when necessary
  172. if(fieldInfos.required) {
  173. field.setAttribute('required', 'required');
  174. }
  175. field.setAttribute('id', prefix + '_' + fieldInfos.name + '_select');
  176. let index = 0;
  177. // Add header option, useful if the field is required
  178. let header = document.createElement('option');
  179. // The value must be an empty string so the browser can block the submit
  180. // event if the field is required
  181. header.setAttribute('value', '');
  182. header.innerHTML = lang.form_select_header_option;
  183. field.appendChild(header);
  184. // Add all options to select
  185. for(let choice of fieldInfos.options) {
  186. let option = document.createElement('option');
  187. // Options' values are incremental numeric indexes
  188. option.setAttribute('value', index);
  189. // Set the value defined by the user
  190. option.innerHTML = choice;
  191. field.appendChild(option);
  192. // Increment the index
  193. index++;
  194. }
  195. return field
  196. }
  197. // Returns a <input> HTML element with desired type
  198. // fieldInfos: object describing the field
  199. // required: boolean on whether the field is required or optional
  200. // type: type of the input field (text, email, date...)
  201. // return: a <input> HTML element corresponding to the info passed as input
  202. function getInputField(fieldInfos, required) {
  203. let field = getBaseField(fieldInfos, required, 'input')
  204. field.setAttribute('type', fieldInfos.type);
  205. return field;
  206. }
  207. // Returns a <textarea> HTML element
  208. // fieldInfos: object describing the field
  209. // required: boolean on whether the field is required or optional
  210. // return: a <textarea> element corresponding to the info passed as input
  211. function getTextarea(fieldInfos, required) {
  212. return getBaseField(fieldInfos, required, 'textarea');
  213. }
  214. // Returns a base HTML element with generic info to be processed by functions at
  215. // higher level
  216. // fieldInfos: object describing the field
  217. // required: boolean on whether the field is required or optional
  218. // tag: the HTML tag the field element must have
  219. // return: a HTML element of the given tag with basic info given as input
  220. function getBaseField(fieldInfos, required, tag) {
  221. let field = document.createElement(tag);
  222. if(fieldInfos.required) {
  223. field.setAttribute('required', 'required');
  224. }
  225. field.setAttribute('placeholder', fieldInfos.label);
  226. field.setAttribute('id', prefix + '_' + fieldInfos.name + '_' + tag);
  227. return field;
  228. }
  229. // Returns a submit button
  230. // id: button HTML identifier
  231. // text: button text
  232. // return: a div node containing the button
  233. function getSubmitButton(id, text) {
  234. var submit = document.createElement('div');
  235. submit.setAttribute('id', id);
  236. var button = document.createElement('button');
  237. button.setAttribute('type', 'submit');
  238. button.setAttribute('id', id + '_btn');
  239. button.innerHTML = text;
  240. submit.appendChild(button);
  241. return submit;
  242. }
  243. // Send form data through the XHR object
  244. // return: nothing
  245. function sendForm() {
  246. // Clear status
  247. let status = document.getElementById('form_status');
  248. status.setAttribute('class', 'sending');
  249. status.innerHTML = lang.send_status_progress;
  250. xhr.send.open('POST', server + '/send');
  251. xhr.send.setRequestHeader('Content-Type', 'application/json');
  252. xhr.send.send(JSON.stringify(getFormData()));
  253. // Get a new token
  254. getToken();
  255. }
  256. // Fetch form inputs from HTML elements
  257. // return: an object containing all the user's input
  258. function getFormData() {
  259. let data = {};
  260. data.token = token;
  261. data.custom = {};
  262. // Select the field
  263. let index = 0;
  264. if(labels) {
  265. index = 1;
  266. }
  267. // Iterate over all the fields
  268. for(let field in DOMFields) {
  269. let el = DOMFields[field].children[index];
  270. // Do we need to push this field into default or custom fields?
  271. if(field in customFields) {
  272. data.custom[field] = el.value;
  273. } else {
  274. data[field] = el.value;
  275. }
  276. }
  277. return data;
  278. }
  279. // Empties the form fields
  280. // return: nothing
  281. function cleanForm() {
  282. // Select the field
  283. let index = 0;
  284. if(labels) {
  285. index = 1;
  286. }
  287. // Iterate over all the fields
  288. for(let field in DOMFields) {
  289. let el = DOMFields[field].children[index];
  290. // If it's a <select> element, select the first element so it looks
  291. // like a reset
  292. if(!el.tagName.toLowerCase().localeCompare('select')) {
  293. el.children[0].selected = true;
  294. } else {
  295. el.value = '';
  296. }
  297. }
  298. }
  299. // Asks the server for a token
  300. // return: nothing
  301. function getToken() {
  302. xhr.token.open('GET', server + '/register');
  303. xhr.token.send();
  304. }
  305. // Asks the server for translated strings to display
  306. // return: notghing
  307. function getLangSync() {
  308. xhr.lang.open('GET', server + '/lang', false);
  309. xhr.lang.send();
  310. }
  311. // Asks the server for the custom fields if there's one or more set in the
  312. // configuration file
  313. // return: nothing
  314. function getCustomFieldsSync() {
  315. xhr.customFields.open('GET', server + '/fields', false);
  316. xhr.customFields.send();
  317. }