This is the second post in the series, In this post we will look on how we can add the cart_item to the drafts module, when the user clicks on ” Draft” button on the Catalog Item page.
As said in the earlier post, the ” My Drafts” Module will look entirely similar to the cart page in 2 -step check out.
Firstly I designed(lifted) a new Cart Page,Most of this cart page is copied from the catalog_cart_default which will be the default cart if no Cart is specified. This condition will be present in the catalog_item UI Macro.
As we don’t want to disturb OOB components,we need a new UI Macro Catalog_cart_draft, which should be attached to “cart” reference field on the ITEM you want to enable Draft functionality.
The code of the catalog_cart_draft is looks something like this :
Name of the UI Macro : catalog_cart_draft
<!--?xml version="1.0" encoding="utf-8" ?-->
<table style="float: right;">
<tbody>
<tr>
<td id="cart"></td>
</tr>
</tbody>
</table>
<input id="sysparm_id" type="hidden" value="${sysparm_id}" />
<table id="qty" style="display: none; width: 100%;">
<tbody>
<tr>
<td colspan="2"><a id="order_now" onclick="orderNow();"><img src="images/order_now.pngx" alt="${gs.getMessage('Submit')}" /></a></td>
</tr>
<!-- Introducing the cart_draft button.Upon click we are calling draftNow()-->
<tr>
<td colspan="2"><a id="cart_draft" onclick="draftNow();"><img src="cart_draft.png" alt="${gs.getMessage('Draft')}" /></a></td>
<!-- These two buttons we will discuss in the later posts --></tr>
<tr>
<td><a onclick="orderEdit();"><img src="images/order_now.pngx" alt="${gs.getMessage('Update Cart')}" /></a></td>
</tr>
<tr>
<td colspan="2"><a id="cart_draft_two" onclick="draftNowTwo();"><img src="cart_draft1.png" alt="${gs.getMessage('Submit')}" /></a></td>
</tr>
</tbody>
</table>
<script type="text/javascript" language="javascript">
var g_cart = new SCCart();</p-->
function draftNowTwo(target){
g_form.setValue('cbx_drft_check','draft');
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){
g_form.setMandatory(fields[i], false);
i++;
}
gel("cart_draft_two").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('HelloWorld');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();
orderEdit(target);
}
function draftNow(){
g_form.setValue('cbx_drft_check','draft');
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){
g_form.setMandatory(fields[i], false);
i++;
}
gel("cart_draft").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('Hello');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();
orderNow();
//window.location.href = "wishlistpage.do?";
}
function scCartOnRender() {
g_cart = new SCCart();
<j:if test="${sc_cat_item.no_order != true}">
g_cart.attachWindow('qty', 'cart', "${gs.getMessage('Order this Item')}");
</j:if>
<j:if test="${sc_cat_item.no_cart == true}">
g_cart.setCartVisible(false);
</j:if>
g_cart.addCartContent();
g_cart.editID = '${sysparm_cart_edit}';
g_cart.getWithBackButton();
}
addRenderEvent(scCartOnRender);
function addToCart() {
var m = g_form.catalogOnSubmit();
if (!m)
return;
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value
// To prevent duplicate key violations due to multiple rapid clicks
// clear the item_guid if not empty and continue with the addToCart
// else return until a new item_guid is returned from the server
if (guid == "")
return;
item_guid.value = "";
// hide the attachment header and delete out attachment name spans
var attachmentList = gel("header_attachment_list");
if (attachmentList) {
var count = attachmentList.childNodes.length;
while (count > 1) {
count--;
var node = attachmentList.childNodes[count];
rel(node);
}
var listLabel = gel("header_attachment_list_label");
listLabel.style.display = "none";
}
g_cart.add(gel("sysparm_id").value, getQuantity(), guid);
}
function orderNow() {
var m = g_form.catalogOnSubmit();
if (!m)
return;
// Disable the Order Now button to prevent muliple item order
// as a result of muliple clicks before navigating away
gel("order_now").onclick = "";
var item_guid = gel("sysparm_item_guid");
if (item_guid)
item_guid = item_guid.value
g_cart.order(gel("sysparm_id").value, getQuantity(), item_guid);
}
function calcPrice() {
g_cart.recalcPrice(gel("sysparm_id").value, getQuantity());
}
function orderEdit(target) {
if (!target)
target = '${sysparm_cart_edit}';
var m = g_form.catalogOnSubmit();
if (!m)
return;
g_cart.edit(target, getQuantity());
}
function proceedCheckout(target) {
var m = g_form.catalogOnSubmit();
if (!m)
return;
g_cart.edit(target, getQuantity());
//g_cart.orderUpdate(target, getQuantity());
}
function getQuantity() {
var quantity = 1;
var quan_widget = gel("quantity");
if (quan_widget)
quantity = quan_widget.value;
return quantity;
}
</script>
When we click on ” order now” button the orderNow() function defined above will be called.g_form.catalogOnSubmit(); function call will in-turn call the processor ServiceCatalog which will invoke the script include ServiceCatalogProcessor.
If You open ServiceCatalogProcessor, it has a lot of if conditions and each if condition will call a Script Include based on the g_request and some parameters which will be sent along.
When Order Now is clicked, CatalogTransactionOrder Script include gets called, and the same script include will be responsible for setting the Response Object parameters.
if (this.action == 'order')
return new CatalogTransactionOrder(this.request, this.response, this.processor);
this.action is a request parameter set which will contain,the ID of the button you just clicked on. Yes I love Service Now too. But that’s not it.The amount of flexibility you have is also really HUGE when the tool is SNOW.
So all we will have to do here is, We will call ordernow() function because it creates a cart_item,but before that we should have a mechanism in which we can identify if the item created, is because of the click on Order_Now or Draft.
Also, We need to take care that, when the user clicks on Draft, all the mandatory fields should become, non-mandatory,also the onSubmit scripts should not run.
g_form.setValue('cbx_drft_check','draft');
var fields = g_form.getEditableFields();
var i=0;
while(fields[i]){
g_form.setMandatory(fields[i], false);
i++;
}
The value cbx_draft_check will be on a variable_set which will be included in all the Items which need Draft enabled.We set this variable,when the Draft button is clicked, and make sure the onSubmit doesn’t run. Also, We will clear this out at the end of each onSubmit( to make sure when the user clicks on Order Now, we want the validations to happen)
How do we identify a cart_item which is created because of the user clicking on Draft ?
I have created 2 fields on the sc_cart_item table
a) stage
b) belongs_to
The stage value will be 0 if the cart_item is created by the normal Ordering way, or 1 if its created when Draft is clicked, and belongs_to will always contain the user to whom the cart item belongs to.
Reason why we have belongs_to :
The relationship between a cart and cart_item is from the Reference field of CART on the cart_item table. This field will contain the reference of the Requester’s cart.
When a user clicks on DRAFT, we remove this link, the link between a cart and cart_item because we don’t want this cart_item to appear in the 2nd cart summary page.
And because we have removed it, we need to track it. So we have this belongs_to field.Anytime you want to link it again(just incase) there is a Script Include with Cart functions which when called will return the Cart ID of the user, use that , get the Cart ID and update the value in the reference of cart_item.
So the field belongs_to.
gel("cart_draft").onclick = "";
var guid;
var item_guid = gel("sysparm_item_guid");
if (item_guid)
guid = item_guid.value;
var ga = new GlideAjax('Hello');
ga.addParam('sysparm_name','hello');
ga.addParam('sysparm_guid',guid);
ga.getXMLWait();
orderNow();
//window.location.href = "wishlistpage.do?";
One more beautiful thing to note : the guid is a random sys_id generated on catalog_item page,using gs.generateID() which is available to this UI Macro as well, because this is called in the context of the catalog_item.
The GUID acts as identifier when the catalog item(i.e when you first click on any item in Service Catalog) is first created and also, the cart item created will have the same sys_id as that of GUID.
I wish cart_item to request_item translation were also this beautiful.
The last piece left is, ” How am I updating the belongs_to and the status.Here is how it is:
The Script Include Hello is as follows :
Name : hello
var Hello = Class.create();
Hello.prototype = Object.extendsObject(AbstractAjaxProcessor, {
hello: function() {
var guid = this.getParameter('sysparm_guid');
var gr = new GlideRecord("u_draft_guide");
gr.initialize();
gr.u_order_id = guid;
var a =gr.insert();
return a;
}
});
Patience.Yes, I am inserting this GUID into a table called u_draft_guide.The reason is SEQUENCING. I will explain, but first let me complete the way I handled this.
The above piece of simple code will insert the record into a table called u_draft_guide. So when ever you click on draft and a cart_item is created, the cart_items sys_id will be available in the table called u_draft_guide.
I have a before insert Business Rule that runs on the sc_cart_item table which will check if the sys_id that is being created( yes in before insert you can still access the sys_id of the record that’s going to get created)
If it sees that there is an entry of sys_id in u_draft_guide table, it understands that this cart_item is created because of clicking on Draft and it does the following things.
1. Clears the CART reference field
2.Maps the belongs_to
3.Changes the status.
Here is how it looks :
var gr= new GlideRecord("u_draft_guide");
gr.addQuery("u_order_id",current.sys_id);
gr.query();
if(gr.next()){
current.cart = '';
current.u_belongsto = gs.getUserID();
current.u_status = '1';
//gs.addInfoMessage("Item added to Draft !");
}
Now the entire setup is complete.Except for the Drafts module page; Here is the code for it. The only thing that I changed in this page is the glide of sc_cart_item and removed the Submit Order button
Type: UI Page
Name : draftpage
In the first Glide of servicecatalog_cart_template(the UI macro rendering the 2nd cart summary page) I have changed the first few lines of Glide:
var sc_cart_item = new GlideRecord('sc_cart_item');
sc_cart_item.addQuery('u_status', 1);
sc_cart_item.addQuery('u_belongsto',system.getUserID());
var sc_cat_item = new GlideRecord('sc_cat_item');
sc_cat_item.initialize();
sc_cart_item.query();
As you can see, i am getting all those cart_items as per the belongs_to and status.
Now coming to the point why do I need all the intermediate table: Its because I wan’t to make damn sure the code runs in order. You could argue and ask me to write a Glide or call a Script Include in the UI Macro itself as shown below
Our lines of Glide/Script Include Invocation
orderNow();
yes I tried that, But many a time, my orderNow() is invoked asynchronously. Hence the Hungama.
Correct me if you see any irresponsible coding.. I had a gun to my head.
Acknowledgments : Loyola, Prashanth.