You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

print_format.js 18 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. // _p can be referenced as this inside $.extend
  2. $.extend(_p, {
  3. show_dialog: function() {
  4. if(!_p.dialog) {
  5. _p.make_dialog();
  6. }
  7. _p.dialog.show();
  8. },
  9. make_dialog: function() {
  10. // Prepare Dialog Box Layout
  11. var d = new Dialog(
  12. 360, // w
  13. 140, // h
  14. 'Print Formats', // title
  15. [ // content
  16. ['HTML', 'Select'],
  17. ['Check', 'No Letterhead'],
  18. ['HTML', 'Buttons']
  19. ]);
  20. //d.widgets['No Letterhead'].checked = 1;
  21. // Print Button
  22. $btn(d.widgets.Buttons, 'Print', function() {
  23. _p.build(
  24. sel_val(cur_frm.print_sel), // fmtname
  25. _p.go, // onload
  26. d.widgets['No Letterhead'].checked // no_letterhead
  27. );
  28. },
  29. {
  30. cssFloat: 'right',
  31. marginBottom: '16px',
  32. marginLeft: '7px'
  33. }, 'green');
  34. // Print Preview
  35. $btn(d.widgets.Buttons, 'Preview', function() {
  36. _p.build(
  37. sel_val(cur_frm.print_sel), // fmtname
  38. _p.preview, // onload
  39. d.widgets['No Letterhead'].checked // no_letterhead
  40. );
  41. },
  42. {
  43. cssFloat: 'right',
  44. marginBottom: '16px'
  45. }, '');
  46. // Delete previous print format select list and Reload print format list from current form
  47. d.onshow = function() {
  48. var c = _p.dialog.widgets['Select'];
  49. if(c.cur_sel && c.cur_sel.parentNode == c) {
  50. c.removeChild(c.cur_sel);
  51. }
  52. c.appendChild(cur_frm.print_sel);
  53. c.cur_sel = cur_frm.print_sel;
  54. }
  55. _p.dialog = d;
  56. },
  57. // Define formats dict
  58. formats: {},
  59. /* args dict can contain:
  60. + fmtname --> print format name
  61. + onload
  62. + no_letterhead
  63. + only_body
  64. */
  65. build: function(fmtname, onload, no_letterhead, only_body) {
  66. args = {
  67. fmtname: fmtname,
  68. onload: onload,
  69. no_letterhead: no_letterhead,
  70. only_body: only_body
  71. };
  72. if(!cur_frm) {
  73. alert('No Document Selected');
  74. return;
  75. }
  76. // Get current doc (record)
  77. var doc = locals[cur_frm.doctype][cur_frm.docname];
  78. if(args.fmtname == 'Standard') {
  79. /*
  80. Render standard print layout
  81. The function passed as args onload is then called using these parameters
  82. */
  83. args.onload(_p.render({
  84. body: _p.print_std(args.no_letterhead),
  85. style: _p.print_style,
  86. doc: doc,
  87. title: doc.name,
  88. no_letterhead: args.no_letterhead,
  89. only_body: args.only_body
  90. }));
  91. } else {
  92. if (!_p.formats[args.fmtname]) {
  93. /*
  94. If print formats are not loaded, then load them and call the args onload function on callback.
  95. I think, this case happens when preview is invoked directly
  96. */
  97. var build_args = args;
  98. $c(
  99. command = 'webnotes.widgets.form.get_print_format',
  100. args = { 'name': build_args.fmtname },
  101. fn = function(r, rt) {
  102. _p.formats[build_args.fmtname] = r.message;
  103. build_args.onload(_p.render({
  104. body: _p.formats[build_args.fmtname],
  105. style: '',
  106. doc: doc,
  107. title: doc.name,
  108. no_letterhead: build_args.no_letterhead,
  109. only_body: build_args.only_body
  110. }));
  111. }
  112. );
  113. } else {
  114. // If print format is already loaded, go ahead with args onload function call
  115. args.onload(_p.render({
  116. body: _p.formats[args.fmtname],
  117. style: '',
  118. doc: doc,
  119. title: doc.name,
  120. no_letterhead: args.no_letterhead,
  121. only_body: args.only_body
  122. }));
  123. }
  124. }
  125. },
  126. /*
  127. args dict can contain:
  128. + body
  129. + style
  130. + doc
  131. + title
  132. + no_letterhead
  133. + only_body
  134. */
  135. render: function(args) {
  136. var container = document.createElement('div');
  137. var stat = '';
  138. // if draft/archived, show draft/archived banner
  139. stat += _p.show_draft(args);
  140. stat += _p.show_archived(args);
  141. // Append args.body's content as a child of container
  142. container.innerHTML = args.body;
  143. // Show letterhead?
  144. _p.show_letterhead(container, args);
  145. _p.run_embedded_js(container, args.doc);
  146. var style = _p.consolidate_css(container, args);
  147. _p.render_header_on_break(container, args);
  148. return _p.render_final(style, stat, container, args);
  149. },
  150. head_banner_format: function() {
  151. return "\
  152. <div style = '\
  153. text-align: center; \
  154. padding: 8px; \
  155. background-color: #CCC;'> \
  156. <div style = '\
  157. font-size: 20px; \
  158. font-weight: bold;'>\
  159. {{HEAD}}\
  160. </div>\
  161. {{DESCRIPTION}}\
  162. </div>"
  163. },
  164. /*
  165. Check if doc's status is not submitted (docstatus == 0)
  166. and submission is pending
  167. Display draft in header if true
  168. */
  169. show_draft: function(args) {
  170. if(args.doc && cint(args.doc.docstatus)==0 && cur_frm.perm[0][SUBMIT]) {
  171. draft = _p.head_banner_format();
  172. draft = draft.replace("{{HEAD}}", "DRAFT");
  173. draft = draft.replace("{{DESCRIPTION}}", "This box will go away after the document is submitted.");
  174. return draft;
  175. } else {
  176. return "";
  177. }
  178. },
  179. /*
  180. Check if doc is archived
  181. Display archived in header if true
  182. */
  183. show_archived: function(args) {
  184. if(args.doc && args.doc.__archived) {
  185. archived = _p.head_banner_format();
  186. archived = archived.replace("{{HEAD}}", "ARCHIVED");
  187. archived = archived.replace("{{DESCRIPTION}}", "You must restore this document to make it editable.");
  188. } else {
  189. return "";
  190. }
  191. },
  192. consolidate_css: function(container, args) {
  193. // Extract <style> content from container
  194. var body_style = '';
  195. var style_list = container.getElementsByTagName('style');
  196. while(style_list && style_list.length>0) {
  197. for(i in style_list) {
  198. if(style_list[i] && style_list[i].innerHTML) {
  199. body_style += style_list[i].innerHTML;
  200. var parent = style_list[i].parentNode;
  201. if(parent) {
  202. parent.removeChild(style_list[i]);
  203. } else {
  204. container.removeChild(style_list[i]);
  205. }
  206. }
  207. }
  208. style_list = container.getElementsByTagName('style');
  209. }
  210. // Concatenate all styles
  211. //style_concat = _p.def_print_style_other + args.style + body_style;
  212. style_concat = (args.only_body ? '' : _p.def_print_style_body)
  213. + _p.def_print_style_other + args.style + body_style;
  214. return style_concat;
  215. },
  216. // This is used to calculate and substitude values in the HTML
  217. run_embedded_js: function(container, doc) {
  218. var jslist = container.getElementsByTagName('script');
  219. while(jslist && jslist.length > 0) {
  220. for(i in jslist) {
  221. if(jslist[i] && jslist[i].innerHTML) {
  222. var code = jslist[i].innerHTML;
  223. var parent = jslist[i].parentNode;
  224. var span = $a(parent, 'span');
  225. parent.replaceChild(span, jslist[i]);
  226. var val = code ? eval(code) : '';
  227. if(!val || typeof(val)=='object') { val = ''; }
  228. span.innerHTML = val;
  229. }
  230. }
  231. jslist = container.getElementsByTagName('script');
  232. }
  233. },
  234. // Attach letterhead at top of container
  235. show_letterhead: function(container, args) {
  236. if(!(args.no_letterhead || args.only_body)) {
  237. container.innerHTML = '<div>' + _p.get_letter_head() + '</div>'
  238. + container.innerHTML;
  239. }
  240. },
  241. render_header_on_break: function(container, args) {
  242. var page_set = container.getElementsByClassName('page-settings');
  243. if(page_set.length) {
  244. for(var i = 0; i < page_set.length; i++) {
  245. var tmp = '';
  246. // if draft/archived, show draft/archived banner
  247. tmp += _p.show_draft(args);
  248. tmp += _p.show_archived(args);
  249. _p.show_letterhead(page_set[i], args);
  250. page_set[i].innerHTML = tmp + page_set[i].innerHTML;
  251. }
  252. }
  253. },
  254. // called by _p.render for final render of print
  255. render_final: function(style, stat, container, args) {
  256. var header = '<div class="page-settings">\n';
  257. var footer = '\n</div>';
  258. if(!args.only_body) {
  259. header = '<!DOCTYPE html>\n\
  260. <html>\
  261. <head>\
  262. <title>' + args.title + '</title>\
  263. <style>' + style + '</style>\
  264. </head>\
  265. <body>\n' + header;
  266. footer = footer + '\n</body>\n\
  267. </html>';
  268. }
  269. var finished = header
  270. + stat
  271. + container.innerHTML.replace(/<div/g, '\n<div').replace(/<td/g, '\n<td')
  272. + footer;
  273. return finished;
  274. },
  275. // fetches letter head from current doc or control panel
  276. get_letter_head: function() {
  277. var cp = locals['Control Panel']['Control Panel'];
  278. var lh = '';
  279. if(cur_frm.doc.letter_head) {
  280. lh = cstr(_p.letter_heads[cur_frm.doc.letter_head]);
  281. } else if (cp.letter_head) {
  282. lh = cp.letter_head;
  283. }
  284. return lh;
  285. },
  286. // common print style setting
  287. print_style: "\
  288. .datalabelcell { \
  289. padding: 2px 0px; \
  290. width: 38%; \
  291. vertical-align: top; \
  292. } \
  293. .datainputcell { \
  294. padding: 2px 0px; \
  295. width: 62%; \
  296. text-align: left; \
  297. }\
  298. .sectionHeading { \
  299. font-size: 16px; \
  300. font-weight: bold; \
  301. margin: 8px 0px; \
  302. } \
  303. .columnHeading { \
  304. font-size: 14px; \
  305. font-weight: bold; \
  306. margin: 8px 0px; \
  307. }",
  308. print_std: function(no_letterhead) {
  309. // Get doctype, docname, layout for a doctype
  310. var docname = cur_frm.docname;
  311. var doctype = cur_frm.doctype;
  312. var data = getchildren('DocField', doctype, 'fields', 'DocType');
  313. var layout = _p.add_layout(doctype);
  314. this.pf_list = [layout];
  315. var me = this;
  316. me.layout = layout;
  317. $.extend(this, {
  318. build_head: function(doctype, docname) {
  319. // Heading
  320. var h1_style = {
  321. fontSize: '22px',
  322. marginBottom: '8px'
  323. }
  324. var h1 = $a(me.layout.cur_row.header, 'h1', '', h1_style);
  325. h1.innerHTML = cur_frm.pformat[docname] ? cur_frm.pformat[docname] :
  326. get_doctype_label(doctype);
  327. var h2_style = {
  328. fontSize: '16px',
  329. color: '#888',
  330. marginBottom: '8px',
  331. paddingBottom: '8px',
  332. borderBottom: (me.layout.with_border ? '0px' :
  333. '1px solid #000')
  334. }
  335. var h2 = $a(me.layout.cur_row.header, 'div', '', h2_style);
  336. h2.innerHTML = docname;
  337. },
  338. build_data: function(data, doctype, docname) {
  339. // Start with a row and a cell in that row
  340. if(data[0] && data[0].fieldtype != "Section Break") {
  341. me.layout.addrow();
  342. if(data[0].fieldtype != "Column Break") {
  343. me.layout.addcell();
  344. }
  345. }
  346. $.extend(this, {
  347. generate_custom_html: function(field, doctype, docname) {
  348. var container = $a(me.layout.cur_cell, 'div');
  349. container.innerHTML = cur_frm.pformat[field.fieldname](locals[doctype][docname]);
  350. },
  351. render_normal: function(field) {
  352. switch(field.fieldtype) {
  353. case 'Section Break':
  354. me.layout.addrow();
  355. // Add column if no column break after this field
  356. if(field[i+1] && field[i+1].fieldtype !=
  357. 'Column Break') {
  358. me.layout.addcell();
  359. }
  360. break;
  361. case 'Column Break':
  362. me.layout.addcell(field.width, field.label);
  363. break;
  364. case 'Table':
  365. var table = print_table(
  366. doctype, // dt
  367. docname, // dn
  368. field.fieldname,
  369. field.options, // tabletype
  370. null, // cols
  371. null, // head_labels
  372. null, // widths
  373. null); // condition
  374. me.layout = _p.print_std_add_table(table, me.layout, me.pf_list, doctype, no_letterhead);
  375. break;
  376. case 'HTML':
  377. var div = $a(me.layout.cur_cell, 'div');
  378. div.innerHTML = field.options;
  379. break;
  380. case 'Code':
  381. var div = $a(me.layout.cur_cell, 'div');
  382. var val = _f.get_value(doctype, docname,
  383. field.fieldname);
  384. div.innerHTML = '<div>' + field.label +
  385. ': </div><pre style="font-family: Courier, Fixed;">' + (val ? val : '') +
  386. '</pre>';
  387. break;
  388. case 'Text Editor':
  389. var div = $a(me.layout.cur_cell, 'div');
  390. var val = _f.get_value(doctype, docname,
  391. field.fieldname);
  392. div.innerHTML = val ? val : '';
  393. break;
  394. default:
  395. // Add Cell Data
  396. _p.print_std_add_field(doctype, docname, field, me.layout);
  397. break;
  398. }
  399. }
  400. });
  401. // Then build each field
  402. for(var i = 0; i < data.length; i++) {
  403. var fieldname = data[i].fieldname ? data[i].fieldname :
  404. data[i].label;
  405. var field = fieldname ?
  406. get_field(doctype, fieldname, docname) : data[i];
  407. if(!field.print_hide) {
  408. if(cur_frm.pformat[field.fieldname]) {
  409. // If there is a custom method to generate the HTML, then use it
  410. this.generate_custom_html(field, doctype, docname);
  411. } else {
  412. // Do the normal rendering
  413. this.render_normal(field);
  414. }
  415. }
  416. }
  417. me.layout.close_borders();
  418. },
  419. build_html: function() {
  420. var html = '';
  421. for(var i = 0; i < me.pf_list.length; i++) {
  422. if(me.pf_list[i].wrapper) {
  423. html += me.pf_list[i].wrapper.innerHTML;
  424. } else if(me.pf_list[i].innerHTML) {
  425. html += me.pf_list[i].innerHTML;
  426. } else {
  427. html += me.pf_list[i];
  428. }
  429. }
  430. this.pf_list = [];
  431. return html;
  432. }
  433. });
  434. this.build_head(doctype, docname);
  435. this.build_data(data, doctype, docname);
  436. var html = this.build_html();
  437. return html;
  438. },
  439. add_layout: function(doctype) {
  440. var layout = new Layout();
  441. layout.addrow();
  442. if(locals['DocType'][doctype].print_outline == 'Yes') {
  443. layout.with_border = 1
  444. }
  445. return layout;
  446. },
  447. print_std_add_table: function(t, layout, pf_list, dt, no_letterhead) {
  448. if(t.appendChild) {
  449. // If only one table is passed
  450. layout.cur_cell.appendChild(t);
  451. } else {
  452. page_break = '\n\
  453. <div style = "page-break-after: always;" \
  454. class = "page_break"></div><div class="page-settings"></div>';
  455. // If a list of tables is passed
  456. for(var i = 0; i < t.length-1; i++) {
  457. // add to current page
  458. layout.cur_cell.appendChild(t[i]);
  459. layout.close_borders();
  460. pf_list.push(page_break);
  461. // Create new page
  462. layout = _p.add_layout(dt, no_letterhead);
  463. pf_list.push(layout);
  464. layout.addrow();
  465. layout.addcell();
  466. var div = $a(layout.cur_cell, 'div');
  467. div.innerHTML = 'Continued from previous page...';
  468. div.style.padding = '4px';
  469. }
  470. // Append last table
  471. layout.cur_cell.appendChild(t[t.length-1]);
  472. }
  473. return layout;
  474. },
  475. print_std_add_field: function(dt, dn, f, layout) {
  476. var val = _f.get_value(dt, dn, f.fieldname);
  477. if(f.fieldtype!='Button') {
  478. if(val || in_list(['Float', 'Int', 'Currency'], f.fieldtype)) {
  479. // If value or a numeric type then proceed
  480. // Add field table
  481. row = _p.field_tab(layout.cur_cell);
  482. // Add label
  483. row.cells[0].innerHTML = f.label ? f.label : f.fieldname;
  484. $s(row.cells[1], val, f.fieldtype);
  485. // left align currency in normal display
  486. if(f.fieldtype == 'Currency') {
  487. $y(row.cells[1], { textAlign: 'left' });
  488. }
  489. }
  490. }
  491. },
  492. field_tab: function(layout_cell) {
  493. var tab = $a(layout_cell, 'table', '', {width:'100%'});
  494. var row = tab.insertRow(0);
  495. _p.row = row; // Don't know this line's purpose
  496. row.insertCell(0);
  497. row.insertCell(1);
  498. row.cells[0].className = 'datalabelcell';
  499. row.cells[1].className = 'datainputcell';
  500. return row;
  501. }
  502. });
  503. print_table = function(dt, dn, fieldname, tabletype, cols, head_labels, widths, condition, cssClass, modifier) {
  504. var me = this;
  505. $.extend(this, {
  506. flist: fields_list[tabletype],
  507. data: getchildren(
  508. tabletype, // child_dt
  509. dn, // parent
  510. fieldname, // parentfield
  511. dt // parenttype
  512. ),
  513. cell_style: {
  514. border: '1px solid #000',
  515. padding: '2px',
  516. verticalAlign: 'top'
  517. },
  518. head_cell_style: {
  519. border: '1px solid #000',
  520. padding: '2px',
  521. verticalAlign: 'top',
  522. backgroundColor: '#ddd',
  523. fontWeight: 'bold'
  524. },
  525. table_style: {
  526. width: '100%',
  527. borderCollapse: 'collapse',
  528. marginBottom: '10px'
  529. },
  530. /*
  531. This function prepares a list of columns to be displayed and calls make_print_table to create a table with these columns
  532. */
  533. prepare_col_heads: function(flist) {
  534. var new_flist = [];
  535. // Make a list of column headings
  536. if(cols && cols.length) {
  537. // If cols to be displayed are passed in print_table
  538. if(cols[0] == 'SR') { new_flist.push('SR') }
  539. for(var i = 0; i < cols.length; i++) {
  540. for(var j = 0; j < flist.length; j++) {
  541. if(flist[j].fieldname == cols[i]) {
  542. new_flist.push(flist[j]);
  543. break;
  544. }
  545. }
  546. }
  547. } else {
  548. // Default action: remove hidden cols
  549. new_flist.push('SR');
  550. for(var i = 0; i < flist.length; i++) {
  551. if(!flist[i].print_hide) {
  552. new_flist.push(flist[i]);
  553. }
  554. }
  555. }
  556. // Changing me.flist so that it could be used to hide data
  557. me.flist = new_flist;
  558. },
  559. // This function makes a new table with its heading rows
  560. make_print_table: function(flist) {
  561. // Make a table
  562. var wrapper = document.createElement('div');
  563. var table = $a(wrapper, 'table', '', me.table_style);
  564. table.wrapper = wrapper;
  565. // Make Head Row
  566. table.insertRow(0);
  567. var col_start = 0;
  568. // If 'SR' exists in flist, then create its heading column cell
  569. if(flist[0]=='SR') {
  570. var cell = table.rows[0].insertCell(0);
  571. cell.innerHTML = head_labels?head_labels[0]:'<b>SR</b>';
  572. $y(cell, { width: '30px' });
  573. $y(cell, me.head_cell_style);
  574. col_start++;
  575. }
  576. for(var c = col_start; c < flist.length; c++) {
  577. var cell = table.rows[0].insertCell(c);
  578. $y(cell, me.head_cell_style);
  579. cell.innerHTML = head_labels?head_labels[c]:flist[c].label;
  580. if(flist[c].width) { $y(cell, {width: flist[c].width}); }
  581. if(widths) { $y(cell, {width: widths[c]}); }
  582. if(in_list(['Currency', 'Float'], flist[c].fieldtype)) {
  583. $y(cell, { textAlign: 'right' });
  584. }
  585. }
  586. return table;
  587. },
  588. // Populate table with data
  589. populate_table: function(table, data) {
  590. for(var r = 0; r < data.length; r++) {
  591. if((!condition) || (condition(data[r]))) {
  592. // Check for page break
  593. if(data[r].page_break) {
  594. table = me.make_print_table(me.flist);
  595. me.table_list.push(table.wrapper);
  596. }
  597. var row = table.insertRow(table.rows.length);
  598. // Add serial number if required
  599. if(me.flist[0] == 'SR') {
  600. var cell = row.insertCell(0);
  601. cell.innerHTML = r + 1;
  602. $y(cell, me.cell_style);
  603. }
  604. for(var c=me.flist.indexOf('SR')+1; c<me.flist.length; c++){
  605. var cell = row.insertCell(c);
  606. $y(cell, me.cell_style);
  607. if(modifier && me.flist[c].fieldname in modifier) {
  608. data[r][me.flist[c].fieldname] = modifier[me.flist[c].fieldname](data[r]);
  609. }
  610. $s(cell, data[r][me.flist[c].fieldname],
  611. me.flist[c].fieldtype);
  612. if(in_list(['Currency', 'Float'], me.flist[c].fieldtype)) {
  613. cell.style.textAlign = 'right';
  614. }
  615. }
  616. }
  617. }
  618. }
  619. });
  620. // If no data, do not create table
  621. if(!this.data.length) { return document.createElement('div'); }
  622. this.prepare_col_heads(this.flist);
  623. var table = me.make_print_table(this.flist);
  624. this.table_list = [table.wrapper];
  625. this.populate_table(table, this.data);
  626. // If multiple tables exists, send whole list, else send only one table
  627. return (me.table_list.length > 1) ? me.table_list : me.table_list[0];
  628. }