Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

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