Brak opisu

settings-views-html-settings-tax.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /* global htmlSettingsTaxLocalizeScript, ajaxurl */
  2. /**
  3. * Used by woocommerce/includes/admin/settings/views/html-settings-tax.php
  4. */
  5. ( function( $, data, wp, ajaxurl ) {
  6. $( function() {
  7. if ( ! String.prototype.trim ) {
  8. String.prototype.trim = function () {
  9. return this.replace( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '' );
  10. };
  11. }
  12. var rowTemplate = wp.template( 'wc-tax-table-row' ),
  13. rowTemplateEmpty = wp.template( 'wc-tax-table-row-empty' ),
  14. paginationTemplate = wp.template( 'wc-tax-table-pagination' ),
  15. $table = $( '.wc_tax_rates' ),
  16. $tbody = $( '#rates' ),
  17. $save_button = $( ':input[name="save"]' ),
  18. $pagination = $( '#rates-pagination' ),
  19. $search_field = $( '#rates-search .wc-tax-rates-search-field' ),
  20. $submit = $( '.submit .button-primary[type=submit]' ),
  21. WCTaxTableModelConstructor = Backbone.Model.extend({
  22. changes: {},
  23. setRateAttribute: function( rateID, attribute, value ) {
  24. var rates = _.indexBy( this.get( 'rates' ), 'tax_rate_id' ),
  25. changes = {};
  26. if ( rates[ rateID ][ attribute ] !== value ) {
  27. changes[ rateID ] = {};
  28. changes[ rateID ][ attribute ] = value;
  29. rates[ rateID ][ attribute ] = value;
  30. }
  31. this.logChanges( changes );
  32. },
  33. logChanges: function( changedRows ) {
  34. var changes = this.changes || {};
  35. _.each( changedRows, function( row, id ) {
  36. changes[ id ] = _.extend( changes[ id ] || {
  37. tax_rate_id : id
  38. }, row );
  39. } );
  40. this.changes = changes;
  41. this.trigger( 'change:rates' );
  42. },
  43. getFilteredRates: function() {
  44. var rates = this.get( 'rates' ),
  45. search = $search_field.val().toLowerCase();
  46. if ( search.length ) {
  47. rates = _.filter( rates, function( rate ) {
  48. var search_text = _.toArray( rate ).join( ' ' ).toLowerCase();
  49. return ( -1 !== search_text.indexOf( search ) );
  50. } );
  51. }
  52. rates = _.sortBy( rates, function( rate ) {
  53. return parseInt( rate.tax_rate_order, 10 );
  54. } );
  55. return rates;
  56. },
  57. block: function() {
  58. $( '.wc_tax_rates' ).block({
  59. message: null,
  60. overlayCSS: {
  61. background: '#fff',
  62. opacity: 0.6
  63. }
  64. });
  65. },
  66. unblock: function() {
  67. $( '.wc_tax_rates' ).unblock();
  68. },
  69. save: function() {
  70. var self = this;
  71. self.block();
  72. Backbone.ajax({
  73. method: 'POST',
  74. dataType: 'json',
  75. url: ajaxurl + ( ajaxurl.indexOf( '?' ) > 0 ? '&' : '?' ) + 'action=woocommerce_tax_rates_save_changes',
  76. data: {
  77. current_class: data.current_class,
  78. wc_tax_nonce: data.wc_tax_nonce,
  79. changes: self.changes
  80. },
  81. success: function( response, textStatus ) {
  82. if ( 'success' === textStatus && response.success ) {
  83. WCTaxTableModelInstance.set( 'rates', response.data.rates );
  84. WCTaxTableModelInstance.trigger( 'change:rates' );
  85. WCTaxTableModelInstance.changes = {};
  86. WCTaxTableModelInstance.trigger( 'saved:rates' );
  87. // Reload view.
  88. WCTaxTableInstance.render();
  89. }
  90. self.unblock();
  91. }
  92. });
  93. }
  94. } ),
  95. WCTaxTableViewConstructor = Backbone.View.extend({
  96. rowTemplate: rowTemplate,
  97. per_page: data.limit,
  98. page: data.page,
  99. initialize: function() {
  100. var qty_pages = Math.ceil( _.toArray( this.model.get( 'rates' ) ).length / this.per_page );
  101. this.qty_pages = 0 === qty_pages ? 1 : qty_pages;
  102. this.page = this.sanitizePage( data.page );
  103. this.listenTo( this.model, 'change:rates', this.setUnloadConfirmation );
  104. this.listenTo( this.model, 'saved:rates', this.clearUnloadConfirmation );
  105. $tbody.on( 'change autocompletechange', ':input', { view: this }, this.updateModelOnChange );
  106. $search_field.on( 'keyup search', { view: this }, this.onSearchField );
  107. $pagination.on( 'click', 'a', { view: this }, this.onPageChange );
  108. $pagination.on( 'change', 'input', { view: this }, this.onPageChange );
  109. $( window ).on( 'beforeunload', { view: this }, this.unloadConfirmation );
  110. $submit.on( 'click', { view: this }, this.onSubmit );
  111. $save_button.prop( 'disabled', true );
  112. // Can bind these directly to the buttons, as they won't get overwritten.
  113. $table.find( '.insert' ).on( 'click', { view: this }, this.onAddNewRow );
  114. $table.find( '.remove_tax_rates' ).on( 'click', { view: this }, this.onDeleteRow );
  115. $table.find( '.export' ).on( 'click', { view: this }, this.onExport );
  116. },
  117. render: function() {
  118. var rates = this.model.getFilteredRates(),
  119. qty_rates = _.size( rates ),
  120. qty_pages = Math.ceil( qty_rates / this.per_page ),
  121. first_index = 0 === qty_rates ? 0 : this.per_page * ( this.page - 1 ),
  122. last_index = this.per_page * this.page,
  123. paged_rates = _.toArray( rates ).slice( first_index, last_index ),
  124. view = this;
  125. // Blank out the contents.
  126. this.$el.empty();
  127. if ( paged_rates.length ) {
  128. // Populate $tbody with the current page of results.
  129. $.each( paged_rates, function( id, rowData ) {
  130. view.$el.append( view.rowTemplate( rowData ) );
  131. } );
  132. } else {
  133. view.$el.append( rowTemplateEmpty() );
  134. }
  135. // Initialize autocomplete for countries.
  136. this.$el.find( 'td.country input' ).autocomplete({
  137. source: data.countries,
  138. minLength: 2
  139. });
  140. // Initialize autocomplete for states.
  141. this.$el.find( 'td.state input' ).autocomplete({
  142. source: data.states,
  143. minLength: 3
  144. });
  145. // Postcode and city don't have `name` values by default.
  146. // They're only created if the contents changes, to save on database queries (I think)
  147. this.$el.find( 'td.postcode input, td.city input' ).on( 'change', function() {
  148. $( this ).attr( 'name', $( this ).data( 'name' ) );
  149. });
  150. if ( qty_pages > 1 ) {
  151. // We've now displayed our initial page, time to render the pagination box.
  152. $pagination.html( paginationTemplate( {
  153. qty_rates: qty_rates,
  154. current_page: this.page,
  155. qty_pages: qty_pages
  156. } ) );
  157. } else {
  158. $pagination.empty();
  159. view.page = 1;
  160. }
  161. },
  162. updateUrl: function() {
  163. if ( ! window.history.replaceState ) {
  164. return;
  165. }
  166. var url = data.base_url,
  167. search = $search_field.val();
  168. if ( 1 < this.page ) {
  169. url += '&p=' + encodeURIComponent( this.page );
  170. }
  171. if ( search.length ) {
  172. url += '&s=' + encodeURIComponent( search );
  173. }
  174. window.history.replaceState( {}, '', url );
  175. },
  176. onSubmit: function( event ) {
  177. event.data.view.model.save();
  178. event.preventDefault();
  179. },
  180. onAddNewRow: function( event ) {
  181. var view = event.data.view,
  182. model = view.model,
  183. rates = _.indexBy( model.get( 'rates' ), 'tax_rate_id' ),
  184. changes = {},
  185. size = _.size( rates ),
  186. newRow = _.extend( {}, data.default_rate, {
  187. tax_rate_id: 'new-' + size + '-' + Date.now(),
  188. newRow: true
  189. } ),
  190. $current, current_id, current_order, rates_to_reorder, reordered_rates;
  191. $current = $tbody.children( '.current' );
  192. if ( $current.length ) {
  193. current_id = $current.last().data( 'id' );
  194. current_order = parseInt( rates[ current_id ].tax_rate_order, 10 );
  195. newRow.tax_rate_order = 1 + current_order;
  196. rates_to_reorder = _.filter( rates, function( rate ) {
  197. if ( parseInt( rate.tax_rate_order, 10 ) > current_order ) {
  198. return true;
  199. }
  200. return false;
  201. } );
  202. reordered_rates = _.map( rates_to_reorder, function( rate ) {
  203. rate.tax_rate_order++;
  204. changes[ rate.tax_rate_id ] = _.extend(
  205. changes[ rate.tax_rate_id ] || {}, { tax_rate_order : rate.tax_rate_order }
  206. );
  207. return rate;
  208. } );
  209. } else {
  210. newRow.tax_rate_order = 1 + _.max(
  211. _.pluck( rates, 'tax_rate_order' ),
  212. function ( val ) {
  213. // Cast them all to integers, because strings compare funky. Sighhh.
  214. return parseInt( val, 10 );
  215. }
  216. );
  217. // Move the last page
  218. view.page = view.qty_pages;
  219. }
  220. rates[ newRow.tax_rate_id ] = newRow;
  221. changes[ newRow.tax_rate_id ] = newRow;
  222. model.set( 'rates', rates );
  223. model.logChanges( changes );
  224. view.render();
  225. },
  226. onDeleteRow: function( event ) {
  227. var view = event.data.view,
  228. model = view.model,
  229. rates = _.indexBy( model.get( 'rates' ), 'tax_rate_id' ),
  230. changes = {},
  231. $current, current_id;
  232. event.preventDefault();
  233. if ( $current = $tbody.children( '.current' ) ) {
  234. $current.each(function(){
  235. current_id = $( this ).data('id');
  236. delete rates[ current_id ];
  237. changes[ current_id ] = _.extend( changes[ current_id ] || {}, { deleted : 'deleted' } );
  238. });
  239. model.set( 'rates', rates );
  240. model.logChanges( changes );
  241. view.render();
  242. } else {
  243. window.alert( data.strings.no_rows_selected );
  244. }
  245. },
  246. onSearchField: function( event ){
  247. event.data.view.updateUrl();
  248. event.data.view.render();
  249. },
  250. onPageChange: function( event ) {
  251. var $target = $( event.currentTarget );
  252. event.preventDefault();
  253. event.data.view.page = $target.data( 'goto' ) ? $target.data( 'goto' ) : $target.val();
  254. event.data.view.render();
  255. event.data.view.updateUrl();
  256. },
  257. onExport: function( event ) {
  258. var csv_data = 'data:application/csv;charset=utf-8,' + data.strings.csv_data_cols.join(',') + '\n';
  259. $.each( event.data.view.model.getFilteredRates(), function( id, rowData ) {
  260. var row = '';
  261. row += rowData.tax_rate_country + ',';
  262. row += rowData.tax_rate_state + ',';
  263. row += ( rowData.postcode ? rowData.postcode.join( '; ' ) : '' ) + ',';
  264. row += ( rowData.city ? rowData.city.join( '; ' ) : '' ) + ',';
  265. row += rowData.tax_rate + ',';
  266. row += rowData.tax_rate_name + ',';
  267. row += rowData.tax_rate_priority + ',';
  268. row += rowData.tax_rate_compound + ',';
  269. row += rowData.tax_rate_shipping + ',';
  270. row += data.current_class;
  271. csv_data += row + '\n';
  272. });
  273. $( this ).attr( 'href', encodeURI( csv_data ) );
  274. return true;
  275. },
  276. setUnloadConfirmation: function() {
  277. this.needsUnloadConfirm = true;
  278. $save_button.prop( 'disabled', false );
  279. },
  280. clearUnloadConfirmation: function() {
  281. this.needsUnloadConfirm = false;
  282. $save_button.prop( 'disabled', true );
  283. },
  284. unloadConfirmation: function( event ) {
  285. if ( event.data.view.needsUnloadConfirm ) {
  286. event.returnValue = data.strings.unload_confirmation_msg;
  287. window.event.returnValue = data.strings.unload_confirmation_msg;
  288. return data.strings.unload_confirmation_msg;
  289. }
  290. },
  291. updateModelOnChange: function( event ) {
  292. var model = event.data.view.model,
  293. $target = $( event.target ),
  294. id = $target.closest( 'tr' ).data( 'id' ),
  295. attribute = $target.data( 'attribute' ),
  296. val = $target.val();
  297. if ( 'city' === attribute || 'postcode' === attribute ) {
  298. val = val.split( ';' );
  299. val = $.map( val, function( thing ) {
  300. return thing.trim();
  301. });
  302. }
  303. if ( 'tax_rate_compound' === attribute || 'tax_rate_shipping' === attribute ) {
  304. if ( $target.is( ':checked' ) ) {
  305. val = 1;
  306. } else {
  307. val = 0;
  308. }
  309. }
  310. model.setRateAttribute( id, attribute, val );
  311. },
  312. sanitizePage: function( page_num ) {
  313. page_num = parseInt( page_num, 10 );
  314. if ( page_num < 1 ) {
  315. page_num = 1;
  316. } else if ( page_num > this.qty_pages ) {
  317. page_num = this.qty_pages;
  318. }
  319. return page_num;
  320. }
  321. } ),
  322. WCTaxTableModelInstance = new WCTaxTableModelConstructor({
  323. rates: data.rates
  324. } ),
  325. WCTaxTableInstance = new WCTaxTableViewConstructor({
  326. model: WCTaxTableModelInstance,
  327. el: '#rates'
  328. } );
  329. WCTaxTableInstance.render();
  330. });
  331. })( jQuery, htmlSettingsTaxLocalizeScript, wp, ajaxurl );