This section describes how to use the JqWidgets API controls and implement a Add New Row functionality (where a new row is dynamically added to a table). All JqWidgets controls are JQuery controls and the JQuery notation and code is extensively used in developing this UI. The page in question is the Create Vouchers page.
The top of the page contains the list of JS and css files required.
<script src="/omni/js/autoNumeric/autoNumeric.js"></script>
<link rel="stylesheet" href="/omni/js/jqwidgets/styles/jqx.base.css" type="text/css" />
<link rel="stylesheet" href="/omni/js/jqwidgets/styles/jqx.ui-sunny.css" type="text/css" />
<script type="text/javascript" src="/omni/js/jqwidgets/jqx-all.js"></script>
autoNumeric.js is the JS library used for showing the numbers in a standard formatted string with thousand, million separators.
jqx.base.css and jqx.ui-sunny.css are the CSS files used for the jqWidgets API.
jqx-all.js contains the jqWidgets controls that are used in this screen.
The next section contains the declaration of all the data-sources. The data-source is a Javascript Array that is used to load all the different drop-downs and combo-boxes used in the screen.
Remote Data Sources
The first set of data-sources are those that are loaded remotely via a call to the Web Service defined in the DNN instance. These data sources are used to load the combo-boxes for jobs,ledgers and parties.
var job_source =
{
datatype: "json",
datafields: [
{ name: 'jobdesc' },
{ name: 'jobid' }
],
url: '/omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCEGetJobs',
type: 'POST',
data: {"accentreid":"[OMNI:GetDefaultAccountCentre.account_centre_id]"}
};
var ledger_source =
{
datatype: "json",
datafields: [
{ name: 'ledgername' },
{ name: 'ledgerid' }
],
url: '/omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCEGetLedgers',
type: 'GET',
data: {"searchString":""}
};
var party_source =
{
datatype: "json",
datafields: [
{ name: 'partyid' },
{ name: 'partyname' },
{ name: 'partytype' },
],
url: '/omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCEGetParties',
type: 'GET',
data: {"searchString":""}
};
Every data source has 5 fields:
datatype : "json" denotes the type of the data returned by the Web Service
datafields: These are the fields being returned by the Web Service
url: This is the URL of the Web service
type: Whether a GET or POST request is being sent to the webservice.
data: The input to the Web Service.
All the Web Services are in the page called API under Home the module used is DnnApiEndpoint. For instance the service used for getting ledgers is:
Local Data Sources
The subsequent set of data-sources are loaded when the page is generated i.e. the data is retrieved through a razor template and a MyToken instance.
var bankList = [
<razor>
@{
var hasComma = false;
foreach(var dbRowB in OMNIFINANCE.GetBankLedgerList())
{
if(hasComma){
<text>,</text>
}
<text>{ ledgerd: '@System.Web.HttpUtility.HtmlEncode(dbRowB.bank_ac_name)', ledgerid: '@dbRowB.ledger_id'}</text>
hasComma = true;
}
}
</razor>
];
var bank_source =
{
datatype: "json",
datafields: [
{ name: 'ledgerd' },
{ name: 'ledgerid' }
],
localdata: bankList
};
bankList is an array that is loaded through a <razor> template it calls the MyToken OMNIFINANCE.GetBankLedgerList and iterates through the records returned by the token. For each record a JSON object with the fields 'ledgerd' and ledgerid' is created.
bankList is then loaded into a datasource called bank_source.
The bank_source data source is used to load the combo-box of banks in the case of Bank vouchers.
Similarly there is an array called cashList and a data source called cash_source for loading the combo-box for Cash Accounts in case of Cash Vouchers.
The next block of code creates the fields in the header section the jquery syntax : $(document).ready
is a function that is executed when the page completes loading.
$(document).ready(function () {
// add the drop-down for the financial periods
var fps = [
<razor>
@{
var hasComma = false;
foreach(var dbRow0 in OMNIFINANCE.GetFinancialPeriods())
{
if(hasComma){
<text>,</text>
}
<text>{ fpd: '@System.Web.HttpUtility.HtmlEncode(dbRow0.description)', fpid: '@dbRow0.fin_period_id'}</text>
hasComma = true;
}
}
</razor>
];
// prepare the data. Set the datatype to 'json', 'xml', 'tsv', 'array', 'local' or 'csv.
var finperiod_source =
{
datatype: "json",
datafields: [
{ name: 'fpd' },
{ name: 'fpid' }
],
localdata: fps
};
// create a new instance of the jqx.dataAdapter.
var dataAdapter0 = new $.jqx.dataAdapter(finperiod_source);
// Create a combo for the fin period
$("#dv_finperiod").jqxComboBox({ source: dataAdapter0, displayMember: "fpd", valueMember: "fpid"});
$("#dv_finperiod").jqxComboBox('selectItem','[OMNI:GetCurrentFinancialPeriod]');
finperiod_source is the datasource based on the array 'fps' that is used to load the drop-down for financial period. The code for $("#dv_finperiod").jqxComboBox({ source: dataAdapter0, displayMember: "fpd", valueMember: "fpid"}); loads the Combobox and uses the "fpd" field as the display and the "fpid" field as the value. The following line $("#dv_finperiod").jqxComboBox('selectItem','[OMNI:GetCurrentFinancialPeriod]'); sets the default selection as the current financial period. For details on the API see: http://www.jqwidgets.com/jquery-widgets-documentation/documentation/jqxcombobox/jquery-combobox-getting-started.htm
The next section sets up the combo-box for the voucher type
// Now add the drop-down for the voucher type
var vts = [
<razor>
@{
var hasComma = false;
foreach(var dbRow1 in OMNIFINANCE.GetVoucherTypes())
{
if(hasComma){
<text>,</text>
}
<text>{ vtd: '@System.Web.HttpUtility.HtmlEncode(dbRow1.description)', vtid: '@dbRow1.voucher_type_id'}</text>
hasComma = true;
}
}
</razor>
];
// prepare the data. Set the datatype to 'json', 'xml', 'tsv', 'array', 'local' or 'csv.
var vt_source =
{
datatype: "json",
datafields: [
{ name: 'vtd' },
{ name: 'vtid' }
],
localdata: vts
};
// create a new instance of the jqx.dataAdapter.
var dataAdapter1 = new $.jqx.dataAdapter(vt_source);
// Create a combo for the voucher type
$("#dv_vchrtype").jqxComboBox({ source: dataAdapter1, displayMember: "vtd", valueMember: "vtid"});
// depending on the voucher type chosen display the bank and cash entry rows
$("#dv_vchrtype").on('select', function (event)
{
$('#payment_entries').attr('style','display: none');
var args = event.args;
if (args) {
// index represents the item's index.
var index = args.index;
var item = args.item;
// get item's label and value.
var label = item.label;
var value = item.value;
if(label.indexOf('C01') > 0)
{
// cash payment
$('#payment_entries').attr('style','display: block');
// set the legend
$('#payment_legend').html('Cash Payment');
setFieldsForPayment(cash_source, 'payment',0);
$('#payment_header').html('Cash Ledger');
} else if(label.indexOf('C08') > 0)
{
// cash receipt
$('#payment_entries').attr('style','display: block');
// set the legend
$('#payment_legend').html('Cash Receipt');
setFieldsForPayment(cash_source, 'receipt',0);
$('#payment_header').html('Cash Ledger');
}
else if(label.indexOf('B01') > 0)
{
// bank payment
$('#payment_entries').attr('style','display: block');
// set the legend
$('#payment_legend').html('Bank Payment');
setFieldsForPayment(bank_source, 'payment',1);
$('#payment_header').html('Bank Ledger');
}
else if(label.indexOf('B08') > 0)
{
// bank receipt
$('#payment_entries').attr('style','display: block');
// set the legend
$('#payment_legend').html('Bank Receipt');
setFieldsForPayment(bank_source, 'receipt',1);
$('#payment_header').html('Bank Ledger');
}
}
});
The first few lines of this section is similar to the earlier fields except the section beginning with $("#dv_vchrtype").on('select', function (event) - this sets up the handler used for the 'select' event i.e. when the user selects a option in the Combo-box
In this case the handler checks if the voucher type is a Cash/Bank Payment or Receipt and shows the row to choose the Bank or Cash Ledgers. It also labels the legend for the section adequately.
The next block of code sets up the remaining fields i.e. currency, date and accounting centre.
// Now add the drop-down for the currency
var curr = [
<razor>
@{
var hasComma = false;
foreach(var dbRow2 in OMNIFINANCE.GetCurrencyList())
{
if(hasComma){
<text>,</text>
}
<text>{ cd: '@System.Web.HttpUtility.HtmlEncode(dbRow2.currency)', cid: '@dbRow2.currency_code'}</text>
hasComma = true;
}
}
</razor>
];
// prepare the data. Set the datatype to 'json', 'xml', 'tsv', 'array', 'local' or 'csv.
var c_source =
{
datatype: "json",
datafields: [
{ name: 'cd' },
{ name: 'cid' }
],
localdata: curr
};
// create a new instance of the jqx.dataAdapter.
var dataAdapter2 = new $.jqx.dataAdapter(c_source);
// Create a combo for the currency list
$("#dv_currency").jqxComboBox({ source: dataAdapter2, displayMember: "cd", valueMember: "cid"});
var defCurr = '[OMNI:GetNativeCurrency.currency_code]';
// set the default currency
$("#dv_currency").jqxComboBox('selectItem',defCurr);
// set the date format
$("#dv_vdate").jqxDateTimeInput({theme: 'ui-sunny',formatString: 'dd-MM-yyyy'});
// Now add the drop-down for the accounting center
var accenter = [
<razor>
@{
var hasComma = false;
foreach(var dbRow3 in OMNIFINANCE.GetAccountCentreList())
{
if(hasComma){
<text>,</text>
}
<text>{ acd: '@System.Web.HttpUtility.HtmlEncode(dbRow3.accenter)', acid: '@dbRow3.account_centre_id'}</text>
hasComma = true;
}
}
</razor>
];
// prepare the data. Set the datatype to 'json', 'xml', 'tsv', 'array', 'local' or 'csv.
var ac_source =
{
datatype: "json",
datafields: [
{ name: 'acd' },
{ name: 'acid' }
],
localdata: accenter
};
// create a new instance of the jqx.dataAdapter.
var dataAdapter3 = new $.jqx.dataAdapter(ac_source);
// Create a combo for the accounting centre
$("#dv_accenter").jqxComboBox({ source: dataAdapter3, displayMember: "acd", valueMember: "acid", width: "566px"});
// change the data source of the job list
$("#dv_accenter").on('select', function (event)
{
var args = event.args;
if (args) {
// index represents the item's index.
var index = args.index;
var item = args.item;
// get item's label and value.
var label = item.label;
var value = item.value;
// value is the accounting centre id
// change the job source's list
job_source.data.accentreid = value;
}
});
A handler function is defined for the 'select' event for the accounting centre Combo Box. In this case if the accounting centre is changed then the accounting centre ID that is set as the variable in the Job List data source is changed so that only those jobs belonging to the Chosen Accounting Centre is displayed in the Jobs Combo Box.
// set the formats for the div showing the total of the DR and CR amounts
$('#total_dr_amt').autoNumeric('init', {dGroup: '[OMNI:GetNativeCurrency.c_group]', aSign: '[OMNI:GetNativeCurrency.display_code]'});
// set the formats for the div showing the total of the DR and CR amounts
$('#total_cr_amt').autoNumeric('init', {dGroup: '[OMNI:GetNativeCurrency.c_group]', aSign: '[OMNI:GetNativeCurrency.display_code]'});
The function autoNumeric displays the DR and CR amounts in terms of a formatted text with the appropriate locale format and the currency symbol.
Validator Rules
The next section has the validation rules that apply to all the fields on the form. The validation rules use the JqWidgets validator see: http://www.jqwidgets.com/jquery-widgets-documentation/documentation/jqxvalidator/jquery-validator-getting-started.htm
// now set the rules for validation of the header fields
$('#voucher_fields').jqxValidator({
rules: [
{
input: '#dv_vchrtype',
message: 'Choose a Voucher Type.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('#dv_vchrtype');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#dv_currency',
message: 'Choose a Currency.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('#dv_currency');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
}, {
input: '#dv_finperiod',
message: 'Choose a Financial Period.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('#dv_finperiod');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
}, {
input: '#dv_vdate',
message: 'Choose a Date.',
action: 'valuechanged',
rule: function(){return checkIfDateChosen('#dv_vdate');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#dv_accenter',
message: 'Choose an Accounting Center.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('#dv_accenter');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
}, {
input: '#payment_0_jobS',
message: 'Choose a Job.',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if(item.label.indexOf('C01')>0 || item.label.indexOf('C08')>0
|| item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
return checkIfComboBoxChosen('#payment_0_jobS');
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#payment_0_ledgerS',
message: 'Choose a Ledger.',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if(item.label.indexOf('C01')>0 || item.label.indexOf('C08')>0
|| item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
return checkIfComboBoxChosen('#payment_0_ledgerS');
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#payment_0_partyS',
message: 'Choose a Party.',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if(item.label.indexOf('C01')>0 || item.label.indexOf('C08')>0
|| item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
return checkIfComboBoxChosen('#payment_0_partyS');
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#instrument_date',
message: 'Choose the Date.',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if( item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
return checkIfDateChosen('#instrument_date');
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#instrument_no',
message: 'Too many characters (max 30)',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if( item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
var input = '#instrument_no';
if($(input).val() != null && $(input).val() != undefined)
{
if($(input).val().length > 30) return false;
else return true;
} else return true;
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '#instrument_details',
message: 'Too many characters (max 55)',
action: 'valuechanged',
rule: function(){
// first check if the voucher type chosen is a bank or cash voucher
// then validate required
var item = $("#dv_vchrtype").jqxComboBox('getSelectedItem');
if( item.label.indexOf('B01')>0 || item.label.indexOf('B08')>0)
{
var input = '#instrument_details';
if($(input).val() != null && $(input).val() != undefined)
{
if($(input).val().length > 55) return false;
else return true;
} else return true;
} else return true;
},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '.jobS',
message: 'Choose a Job.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('.jobS');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '.ledgerS',
message: 'Choose a Ledger.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('.ledgerS');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
},{
input: '.partyS',
message: 'Choose a Party.',
action: 'valuechanged',
rule: function(){return checkIfComboBoxChosen('.partyS');},
hintRender: function(message,input){return showAlertForFieldAsRequired(message,input);}
}
]
});
The validation is performed by a set of rules, each rule has the following fields:
input - this is the jquery selector for the input that it applies to it can be an ID or a class. To specify an ID use '#<id>', to specify a class use '.<class name>'
message - the message to be displayed if the validation fails.
action - the action event supported by the input field
rule - the function to be called to check if the field is valid or not. This also supports some standard validation rules see: http://www.jqwidgets.com/jquery-widgets-documentation/documentation/jqxvalidator/jquery-validator-api.htm
hintRender - this shows the function to be called when the validation fails - typically used to highlight the field that is failing validation.
Data is saved when the Save button is clicked, once clicked the following sequence of events occur:
Validate the fields - run the validation rules defined above.
Check that the DR and CR amounts match for all entries in the voucher.
Collect all the fields and then send a POST request to the Web Service that will save the voucher.
// now this function is executed to actually save the voucher
$("#createVoucher").click(function () {
// first validate all the fields
$('#voucher_fields').jqxValidator('validate');
if(checkVoucherAmount())
{
console.log('ready to save a voucher');
// get the header related variables
var vType = $("#dv_vchrtype").jqxComboBox('getSelectedItem').value;
var vTypeName = $("#dv_vchrtype").jqxComboBox('getSelectedItem').label;
var currency = $("#dv_currency").jqxComboBox('getSelectedItem').value;
var finYr = $("#dv_finperiod").jqxComboBox('getSelectedItem').value;
var vDate = $("#dv_vdate").val();
var acCentre = $("#dv_accenter").jqxComboBox('getSelectedItem').value;
// now check if the payment/receipt related entry is there
var pJob = '';
var pLedger = '';
var pParty = '';
var pDr = '';
var pCr = ''
var pNarration = '';
var pRefType = '';
var pRefID = '';
var pBankPMode = '';
var pBankPDate = '';
var pBankPNumber = '';
var pBankPDetails = '';
if(vTypeName.indexOf('C01')>0 || vTypeName.indexOf('C08')>0
|| vTypeName.indexOf('B01')>0 || vTypeName.indexOf('B08')>0)
{
pJob = $("#payment_0_jobS").jqxComboBox('getSelectedItem').value;
pLedger = $("#payment_0_ledgerS").jqxComboBox('getSelectedItem').value;
pParty = $("#payment_0_partyS").jqxComboBox('getSelectedItem').value;
pDr = $("#payment_0_dr_amtS").val();
pCr = $("#payment_0_cr_amtS").val();
pNarration = $("#payment_0_narrationS").val();
if(vTypeName.indexOf('B01')>0 || vTypeName.indexOf('B08'))
{
pBankPMode = $("#instrument_mode").jqxDropDownList('getSelectedItem').value;
pBankPDate = $("#instrument_date").val();
pBankPNumber = $('#instrument_no').val();
pBankPDetails = $('#instrument_details').val();
}
// and finally the reference
pRefType = $('#payment_0_refS').data('refType');
pRefID = refEntriesList.push($('#payment_0_refS').data('refID'));
}
// get the voucher entries into the arrays
recordEntries();
// now send the POST request
$.ajax({
type: 'POST',
url: '/omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCECreateVoucher',
data: {
voucherType : vType,
currency : currency,
finYr : finYr,
voucherDate : vDate,
accountingCentre : acCentre,
paymentJob : pJob,
paymentLedger : pLedger,
paymentParty : pParty,
paymentDr : pDr,
paymentCr : pCr,
paymentNarration : pNarration,
paymentRefType : pRefType,
paymentRefID : pRefID,
paymentBankMode : pBankPMode,
paymentBankDate : pBankPDate,
paymentBankNumber : pBankPNumber,
paymentBankDetails : pBankPDetails,
jobList : jobEntriesList.join(),
ledgerList : ledgerEntriesList.join(),
partyList : partyEntriesList.join(),
drList : drEntriesList.join(),
crList : crEntriesList.join(),
narrationList : narrationEntriesList.join(),
refList : refEntriesList.join(),
refTypeList : refTypeList.join()
},
beforeSend:function(){
// this is where we append a loading image
$('#createVoucher').attr('disabled','disabled');
$('#createVoucher').html('Please Wait...<img src="/omni/DesktopModules/AvatarSoft/ActionForm/static/loader/rounded-blocks.GIF" width="24px" alt="Loading..." />');
}
}).
done(function(data){
// successful request; do something with the data
$('#success-panel').empty();
// now check what the result value is
if(data.Success == 1)
{
$('#voucher_fields').hide();
// show the success message
$('#success-panel').attr('class','alert alert-success');
$('#success-panel').html('The Voucher: <strong>' + data.VoucherCode + '</strong> was successfully created.');
$('#success-btn-panel').html('<a href="../Vouchers.aspx" class="btn btn-info">OK</a> <a href="CreateVoucher.aspx" class="btn btn-info">Create Another Voucher</a>');
}
else
{
// show the error message
$('#success-panel').attr('class','alert alert-danger alert-dismissable');
$('#success-panel').html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button><strong>Error: ' + data.Error + '.</strong> Please try again!');
$('#createVoucher').removeAttr('disabled');
$('#createVoucher').html('Save');
}
}).
fail(function(){
// failed request; give feedback to user
$('#success-panel').attr('class','alert alert-warning alert-dismissable');
$('#success-panel').html('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button><p class="text-danger">There was a temporary error, please try again in a few moments.</p>');
$('#createVoucher').removeAttr('disabled');
$('#createVoucher').html('Save');
});
}
});
beforeSend function is executed before the data is actually sent to the Web Service.
done is called after the response is returned by the Web Service.
fail is called if the POST request to the Web Service fails.
The following section contains functions that are called throughout the page:
destroyFieldsForPayment - this function is called for removing the various input fields in the Bank and Cash Vouchers sections.
setFieldsForPayment - this function displays the fields that are used to display the various input fields.
checkIfDateChosen - this function is called to check if a Date field is chosen by the user.
checkIfComboBoxChosen - this function is called to check if a Combo Box field is chosen by the user.
function destroyFieldsForPayment()
{
$("#payment_0_jobS").jqxComboBox('clear');
$("#payment_0_ledgerS").jqxComboBox('clear');
$("#payment_0_partyS").jqxComboBox('clear');
$("#payment_0_cr_amtS").attr('style','display: none');
$("#payment_0_dr_amtS").attr('style','display: none');
}
// this function displays the rows necessary for the banl and cash entries.
function setFieldsForPayment(ledgerSource, type,bankTrans)
{
// rebuild the dataadapter and combo box for jobs
var jobdataAdapter = new $.jqx.dataAdapter(job_source);
$("#payment_0_jobS").jqxComboBox({ source: jobdataAdapter, displayMember: "jobdesc", valueMember: "jobid"});
// rebuild the dataadapter and combo box for ledgers
var ledgerdataAdapter = new $.jqx.dataAdapter(ledgerSource);
$("#payment_0_ledgerS").jqxComboBox({ source: ledgerdataAdapter, displayMember: "ledgerd", valueMember: "ledgerid"});
// rebuild the combo box for the party
var partydataAdapter = new $.jqx.dataAdapter(party_source);
$("#payment_0_partyS").jqxComboBox({ source: partydataAdapter, displayMember: "partyname", valueMember: "partyid", remoteAutoComplete: true,
autoComplete: true,
search: function (searchString) {
party_source.data.searchString = searchString;
partydataAdapter.dataBind();
}});
// set the textfields
if(type == "payment")
{
$("#payment_0_cr_amtS").attr('style','display: block');
// show only the cr field
$("#payment_0_cr_amtS").jqxNumberInput({width:"50",inputMode: "simple"});
$("#payment_0_cr_amtS").on('change', function (event)
{
calculateTotalCR();
});
} else
{
$("#payment_0_dr_amtS").attr('style','display: block');
$("#payment_0_dr_amtS").jqxNumberInput({width:"50",inputMode: "simple"});
$("#payment_0_dr_amtS").on('change', function (event)
{
calculateTotalDR();
});
}
// add the narration field
$("#payment_0_narrationS").attr('style','display: block');
// add the reference field handler
$("#payment_0_ref").data("rowid","payment_0");
$("#payment_0_ref").click(function()
{
showRefModal(this);
}
);
if(bankTrans == 1)
{
$("#payment_instrument_0").attr('style','display: block');
// now set the fields to track the details of the instrument used
// add a mode drop down
var mode_type_source = [
"Cheque",
"Bank Transfer",
"Bank Draft",
"Other"
];
// Create a jqxDropDownList
$("#instrument_mode").jqxDropDownList({ source: mode_type_source, selectedIndex: 0, width:100,height:25});
// add a date for the instrument
$("#instrument_date").jqxDateTimeInput({theme: 'ui-sunny',formatString: 'dd-MM-yyyy',width:95,height:25});
$("#instrument_no").jqxInput({ placeHolder: "Number", height: 25, width: 90, minLength: 1});
// change the label
if(type == "payment")
{
$("#instrument_details").jqxInput({ placeHolder: "FBO", height: 25, width: 90});
} else
{
$("#instrument_details").jqxInput({ placeHolder: "Drawee Bank", height: 25, width: 90});
}
} else
{
$("#payment_instrument_0").attr('style','display: none');
}
}
function checkIfDateChosen(fieldRef)
{
var getDate= $(fieldRef).jqxDateTimeInput('getDate');
if(getDate === undefined || getDate === null)
{
return false;
}
else return true; // the date has been chosen
}
function checkIfComboBoxChosen(fieldRef)
{
var validRef = true;
$(fieldRef).each(function(i, obj) {
var item = $(this).jqxComboBox('getSelectedItem');
// check if something is selected
if(item === undefined || item === null)
{
validRef = false;
return false;
}
});
return validRef; // check if it is valid
}
function showAlertForFieldAsRequired(message,fieldRef)
{
var spId = $(fieldRef).attr('id') + "_alert";
$(fieldRef).css('border-color','#a94442');
var $alertSpan = $('<span id=\"'+spId+'\" for=\"'+$(fieldRef).attr('id')+'\" class=\"text-danger\">'+message+'</span>');
$(fieldRef).after($alertSpan);
var tM = setTimeout(function(){$('#'+spId).remove();},10000);
return true;
}
function showRefModal(btnClick)
{
var JobDDId = "#" + $(btnClick).data("rowid") +"_jobS";
if(checkIfComboBoxChosen(JobDDId))
{
$('#refdetail').data("jobid",$(JobDDId).jqxComboBox('getSelectedItem').value);
// also set the row number of the clicked button
$('#refdetail').data("rowid",$(btnClick).data("rowid"));
// show the modal
$('#refModal').modal('show');
} else
{
showAlertForFieldAsRequired('Choose a Job.',JobDDId);
}
}
function calculateTotalDR()
{
console.log('calling total dr');
drAmt = 0;
// loop through all DR fields
$(".dr_amtS").each(function() {
var value = $(this).jqxNumberInput('val');
if(!isNaN(value)){
drAmt += value;
}
});
// set the total value
$("#total_dr_amt").autoNumeric('set', drAmt);
}
function calculateTotalCR()
{
console.log('calling total cr');
crAmt = 0;
// loop through all CR fields
$(".cr_amtS").each(function() {
var value = $(this).jqxNumberInput('val');
if(!isNaN(value)){
crAmt += value;
}
});
// set the total value
$("#total_cr_amt").autoNumeric('set', crAmt);
}
function checkVoucherAmount()
{
if(drAmt != crAmt)
{
// show alert
$('#total_dr_amt').after('<span id=\"unbalance_lbl\" class=\"label label-danger\">Unbalanced Transaction</span>');
var tM = setTimeout(function(){$('#unbalance_lbl').remove();},10000);
return false;
}
return true;
}
showAlertsForFieldAsRequired - if a required field fails validation this function highlights the field to the user.
showRefModal - This function shows the dialog (pop-up) that allows the user to choose a reference, This function is called when the user clicks on the Magnifying glass in each row.
calculateTotalDR and calculateTotalCR - this function loops through the rows and calculates the DR and CR amounts.
checkVoucherAmount - verifies that the DR and CR amounts balance.
The next section contains the functionality for adding a new row to the table:
// setup the row clone code - when executed this will add a new row to the main table
jQuery(function($) {
// close the row to be added.
var $button = $('#add-row'),
$row = $('.entry-row').clone(),
$lastrow = $('#last-row');
// remove the row that has been cloned.
$('.entry-row').remove();// first row no longer required
// the click of this button - will add a new row to the table
$button.click(function() {
var $newRow = $row.clone();
// rebuild the dataadapter and combo box for jobs
var jobdataAdapter = new $.jqx.dataAdapter(job_source);
$newRow.find(".jobS").jqxComboBox({ source: jobdataAdapter, displayMember: "jobdesc", valueMember: "jobid"});
// set the ID of the job combo
$newRow.find(".jobS").attr("id",rowCnt +"_jobS");
// rebuild the dataadapter and combo box for ledgers
var ledgerdataAdapter = new $.jqx.dataAdapter(ledger_source);
$newRow.find(".ledgerS").jqxComboBox({ source: ledgerdataAdapter, displayMember: "ledgername", valueMember: "ledgerid",autoComplete: true,
remoteAutoComplete: true,
search: function (searchString) {
ledger_source.data.searchString = searchString;
ledgerdataAdapter.dataBind();
}
});
// set the ID of the ledger combo
$newRow.find(".ledgerS").attr("id",rowCnt +"_ledgerS");
// rebuild the combo box for the party
var partydataAdapter = new $.jqx.dataAdapter(party_source);
$newRow.find(".partyS").jqxComboBox({ source: partydataAdapter, displayMember: "partyname", valueMember: "partyid", remoteAutoComplete: true,
autoComplete: true,
search: function (searchString) {
party_source.data.searchString = searchString;
partydataAdapter.dataBind();
}});
// set the ID of the ledger combo
$newRow.find(".partyS").attr("id",rowCnt +"_partyS");
// set IDS for textboxes for dr and cr amounts
$newRow.find(".dr_amtS").attr("id",rowCnt +"_dr_amtS");
// set the onchange event
$newRow.find(".dr_amtS").jqxNumberInput({width:"50",inputMode: "simple"});
$newRow.find(".dr_amtS").on('change', function (event)
{
calculateTotalDR();
});
$newRow.find(".cr_amtS").attr("id",rowCnt +"_cr_amtS");
$newRow.find(".cr_amtS").jqxNumberInput({width:"50",inputMode: "simple"});
$newRow.find(".cr_amtS").on('change', function (event)
{
calculateTotalCR();
});
// add the narration field
$newRow.find(".narrationS").attr('style','display: block');
$newRow.find(".narrationS").attr("id",rowCnt +"_narrationS");
// add the reference field
$newRow.find(".refS").attr("id",rowCnt +"_refS");
$newRow.find(".btnRef").data("rowid",rowCnt);
$newRow.find(".btnRef").click(function()
{
showRefModal(this);
}
);
// also add a function for removing the row just added.
$newRow.find(".btnTrash").click(function()
{
$(this).parent().parent().remove();
}
);
// id of the row being added
$newRow.attr("id",rowCnt);
$newRow.insertBefore( $lastrow );
rowCnt++;
});
});
The add-row button is the '+' icon on the header next to Reference
When the add-row button is clicked - a row in the table is cloned and the various fields are added to it. The ID of each field is dynamically generated containing the row count.
The following functions iterate through the various fields and collect the data - before it is sent to the Server to be saved by the Web Service.
// take all the voucher entries and loop through to get the records into
// arrays that need to be sent to the server
var jobEntriesList = [];
var ledgerEntriesList = [];
var partyEntriesList = [];
var drEntriesList = [];
var crEntriesList = [];
var narrationEntriesList = [];
var refEntriesList = [];
var refTypeList = [];
function recordEntries()
{
addComboEntry('.jobS',jobEntriesList); // find all elements with the class 'jobS' and add their values into jobEntriesList
addComboEntry('.ledgerS',ledgerEntriesList);
addComboEntry('.partyS',partyEntriesList);
addNormalInput('.dr_amtS',drEntriesList);
addNormalInput('.cr_amtS',crEntriesList);
addNormalInput('.narrationS',narrationEntriesList);
addRefInput();
// the drEntriesList always contains the first entry as the payment entry we will need to remove it from this array
drEntriesList.shift();
// the crEntriesList also contains the frst entry as the payment entry
crEntriesList.shift();
}
// loop through all elements that match the given jquery selector and add it to the passed in array.
function addComboEntry(fieldRef, entryList)
{
$(fieldRef).each(function(i, obj) {
var item = $(this).jqxComboBox('getSelectedItem');
entryList.push(item.value);
});
}
function addNormalInput(fieldRef,entryList)
{
$(fieldRef).each(function(i, obj) {
entryList.push($(this).val());
});
}
// loop through all refS elements and add the fields into the refTypeList and refEntries Array.
function addRefInput()
{
$('.refS').each(function(i, obj) {
refTypeList.push($(this).data('refType'));
refEntriesList.push($(this).data('refID'));
});
}
The next section contains the HTML necessary for displaying the Header and Entries Tables. The class names and styles are standard and should be used in the case of similar interfaces.
<span id="voucher_fields">
<div class="row">
<fieldset id="header">
<legend>Header</legend>
<div class="form-group">
<label class="control-label af-slide required " style="">Voucher Type:<div id='dv_vchrtype'></div></label>
<label class="control-label af-slide required " style="">Currency:<div id='dv_currency'></div></label>
<label class="control-label af-slide required " style="">Financial Period:<div id='dv_finperiod'></div></label>
<label class="control-label af-slide required " style="">Date:<div id='dv_vdate'></div></label>
</div>
<div class="form-group">
<label class="control-label af-slide required " style="">Accounting Center:<div id="dv_accenter"></div></label>
</div>
</fieldset>
</div>
<div class="row" id="payment_entries" style="display:none;">
<fieldset id="entries" >
<legend id="payment_legend">Payments</legend>
<table class="table table-striped">
<thead>
<tr class="active">
<th class="ng-scope required">Job</th>
<th class="ng-scope required" id="payment_header">Ledger</th>
<th class="ng-scope required">Party</th>
<th class="ng-scope">DR</th>
<th class="ng-scope">CR</th>
<th class="ng-scope">Narration</th>
<th class="ng-scope">Reference</th>
<th class="ng-scope"></th>
</tr>
</thead>
<tbody>
<tr class="payment-row" id="payment_0">
<td class="ng-scope"><div id="payment_0_jobS"></div></td>
<td class="ng-scope"><div id="payment_0_ledgerS"></div></td>
<td class="ng-scope"><div id="payment_0_partyS"></div></td>
<td class="ng-scope"><div class="dr_amtS" id="payment_0_dr_amtS" style="width:50px;"></div></td>
<td class="ng-scope"><div class="cr_amtS" id="payment_0_cr_amtS" style="width:50px;"></div></td>
<td class="ng-scope"><textarea id="payment_0_narrationS" cols="20" style="display:none"></textarea>
<div class="payment-instrument-row" id="payment_instrument_0" style="display:none;">
<label id="inst_mode_lbl">Mode:<div id="instrument_mode"></div></label>
<label id="inst_date_lbl">Date:<div id="instrument_date"></div></label>
<input type="text" id="instrument_no"/>
<input type="text" id="instrument_details"/></td>
</div>
</td>
<td class="ng-scope"><div id="payment_0_refS"></div><a class="btn btn-default btn-xs" id="payment_0_ref"><span class="fa fa-search-plus"></span></a></td>
<td class="ng-scope"></td>
</tr>
<tr class="active">
<td class="ng-scope" colspan="3">
</td>
<td class="ng-scope">
<div id="payment_dr_amt" style="padding: 4px 0px; border-width: 0px; text-align: right; width: 48px; height: 17px;"></div>
</td>
<td class="ng-scope">
<div id="payment_cr_amt" style="padding: 4px 0px; border-width: 0px; text-align: right; width: 48px; height: 17px;"></div>
</td>
<td class="ng-scope" colspan="3">
</td>
</tr>
</tbody>
</table>
</fieldset>
</div>
<div class="row">
<fieldset id="entries" >
<legend>Entries</legend>
<table class="table table-striped">
<thead>
<tr class="active">
<th class="ng-scope required">Job</th>
<th class="ng-scope required">Ledger</th>
<th class="ng-scope required">Party</th>
<th class="ng-scope">DR</th>
<th class="ng-scope">CR</th>
<th class="ng-scope">Narration</th>
<th class="ng-scope">Reference</th>
<th class="ng-scope"><a class="btn btn-default btn-xs" id="add-row"><span class="fa fa-plus"></span></a></th>
</tr>
</thead>
<tbody>
<tr class="entry-row" id="0">
<td class="ng-scope"><div class="jobS" id="0_jobS"></div></td>
<td class="ng-scope"><div class="ledgerS" id="0_ledgerS"></div></td>
<td class="ng-scope"><div class="partyS" id="0_partyS"></div></td>
<td class="ng-scope"><div class="dr_amtS" id="0_dr_amtS"></div></td>
<td class="ng-scope"><div class="cr_amtS" id="0_cr_amtS"></div></td>
<td class="ng-scope"><textarea class="narrationS" id="0_narrationS" cols="20" style="display:none"></textarea></td>
<td class="ng-scope"><div class="refS" id="0_refS"></div><a class="btn btn-default btn-xs btnRef"><span class="fa fa-search-plus"></span></a></td>
<td class="ng-scope"><a class="btn btn-default btn-xs btnTrash"><span class="fa fa-trash-o"></span></a></td>
</tr>
<tr id="last-row" class="active">
<td class="ng-scope" colspan="3">
</td>
<td class="ng-scope">
<div id="total_dr_amt" style="padding: 4px 0px; text-align: right; width: 48px; height: 17px;"></div>
</td>
<td class="ng-scope">
<div id="total_cr_amt" style="padding: 4px 0px; text-align: right; width: 48px; height: 17px;"></div>
</td>
<td class="ng-scope" colspan="3">
</td>
</tr>
</tbody>
</table>
</fieldset>
</div>
<div class="form-group">
<div class="field-container af-slide col-sm-9 btn-form btnc">
<button type="button" data-loading-text="Please wait..." class="btn submit form-button loading btn-default btn-normal btn-info " id="createVoucher" style="">Save</button>
</div>
</div>
</span>
<div class="form-group">
<div class="field-container af-slide col-sm-9 btn-form btnc">
<div id="success-panel"></div>
<div id="success-btn-panel"></div>
</div>
</div>
Reference Pop-up dialog
The section that follows this HTML contains the code for displaying the Pop-up dialog that contains the Reference for the Voucher Entry
// set the drop down for the reference type
jQuery(function($) {
// the JSON service that is called when the search is called.
var ref_source =
{
datatype: "json",
datafields: [
{ name: 'referenceid' },
{ name: 'reference' },
{ name: 'referencecode' }
],
url: '/omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCESearchVoucherReference',
type: 'POST',
data: {"searchString":"","refType":"","jobId":""}
};
var refdataAdapter = new $.jqx.dataAdapter(ref_source);
// the drop down with the reference type
var ref_type_source = [
"PO",
"WO",
"INVOICE",
"IDC",
"JOB SCOPE"
];
// Create a jqxDropDownList
$("#reference_type_dd").jqxDropDownList({ source: ref_type_source, selectedIndex: 0, width:120});
// now get a grid and set it in the div
$("#reference_type_searchList").jqxGrid(
{
source: refdataAdapter,
columns: [
{ text: 'Code', datafield: 'referencecode', width: 100 },
{ text: 'Reference', datafield: 'reference', width: 200 },
{ text: 'Reference ID', datafield: 'referenceid', width: 200, hidden: true },
],
width: 300
});
// bind the row select event on the grid
// when a row is selected transfer the ID, type and code of the reference link to the main row of the table from which
// the popup was launched.
$('#reference_type_searchList').bind('rowclick', function (event)
{
console.log('This is a test rowclicks');
// event arguments.
var args = event.args;
// row's bound index.
var rowBoundIndex = args.rowindex;
// row's data. The row's data object or null(when all rows are being selected or unselected with a single action). If you have a datafield called "firstName", to access the row's firstName, use var firstName = rowData.firstName;
var rowData = $("#reference_type_searchList").jqxGrid('getrowdata', rowBoundIndex);
// find the clicked rowid
var mainRowID = $('#refdetail').data("rowid");
// add the data into the clicked text
var refValue = $('#reference_type_dd').jqxDropDownList('getSelectedItem').value +":"+rowData.referencecode;
$("#"+mainRowID+"_refS").html(refValue);
// also set the data into the reference field
$("#"+mainRowID+"_refS").data("refType",$('#reference_type_dd').jqxDropDownList('getSelectedItem').value);
$("#"+mainRowID+"_refS").data("refID",rowData.referenceid);
// hide the dialog
$('#refModal').modal('hide');
});
// code to be excuted when the search button is clicked
$("#searchRef").click(function()
{
var searchString = $("#refSearchString").val();
var rType = $('#reference_type_dd').jqxDropDownList('getSelectedItem');
// disable the form fields
$("#reference_type_dd").jqxDropDownList({ disabled: true });
$("#refSearchString").prop('disabled', true);
// start the search
ref_source.data.searchString = searchString;
ref_source.data.refType = rType.value;
ref_source.data.jobId = $('#refdetail').data("jobid");
refdataAdapter.dataBind();
$("#reference_type_searchList").jqxGrid('updatebounddata');
$("#reference_type_dd").jqxDropDownList({ disabled: false });
$("#refSearchString").prop('disabled', false);
}
);
});
The popup contains a Search Button that when clicked invokes a Web Service /omni/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=FINANCESearchVoucherReference- this web service returns the list of references that match.
These matches are displayed in a Grid - reference_type_searchList - when the user clicks on a row on the grid the popup exits and the reference link is added to the Voucher Entry row.
The HTML for the popup-dialog is:
<div class="modal fade" id="refModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="reftitle">Reference</h4>
</div>
<div class="modal-body" id="refdetail">
<div class="panel-body">
<div class="row">
<fieldset id="ref_modal_fields">
<div class="form-group">
Search:<div id="reference_type_dd"></div>
</div>
<div class="form-group">
<input type="text" id="refSearchString" style="width:90px;"/><a class="btn btn-default btn-xs" id="searchRef" ><span class="fa fa-search"></span></a>
</div>
<div class="form-group">
<div id="reference_type_searchList"></div>
</div>
</fieldset>
</div>
</div>
</div>
<div class="modal-footer">
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
This dialog is based on the Skin Modal Dialog : http://demo.bestdnnskins.com/star2/Shortcodes/Modals/tabid/5104/Default.aspx
The attachment CreateVoucher.html contains the complete source code for this page.