Moved form to API endpoint + fixed opts

master
Mark Moffat 2019-12-16 13:00:30 +10:30
parent 2f2b050c47
commit f149b87c3a
4 changed files with 62 additions and 99 deletions

View File

@ -112,6 +112,38 @@ $(document).ready(function (){
}); });
}); });
$('#productEditForm').validator().on('submit', function(e){
if(!e.isDefaultPrevented()){
e.preventDefault();
if($('#productPermalink').val() === '' && $('#productTitle').val() !== ''){
$('#productPermalink').val(slugify($('#productTitle').val()));
}
$.ajax({
method: 'POST',
url: '/admin/product/update',
data: {
productId: $('#productId').val(),
productTitle: $('#productTitle').val(),
productPrice: $('#productPrice').val(),
productPublished: $('#productPublished').val(),
productStock: $('#productStock').val(),
productDescription: $('#productDescription').val(),
productPermalink: $('#productPermalink').val(),
productOptions: $('#productOptions').val(),
productSubscription: $('#productSubscription').val(),
productComment: $('#productComment').is(':checked'),
productTags: $('#productTags').val()
}
})
.done(function(msg){
showNotification(msg.message, 'success', true);
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger');
});
}
});
$('.set-as-main-image').on('click', function(){ $('.set-as-main-image').on('click', function(){
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
@ -219,13 +251,6 @@ $(document).ready(function (){
window.location = '/admin/orders/bystatus/' + $('#orderStatusFilter').val(); window.location = '/admin/orders/bystatus/' + $('#orderStatusFilter').val();
}); });
// create a permalink from the product title if no permalink has already been set
$(document).on('click', '#frm_edit_product_save', function(e){
if($('#productPermalink').val() === '' && $('#productTitle').val() !== ''){
$('#productPermalink').val(slugify($('#productTitle').val()));
}
});
// Call to API for a change to the published state of a product // Call to API for a change to the published state of a product
$('input[class="published_state"]').change(function(){ $('input[class="published_state"]').change(function(){
$.ajax({ $.ajax({

File diff suppressed because one or more lines are too long

View File

@ -226,6 +226,9 @@ router.get('/admin/product/edit/:id', restrict, checkAccess, async (req, res) =>
let options = {}; let options = {};
if(product.productOptions){ if(product.productOptions){
options = product.productOptions; options = product.productOptions;
if(typeof product.productOptions !== 'object'){
options = JSON.parse(product.productOptions);
}
} }
// If API request, return json // If API request, return json
@ -258,8 +261,8 @@ router.post('/admin/product/removeoption', restrict, checkAccess, async (req, re
delete opts[req.body.optName]; delete opts[req.body.optName];
try{ try{
const updateOption = await db.products.updateOne({ _id: common.getId(req.body.productId) }, { $set: { productOptions: opts } }); const updateOption = await db.products.findOneAndUpdate({ _id: common.getId(req.body.productId) }, { $set: { productOptions: opts } });
if(updateOption.result.nModified === 1){ if(updateOption.ok === 1){
res.status(200).json({ message: 'Option successfully removed' }); res.status(200).json({ message: 'Option successfully removed' });
return; return;
} }
@ -280,42 +283,12 @@ router.post('/admin/product/update', restrict, checkAccess, async (req, res) =>
const product = await db.products.findOne({ _id: common.getId(req.body.productId) }); const product = await db.products.findOne({ _id: common.getId(req.body.productId) });
if(!product){ if(!product){
req.session.message = 'Failed updating product.'; res.status(400).json({ message: 'Failed to update product' });
req.session.messageType = 'danger';
// If API request, return json
if(req.apiAuthenticated){
res.status(400).json({ message: 'Failed to update product' });
return;
}
res.redirect('/admin/product/edit/' + req.body.productId);
return; return;
} }
const count = await db.products.countDocuments({ productPermalink: req.body.productPermalink, _id: { $ne: common.getId(product._id) } }); const count = await db.products.countDocuments({ productPermalink: req.body.productPermalink, _id: { $ne: common.getId(product._id) } });
if(count > 0 && req.body.productPermalink !== ''){ if(count > 0 && req.body.productPermalink !== ''){
// If API request, return json res.status(400).json({ message: 'Permalink already exists. Pick a new one.' });
if(req.apiAuthenticated){
res.status(400).json({ message: 'Permalink already exists. Pick a new one.' });
return;
}
// permalink exits
req.session.message = 'Permalink already exists. Pick a new one.';
req.session.messageType = 'danger';
// keep the current stuff
req.session.productTitle = req.body.productTitle;
req.session.productDescription = req.body.productDescription;
req.session.productPrice = req.body.productPrice;
req.session.productPermalink = req.body.productPermalink;
req.session.productTags = req.body.productTags;
req.session.productOptions = req.body.productOptions;
req.session.productComment = common.checkboxBool(req.body.productComment);
req.session.productStock = req.body.productStock ? req.body.productStock : null;
// redirect to insert
res.redirect('/admin/product/edit/' + req.body.productId);
return; return;
} }
const images = await common.getImages(req.body.productId, req, res); const images = await common.getImages(req.body.productId, req, res);
@ -345,27 +318,10 @@ router.post('/admin/product/update', restrict, checkAccess, async (req, res) =>
// Validate the body again schema // Validate the body again schema
const schemaValidate = validateJson('editProduct', productDoc); const schemaValidate = validateJson('editProduct', productDoc);
if(!schemaValidate.result){ if(!schemaValidate.result){
// If API request, return json res.status(400).json({
if(req.apiAuthenticated){ message: 'Form invalid. Please check values and try again.',
res.status(400).json(schemaValidate.errors); error: schemaValidate.errors
return; });
}
req.session.message = 'Form invalid. Please check values and try again.';
req.session.messageType = 'danger';
// keep the current stuff
req.session.productTitle = req.body.productTitle;
req.session.productDescription = req.body.productDescription;
req.session.productPrice = req.body.productPrice;
req.session.productPermalink = req.body.productPermalink;
req.session.productOptions = productOptions;
req.session.productComment = common.checkboxBool(req.body.productComment);
req.session.productTags = req.body.productTags;
req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null;
// redirect to insert
res.redirect('/admin/product/edit/' + req.body.productId);
return; return;
} }
@ -388,27 +344,10 @@ router.post('/admin/product/update', restrict, checkAccess, async (req, res) =>
// Update the index // Update the index
indexProducts(req.app) indexProducts(req.app)
.then(() => { .then(() => {
// If API request, return json res.status(200).json({ message: 'Successfully saved', product: productDoc });
if(req.apiAuthenticated){
res.status(200).json({ message: 'Successfully saved', product: productDoc });
return;
}
req.session.message = 'Successfully saved';
req.session.messageType = 'success';
res.redirect('/admin/product/edit/' + req.body.productId);
}); });
}catch(ex){ }catch(ex){
// If API request, return json res.status(400).json({ message: 'Failed to save. Please try again' });
if(req.apiAuthenticated){
res.status(400).json({ message: 'Failed to save. Please try again' });
return;
}
console.error(colors.red('Failed to save product: ' + ex));
req.session.message = 'Failed to save. Please try again';
req.session.messageType = 'danger';
res.redirect('/admin/product/edit/' + req.body.productId);
} }
}); });

View File

@ -1,18 +1,18 @@
{{> partials/menu}} {{> partials/menu}}
<div class="col-lg-9"> <div class="col-lg-9">
<form method="post" class="form-horizontal" id="insert_form" action="/admin/product/update" data-toggle="validator"> <form class="form-horizontal" id="productEditForm" data-toggle="validator">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="page-header"> <div class="page-header">
<div class="pull-right"> <div class="pull-right">
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#myModal">{{ @root.__ "Upload image" }}</button> <button type="button" class="btn btn-info" data-toggle="modal" data-target="#myModal">{{ @root.__ "Upload image" }}</button>
<button id="frm_edit_product_save" class="btn btn-success">{{ @root.__ "Save product" }} <i class="fa fa-floppy-o"></i></button> <button id="productUpdate" class="btn btn-success">{{ @root.__ "Save product" }} <i class="fa fa-floppy-o"></i></button>
</div> </div>
<h2>{{ @root.__ "Edit product" }}</h2> <h2>{{ @root.__ "Edit product" }}</h2>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="productTitle" class="col-sm-2 control-label">{{ @root.__ "Product title" }} *</label> <label for="productTitle" class="col-sm-2 control-label">{{ @root.__ "Product title" }} *</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="productTitle" class="form-control" minlength="5" maxlength="200" value="{{result.productTitle}}" required/> <input type="text" id="productTitle" class="form-control" minlength="5" maxlength="200" value="{{result.productTitle}}" required/>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -20,14 +20,14 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon">{{currencySymbol config.currencySymbol}}</span> <span class="input-group-addon">{{currencySymbol config.currencySymbol}}</span>
<input type="number" name="productPrice" class="form-control" value="{{result.productPrice}}" step="any" required/> <input type="number" id="productPrice" class="form-control" value="{{result.productPrice}}" step="any" required/>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="productPublished" class="col-sm-2 control-label">{{ @root.__ "Status" }}</label> <label for="productPublished" class="col-sm-2 control-label">{{ @root.__ "Status" }}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<select class="form-control" id="productPublished" name="productPublished"> <select class="form-control" id="productPublished">
<option value="true" {{selectState result.productPublished "true"}}>{{ @root.__ "Published" }}</option> <option value="true" {{selectState result.productPublished "true"}}>{{ @root.__ "Published" }}</option>
<option value="false" {{selectState result.productPublished "false"}}>{{ @root.__ "Draft" }}</option> <option value="false" {{selectState result.productPublished "false"}}>{{ @root.__ "Draft" }}</option>
</select> </select>
@ -37,21 +37,21 @@
<div class="form-group"> <div class="form-group">
<label for="productStock" class="col-sm-2 control-label">{{ @root.__ "Stock level" }}</label> <label for="productStock" class="col-sm-2 control-label">{{ @root.__ "Stock level" }}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="number" name="productStock" class="form-control" value="{{result.productStock}}" step="any" /> <input type="number" id="productStock" class="form-control" value="{{result.productStock}}" step="any" />
</div> </div>
</div> </div>
{{/if}} {{/if}}
<div class="form-group"> <div class="form-group">
<label for="editor" class="col-sm-2 control-label">{{ @root.__ "Product description" }} *</label> <label for="productDescription" class="col-sm-2 control-label">{{ @root.__ "Product description" }} *</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea id="editor" minlength="5" rows="10" id="productDescription" name="productDescription" class="form-control" required>{{result.productDescription}}</textarea> <textarea minlength="5" rows="10" id="productDescription" class="form-control" required>{{result.productDescription}}</textarea>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label">Permalink</label> <label class="col-sm-2 control-label">Permalink</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" name="productPermalink" id="productPermalink" placeholder="Permalink for the article" value={{result.productPermalink}}> <input type="text" class="form-control" id="productPermalink" placeholder="Permalink for the article" value={{result.productPermalink}}>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-success" id="validate_permalink" type="button">{{ @root.__ "Validate" }}</button> <button class="btn btn-success" id="validate_permalink" type="button">{{ @root.__ "Validate" }}</button>
</span> </span>
@ -60,8 +60,8 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="hidden" id="productOptions" name="productOptions" value="{{stringify result.productOptions}}" /> <input type="hidden" id="productOptions" value="{{stringify result.productOptions}}" />
<label for="editor" class="col-sm-2 control-label">{{ @root.__ "Product options" }}</label> <label class="col-sm-2 control-label">{{ @root.__ "Product options" }}</label>
<div class="col-lg-10"> <div class="col-lg-10">
<ul class="list-group" id="product_opt_wrapper"> <ul class="list-group" id="product_opt_wrapper">
<li class="list-group-item"> <li class="list-group-item">
@ -111,7 +111,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label">Subscription plan</label> <label class="col-sm-2 control-label">Subscription plan</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="productSubscription" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX" value={{@root.result.productSubscription}}> <input type="text" class="form-control" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX" value={{@root.result.productSubscription}}>
<p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p> <p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p>
</div> </div>
</div> </div>
@ -121,8 +121,7 @@
<div class="col-sm-10"> <div class="col-sm-10">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input class="productComment" type="checkbox" {{checkedState result.productComment}} id="productComment" <input class="productComment" type="checkbox" {{checkedState result.productComment}} id="productComment">
name="productComment">
</label> </label>
</div> </div>
<p class="help-block">{{ @root.__ "Allow free form comments when adding products to cart" }}</p> <p class="help-block">{{ @root.__ "Allow free form comments when adding products to cart" }}</p>
@ -131,7 +130,7 @@
<div class="form-group"> <div class="form-group">
<label for="productTags" class="col-sm-2 control-label">{{ @root.__ "Product tag words" }}</label> <label for="productTags" class="col-sm-2 control-label">{{ @root.__ "Product tag words" }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="productTags" name="productTags" value="{{result.productTags}}"> <input type="text" class="form-control" id="productTags" value="{{result.productTags}}">
<p class="help-block">{{ @root.__ "Tag words used to indexed products, making them easier to find and filter." }}</p> <p class="help-block">{{ @root.__ "Tag words used to indexed products, making them easier to find and filter." }}</p>
</div> </div>
</div> </div>
@ -160,7 +159,7 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
<input type="hidden" name="productId" id="productId" value="{{result._id}}" /> <input type="hidden" id="productId" value="{{result._id}}" />
</form> </form>
</div> </div>
@ -190,7 +189,7 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.css" rel="stylesheet">
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#editor').summernote({ $('#productDescription').summernote({
height: 300, height: 300,
minHeight: null minHeight: null
}); });