From d039890a9b14b80836ecd1427e220b61341df648 Mon Sep 17 00:00:00 2001 From: Mario Dian Date: Fri, 14 Dec 2018 16:03:02 +0800 Subject: [PATCH 1/2] Create js-only product management in PoS --- .../Views/Apps/UpdatePointOfSale.cshtml | 86 +++++++- .../wwwroot/products/js/products.jquery.js | 69 +++++++ BTCPayServer/wwwroot/products/js/products.js | 186 ++++++++++++++++++ 3 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 BTCPayServer/wwwroot/products/js/products.jquery.js create mode 100644 BTCPayServer/wwwroot/products/js/products.js diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 9edaeaa38..e1c1e3fc2 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -3,6 +3,26 @@ ViewData["Title"] = "Update Point of Sale"; }
+ +
@@ -58,9 +78,17 @@
+
+ * + +
+
+
* - +
@@ -101,5 +129,61 @@ + + + + + + + } diff --git a/BTCPayServer/wwwroot/products/js/products.jquery.js b/BTCPayServer/wwwroot/products/js/products.jquery.js new file mode 100644 index 000000000..6f89c4bc4 --- /dev/null +++ b/BTCPayServer/wwwroot/products/js/products.jquery.js @@ -0,0 +1,69 @@ +$(document).ready(function(){ + var products = new Products(), + delay = null; + + $('.js-product-template').on('input', function(){ + products.loadFromTemplate(); + + clearTimeout(delay); + + // Delay rebuilding DOM for performance reasons + delay = setTimeout(function(){ + products.showAll(); + }, 1000); + }); + + $('.js-products').on('click', '.js-product-remove', function(event){ + event.preventDefault(); + + var id = $(this).closest('.card').parent().index(); + + products.removeItem(id); + }); + + $('.js-products').on('click', '.js-product-edit', function(event){ + event.preventDefault(); + + var id = $(this).closest('.card').parent().index(); + + products.itemContent(id); + }); + + $('.js-product-save').click(function(event){ + event.preventDefault(); + + var index = $('.js-product-index').val(), + description = $('.js-product-description').val(), + image = $('.js-product-image').val(), + custom = $('.js-product-custom').val(); + obj = { + id: products.escape($('.js-product-id').val()), + price: products.escape($('.js-product-price').val()), + title: products.escape($('.js-product-title').val()), + }; + + // Only continue if price and title is provided + if (obj.price && obj.title) { + if (description) { + obj.description = products.escape(description); + } + if (image) { + obj.image = products.escape(image); + } + if (custom == 'true') { + obj.custom = products.escape(custom); + } + + // Create an id from the title for a new product + if (!Boolean(index)) { + obj.id = products.escape(obj.title.toLowerCase() + ':'); + } + + products.saveItem(obj, index); + } + }); + + $('.js-product-add').click(function(){ + products.itemContent(); + }); +}); \ No newline at end of file diff --git a/BTCPayServer/wwwroot/products/js/products.js b/BTCPayServer/wwwroot/products/js/products.js new file mode 100644 index 000000000..cc595badf --- /dev/null +++ b/BTCPayServer/wwwroot/products/js/products.js @@ -0,0 +1,186 @@ +function Products() { + this.products = []; + + // Get products from template + this.loadFromTemplate(); + + // Show products in the DOM + this.showAll(); +} + +Products.prototype.loadFromTemplate = function() { + var template = $('.js-product-template').val().trim(), + lines = template.split("\n\n"); + + this.products = []; + + // Split products from the template + for (var kl in lines) { + var line = lines[kl], + product = line.split("\n"), + id, price, title, description, image = null, + custom; + + for (var kp in product) { + var productProperty = product[kp].trim(); + + if (kp == 0) { + id = productProperty; + } + + if (productProperty.indexOf('price:') !== -1) { + price = parseFloat(productProperty.replace('price:', '').trim()); + } + if (productProperty.indexOf('title:') !== -1) { + title = productProperty.replace('title:', '').trim(); + } + if (productProperty.indexOf('description:') !== -1) { + description = productProperty.replace('description:', '').trim(); + } + if (productProperty.indexOf('image:') !== -1) { + image = productProperty.replace('image:', '').trim(); + } + if (productProperty.indexOf('custom:') !== -1) { + custom = productProperty.replace('custom:', '').trim(); + } + } + + if (price != null || title != null) { + // Add product to the list + this.products.push({ + 'id': id, + 'title': title, + 'price': price, + 'image': image || null, + 'description': description || null, + 'custom': Boolean(custom) + }); + } + + } +} + +Products.prototype.saveTemplate = function() { + var template = ''; + + // Construct template from the product list + for (var key in this.products) { + var product = this.products[key], + id = product.id, + title = product.title, + price = product.price, + image = product.image + description = product.description, + custom = product.custom; + + template += id + '\n' + + ' price: ' + price + '\n' + + ' title: ' + title + '\n'; + + if (description) { + template += ' description: ' + description + '\n'; + } + if (image) { + template += ' image: ' + image + '\n'; + } + if (custom) { + template += ' custom: true\n'; + } + template += '\n'; + } + + $('.js-product-template').val(template); +} + +Products.prototype.showAll = function() { + var list = []; + + for (var key in this.products) { + var product = this.products[key], + image = product.image; + + list.push(this.template($('#template-product-item'), { + 'title': this.escape(product.title), + 'image': image ? 'Card image cap' : '' + })); + } + + $('.js-products').html(list); +} + +// Load the template +Products.prototype.template = function($template, obj) { + var template = $template.text(); + + for (var key in obj) { + var re = new RegExp('{' + key + '}', 'mg'); + template = template.replace(re, obj[key]); + } + + return template; +} + +Products.prototype.saveItem = function(obj, index) { + // Edit product + if (index) { + this.products[index] = obj; + } else { // Add new product + this.products.push(obj); + } + + this.saveTemplate(); + this.showAll(); + this.modalEmpty(); +} + +Products.prototype.removeItem = function(index) { + if (this.products.length == 1) { + this.products = []; + $('.js-products').html('No products.'); + } else { + this.products.splice(index, 1); + $('.js-products').find('.card').parent().eq(index).remove(); + } + + this.saveTemplate(); +} + +Products.prototype.itemContent = function(index) { + var product = null, + custom = false; + + // Existing product + if (!isNaN(index)) { + product = this.products[index]; + custom = product.custom; + } + + var template = this.template($('#template-product-content'), { + 'id': product != null ? this.escape(product.id) : '', + 'index': isNaN(index) ? '' : this.escape(index), + 'price': product != null ? this.escape(product.price) : '', + 'title': product != null ? this.escape(product.title) : '', + 'description': product != null ? this.escape(product.description) : '', + 'image': product != null ? this.escape(product.image) : '', + 'custom': '' + }); + + $('#product-modal').find('.modal-body').html(template); +} + +Products.prototype.modalEmpty = function() { + var $modal = $('#product-modal'); + + $modal.modal('hide'); + $modal.find('.modal-body').empty(); +} + +Products.prototype.escape = function(input) { + return ('' + input) /* Forces the conversion to string. */ + .replace(/&/g, '&') /* This MUST be the 1st replacement. */ + .replace(/'/g, ''') /* The 4 other predefined entities, required. */ + .replace(/"/g, '"') + .replace(//g, '>') + ; +} \ No newline at end of file From 00673bdb7fb9f11e0104586be62cc3938f83a03d Mon Sep 17 00:00:00 2001 From: Mario Dian Date: Fri, 14 Dec 2018 16:16:08 +0800 Subject: [PATCH 2/2] Fix product width on smaller screens --- BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index e1c1e3fc2..43ca81ed4 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -131,7 +131,7 @@