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.
 
 
 
 
 
 

689 lines
18 KiB

  1. // Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. //
  3. // MIT License (MIT)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a
  6. // copy of this software and associated documentation files (the "Software"),
  7. // to deal in the Software without restriction, including without limitation
  8. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. // and/or sell copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. //
  22. // default print style
  23. _p.def_print_style_body = "html, body, div, span, td, p { \
  24. font-family: inherit; \
  25. font-size: inherit; \
  26. }\
  27. .page-settings {\
  28. font-family: Arial, Helvetica Neue, Sans;\
  29. font-size: 9pt;\
  30. }\
  31. pre { margin:0; padding:0;}";
  32. _p.def_print_style_other = "\n.simpletable, .noborder { \
  33. border-collapse: collapse;\
  34. margin-bottom: 10px;\
  35. }\
  36. .simpletable td {\
  37. border: 1pt solid #777;\
  38. vertical-align: top;\
  39. padding: 4px;\
  40. }\
  41. .noborder td {\
  42. vertical-align: top;\
  43. }";
  44. _p.go = function(html) {
  45. var w = window.open();
  46. if(!w) {
  47. msgprint(_("Please enable pop-ups"));
  48. return;
  49. }
  50. w.document.write(html);
  51. w.print();
  52. w.close();
  53. }
  54. _p.preview = function(html) {
  55. var w = window.open();
  56. if(!w) {
  57. msgprint(_("Please enable pop-ups"));
  58. return;
  59. }
  60. w.document.write(html);
  61. }
  62. // _p can be referenced as this inside $.extend
  63. $.extend(_p, {
  64. show_dialog: function() {
  65. if(!_p.dialog) {
  66. _p.make_dialog();
  67. }
  68. _p.dialog.show();
  69. },
  70. make_dialog: function() {
  71. // Prepare Dialog Box Layout
  72. var dialog = new wn.ui.Dialog({
  73. title: "Print Formats",
  74. fields: [
  75. {fieldtype:"Select", label:"Print Format", fieldname:"print_format", reqd:1},
  76. {fieldtype:"Check", label:"No Letter Head", fieldname:"no_letterhead"},
  77. {fieldtype:"HTML", options: '<p style="text-align: right;">\
  78. <button class="btn btn-primary btn-print">Print</button>\
  79. <button class="btn btn-default btn-preview">Preview</button>\
  80. </p>'},
  81. ]
  82. })
  83. dialog.$wrapper.find(".btn-print").click(function() {
  84. var args = dialog.get_values();
  85. _p.build(
  86. args.print_format, // fmtname
  87. _p.go, // onload
  88. args.no_letterhead // no_letterhead
  89. );
  90. });
  91. dialog.$wrapper.find(".btn-preview").click(function() {
  92. var args = dialog.get_values();
  93. _p.build(
  94. args.print_format, // fmtname
  95. _p.preview, // onload
  96. args.no_letterhead // no_letterhead
  97. );
  98. });
  99. dialog.onshow = function() {
  100. var $print = dialog.fields_dict.print_format.$input;
  101. $print.add_options(cur_frm.print_formats);
  102. if(cur_frm.$print_view_select && cur_frm.$print_view_select.val())
  103. $print.val(cur_frm.$print_view_select.val());
  104. }
  105. _p.dialog = dialog;
  106. },
  107. // Define formats dict
  108. formats: {},
  109. /* args dict can contain:
  110. + fmtname --> print format name
  111. + onload
  112. + no_letterhead
  113. + only_body
  114. */
  115. build: function(fmtname, onload, no_letterhead, only_body, no_heading) {
  116. if(!fmtname) {
  117. fmtname= "Standard";
  118. }
  119. args = {
  120. fmtname: fmtname,
  121. onload: onload,
  122. no_letterhead: no_letterhead,
  123. only_body: only_body
  124. };
  125. if(!cur_frm) {
  126. alert('No Document Selected');
  127. return;
  128. }
  129. // Get current doc (record)
  130. var doc = locals[cur_frm.doctype][cur_frm.docname];
  131. if(args.fmtname == 'Standard') {
  132. args.onload(_p.render({
  133. body: _p.print_std(args.no_letterhead, no_heading),
  134. style: _p.print_style,
  135. doc: doc,
  136. title: doc.name,
  137. no_letterhead: args.no_letterhead,
  138. no_heading: no_heading,
  139. only_body: args.only_body
  140. }));
  141. } else {
  142. var print_format_doc = locals["Print Format"][args.fmtname];
  143. if(!print_format_doc) {
  144. msgprint("Unknown Print Format: " + args.fmtname);
  145. return;
  146. }
  147. args.onload(_p.render({
  148. body: print_format_doc.html,
  149. style: '',
  150. doc: doc,
  151. title: doc.name,
  152. no_letterhead: args.no_letterhead,
  153. no_heading: no_heading,
  154. only_body: args.only_body
  155. }));
  156. }
  157. },
  158. render: function(args) {
  159. var container = document.createElement('div');
  160. var stat = '';
  161. if(!args.no_heading) {
  162. // if draft/archived, show draft/archived banner
  163. stat += _p.show_draft(args);
  164. stat += _p.show_archived(args);
  165. stat += _p.show_cancelled(args);
  166. }
  167. // Append args.body's content as a child of container
  168. container.innerHTML = args.body;
  169. // Show letterhead?
  170. _p.show_letterhead(container, args);
  171. _p.run_embedded_js(container, args.doc);
  172. var style = _p.consolidate_css(container, args);
  173. _p.render_header_on_break(container, args);
  174. return _p.render_final(style, stat, container, args);
  175. },
  176. head_banner_format: function() {
  177. return "\
  178. <div style = '\
  179. text-align: center; \
  180. padding: 8px; \
  181. background-color: #CCC;'> \
  182. <div style = '\
  183. font-size: 20px; \
  184. font-weight: bold;'>\
  185. {{HEAD}}\
  186. </div>\
  187. {{DESCRIPTION}}\
  188. </div>"
  189. },
  190. /*
  191. Check if doc's status is not submitted (docstatus == 0)
  192. and submission is pending
  193. Display draft in header if true
  194. */
  195. show_draft: function(args) {
  196. var is_doctype_submittable = 0;
  197. var plist = locals['DocPerm'];
  198. for(var perm in plist) {
  199. var p = plist[perm];
  200. if((p.parent==args.doc.doctype) && (p.submit==1)){
  201. is_doctype_submittable = 1;
  202. break;
  203. }
  204. }
  205. if(args.doc && cint(args.doc.docstatus)==0 && is_doctype_submittable) {
  206. draft = _p.head_banner_format();
  207. draft = draft.replace("{{HEAD}}", "DRAFT");
  208. draft = draft.replace("{{DESCRIPTION}}", "This box will go away after the document is submitted.");
  209. return draft;
  210. } else {
  211. return "";
  212. }
  213. },
  214. /*
  215. Check if doc is archived
  216. Display archived in header if true
  217. */
  218. show_archived: function(args) {
  219. if(args.doc && args.doc.__archived) {
  220. archived = _p.head_banner_format();
  221. archived = archived.replace("{{HEAD}}", "ARCHIVED");
  222. archived = archived.replace("{{DESCRIPTION}}", "You must restore this document to make it editable.");
  223. return archived;
  224. } else {
  225. return "";
  226. }
  227. },
  228. /*
  229. Check if doc is cancelled
  230. Display cancelled in header if true
  231. */
  232. show_cancelled: function(args) {
  233. if(args.doc && args.doc.docstatus==2) {
  234. cancelled = _p.head_banner_format();
  235. cancelled = cancelled.replace("{{HEAD}}", "CANCELLED");
  236. cancelled = cancelled.replace("{{DESCRIPTION}}", "You must amend this document to make it editable.");
  237. return cancelled;
  238. } else {
  239. return "";
  240. }
  241. },
  242. consolidate_css: function(container, args) {
  243. // Extract <style> content from container
  244. var body_style = '';
  245. var style_list = container.getElementsByTagName('style');
  246. while(style_list && style_list.length>0) {
  247. for(i in style_list) {
  248. if(style_list[i] && style_list[i].innerHTML) {
  249. body_style += style_list[i].innerHTML;
  250. var parent = style_list[i].parentNode;
  251. if(parent) {
  252. parent.removeChild(style_list[i]);
  253. } else {
  254. container.removeChild(style_list[i]);
  255. }
  256. }
  257. }
  258. style_list = container.getElementsByTagName('style');
  259. }
  260. // Concatenate all styles
  261. style_concat = (args.only_body ? '' : _p.def_print_style_body)
  262. + _p.def_print_style_other + args.style + body_style;
  263. return style_concat;
  264. },
  265. // This is used to calculate and substitude values in the HTML
  266. run_embedded_js: function(container, doc) {
  267. script_list = $(container).find("script");
  268. for(var i=0; i<script_list.length; i++) {
  269. var element = script_list[i];
  270. var code = element.innerHTML;
  271. var new_html = code ? (eval(code) || "") : "";
  272. if(in_list(["string", "number"], typeof new_html)) {
  273. $(element).replaceWith(this.add_span(new_html + ""));
  274. }
  275. }
  276. },
  277. add_span: function(html) {
  278. var tags = ["<span[^>]>", "<p[^>]>", "<div[^>]>", "<br[^>]>", "<table[^>]>"];
  279. var match = false;
  280. for(var i=0; i<tags.length; i++) {
  281. if(html.match(tags[i])) {
  282. match = true;
  283. }
  284. }
  285. if(!match) {
  286. html = "<span>" + html + "</span>";
  287. }
  288. return html;
  289. },
  290. // Attach letterhead at top of container
  291. show_letterhead: function(container, args) {
  292. if(!(args.no_letterhead || args.only_body)) {
  293. container.innerHTML = '<div>' + _p.get_letter_head() + '</div>'
  294. + container.innerHTML;
  295. }
  296. },
  297. render_header_on_break: function(container, args) {
  298. var page_set = container.getElementsByClassName('page-settings');
  299. if(page_set.length) {
  300. for(var i = 0; i < page_set.length; i++) {
  301. var tmp = '';
  302. // if draft/archived, show draft/archived banner
  303. tmp += _p.show_draft(args);
  304. tmp += _p.show_archived(args);
  305. _p.show_letterhead(page_set[i], args);
  306. page_set[i].innerHTML = tmp + page_set[i].innerHTML;
  307. }
  308. }
  309. },
  310. // called by _p.render for final render of print
  311. render_final: function(style, stat, container, args) {
  312. if(!args.only_body) {
  313. var header = '<!DOCTYPE html>\
  314. <html>\
  315. <head>\
  316. <meta charset="utf-8" />\
  317. <title>' + args.title + '</title>\
  318. <style>' + style + '</style>\
  319. </head>\
  320. <body>';
  321. var footer = '\
  322. </body>\
  323. </html>';
  324. } else {
  325. var header = '';
  326. var footer = '';
  327. }
  328. var finished = header
  329. + '<div class="page-settings">'
  330. + stat
  331. + container.innerHTML
  332. + '</div>'
  333. + footer;
  334. // replace relative links by absolute links
  335. var prefix = window.location.href.split("app.html")[0]
  336. // find unique matches
  337. var matches = $.unique(finished.match(/src=['"]([^'"]*)['"]/g) || []);
  338. $.each(matches, function(i, v) {
  339. if(v.substr(0,4)=="src=") {
  340. var v = v.substr(5, v.length-6);
  341. if(v.substr(0,4)!="http")
  342. finished = finished.split(v).join(prefix + v);
  343. }
  344. });
  345. return finished;
  346. },
  347. // fetches letter head from current doc or control panel
  348. get_letter_head: function() {
  349. var cp = wn.control_panel;
  350. var lh = '';
  351. if(cur_frm.doc.letter_head) {
  352. lh = cstr(wn.boot.letter_heads[cur_frm.doc.letter_head]);
  353. } else if (cp.letter_head) {
  354. lh = cp.letter_head;
  355. }
  356. return lh;
  357. },
  358. // common print style setting
  359. print_style: "\
  360. .datalabelcell { \
  361. padding: 2px 0px; \
  362. width: 38%; \
  363. vertical-align: top; \
  364. } \
  365. .datainputcell { \
  366. padding: 2px 0px; \
  367. width: 62%; \
  368. text-align: left; \
  369. }\
  370. .sectionHeading { \
  371. font-size: 16px; \
  372. font-weight: bold; \
  373. margin: 8px 0px; \
  374. } \
  375. .columnHeading { \
  376. font-size: 14px; \
  377. font-weight: bold; \
  378. margin: 8px 0px; \
  379. }",
  380. print_std: function(no_letterhead, no_heading) {
  381. // Get doctype, docname, layout for a doctype
  382. var docname = cur_frm.docname;
  383. var doctype = cur_frm.doctype;
  384. var data = getchildren('DocField', doctype, 'fields', 'DocType');
  385. var layout = _p.add_layout(doctype);
  386. this.pf_list = [layout];
  387. var me = this;
  388. me.layout = layout;
  389. $.extend(this, {
  390. build_head: function(data, doctype, docname) {
  391. // Heading
  392. var h1_style = {
  393. fontSize: '22px',
  394. marginBottom: '8px'
  395. }
  396. var h1 = $a(me.layout.cur_row.header, 'h1', '', h1_style);
  397. // Get print heading
  398. if (cur_frm.pformat[docname]) {
  399. // first check in cur_frm.pformat
  400. h1.innerHTML = cur_frm.pformat[docname];
  401. } else {
  402. // then check if select print heading exists and has a value
  403. var val = null;
  404. for (var i = 0; i < data.length; i++) {
  405. if (data[i].fieldname === 'select_print_heading') {
  406. val = _f.get_value(doctype, docname, data[i].fieldname);
  407. break;
  408. }
  409. }
  410. // if not, just have doctype has heading
  411. h1.innerHTML = val ? val : wn._(doctype);
  412. }
  413. var h2_style = {
  414. fontSize: '16px',
  415. color: '#888',
  416. marginBottom: '8px',
  417. paddingBottom: '8px',
  418. borderBottom: (me.layout.with_border ? '0px' :
  419. '1px solid #000')
  420. }
  421. var h2 = $a(me.layout.cur_row.header, 'div', '', h2_style);
  422. h2.innerHTML = docname;
  423. if(cur_frm.state_fieldname) {
  424. $a(h2, 'br');
  425. var span = $a(h2, 'span', '',
  426. {padding: "3px", color: "#fff", backgroundColor: "#777",
  427. display:"inline-block"});
  428. span.innerHTML = cur_frm.doc[cur_frm.state_fieldname];
  429. }
  430. },
  431. build_data: function(data, doctype, docname) {
  432. // Start with a row and a cell in that row
  433. if(data[0] && data[0].fieldtype != "Section Break") {
  434. me.layout.addrow();
  435. if(data[0].fieldtype != "Column Break") {
  436. me.layout.addcell();
  437. }
  438. }
  439. $.extend(this, {
  440. generate_custom_html: function(field, doctype, docname) {
  441. var container = $a(me.layout.cur_cell, 'div');
  442. container.innerHTML = cur_frm.pformat[field.fieldname](locals[doctype][docname]);
  443. },
  444. render_normal: function(field, data, i) {
  445. switch(field.fieldtype) {
  446. case 'Section Break':
  447. me.layout.addrow();
  448. // Add column if no column break after this field
  449. if(data[i+1] && data[i+1].fieldtype !=
  450. 'Column Break') {
  451. me.layout.addcell();
  452. }
  453. break;
  454. case 'Column Break':
  455. me.layout.addcell(field.width, field.label);
  456. break;
  457. case 'Table':
  458. var table = print_table(
  459. doctype, // dt
  460. docname, // dn
  461. field.fieldname,
  462. field.options, // tabletype
  463. null, // cols
  464. null, // head_labels
  465. null, // widths
  466. null); // condition
  467. me.layout = _p.print_std_add_table(table, me.layout, me.pf_list, doctype, no_letterhead);
  468. break;
  469. case 'HTML':
  470. var div = $a(me.layout.cur_cell, 'div');
  471. div.innerHTML = field.options;
  472. break;
  473. case 'Code':
  474. var div = $a(me.layout.cur_cell, 'div');
  475. var val = _f.get_value(doctype, docname,
  476. field.fieldname);
  477. div.innerHTML = '<div>' + field.label +
  478. ': </div><pre style="font-family: Courier, Fixed;">' + (val ? val : '') +
  479. '</pre>';
  480. break;
  481. case 'Text Editor':
  482. var div = $a(me.layout.cur_cell, 'div');
  483. var val = _f.get_value(doctype, docname,
  484. field.fieldname);
  485. div.innerHTML = val ? val : '';
  486. break;
  487. default:
  488. // Add Cell Data
  489. _p.print_std_add_field(doctype, docname, field, me.layout);
  490. break;
  491. }
  492. }
  493. });
  494. // Then build each field
  495. for(var i = 0; i < data.length; i++) {
  496. var fieldname = data[i].fieldname ? data[i].fieldname :
  497. data[i].label;
  498. var field = fieldname ?
  499. wn.meta.get_docfield(doctype, fieldname, docname) : data[i];
  500. if(!field.print_hide) {
  501. if(cur_frm.pformat[field.fieldname]) {
  502. // If there is a custom method to generate the HTML, then use it
  503. this.generate_custom_html(field, doctype, docname);
  504. } else {
  505. // Do the normal rendering
  506. this.render_normal(field, data, i);
  507. }
  508. }
  509. }
  510. me.layout.close_borders();
  511. },
  512. build_html: function() {
  513. var html = '';
  514. for(var i = 0; i < me.pf_list.length; i++) {
  515. if(me.pf_list[i].wrapper) {
  516. html += me.pf_list[i].wrapper.innerHTML;
  517. } else if(me.pf_list[i].innerHTML) {
  518. html += me.pf_list[i].innerHTML;
  519. } else {
  520. html += me.pf_list[i];
  521. }
  522. }
  523. this.pf_list = [];
  524. return html;
  525. }
  526. });
  527. if(!no_heading) {
  528. this.build_head(data, doctype, docname);
  529. }
  530. this.build_data(data, doctype, docname);
  531. var html = this.build_html();
  532. return html;
  533. },
  534. add_layout: function(doctype) {
  535. var layout = new Layout();
  536. layout.addrow();
  537. if(locals['DocType'][doctype].print_outline == 'Yes') {
  538. layout.with_border = 1
  539. }
  540. return layout;
  541. },
  542. print_std_add_table: function(t, layout, pf_list, dt, no_letterhead) {
  543. if(t.appendChild) {
  544. // If only one table is passed
  545. layout.cur_cell.appendChild(t);
  546. } else {
  547. page_break = '\n\
  548. <div style = "page-break-after: always;" \
  549. class = "page_break"></div><div class="page-settings"></div>';
  550. // If a list of tables is passed
  551. for(var i = 0; i < t.length-1; i++) {
  552. // add to current page
  553. layout.cur_cell.appendChild(t[i]);
  554. layout.close_borders();
  555. pf_list.push(page_break);
  556. // Create new page
  557. layout = _p.add_layout(dt, no_letterhead);
  558. pf_list.push(layout);
  559. layout.addrow();
  560. layout.addcell();
  561. var div = $a(layout.cur_cell, 'div');
  562. div.innerHTML = 'Continued from previous page...';
  563. div.style.padding = '4px';
  564. }
  565. // Append last table
  566. layout.cur_cell.appendChild(t[t.length-1]);
  567. }
  568. return layout;
  569. },
  570. print_std_add_field: function(dt, dn, f, layout) {
  571. var val = _f.get_value(dt, dn, f.fieldname);
  572. if(f.fieldtype!='Button') {
  573. if(val || in_list(['Float', 'Int', 'Currency'], f.fieldtype)) {
  574. // If value or a numeric type then proceed
  575. // Add field table
  576. row = _p.field_tab(layout.cur_cell);
  577. // Add label
  578. row.cells[0].innerHTML = f.label ? f.label : f.fieldname;
  579. row.cells[1].innerHTML = wn.format(val, f, {for_print: true});
  580. // left align currency in normal display
  581. if(f.fieldtype == 'Currency') {
  582. $y(row.cells[1], { textAlign: 'left' });
  583. }
  584. }
  585. }
  586. },
  587. field_tab: function(layout_cell) {
  588. var tab = $a(layout_cell, 'table', '', {width:'100%'});
  589. var row = tab.insertRow(0);
  590. _p.row = row; // Don't know this line's purpose
  591. row.insertCell(0);
  592. row.insertCell(1);
  593. row.cells[0].className = 'datalabelcell';
  594. row.cells[0].style.width = "38%";
  595. row.cells[1].className = 'datainputcell';
  596. return row;
  597. }
  598. });