Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

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