/* Minification failed. Returning unminified contents.
(21539,102-104): run-time error JS1137: 'in' is a new reserved word and should not be used as an identifier: in
(21570,89-91): run-time error JS1137: 'in' is a new reserved word and should not be used as an identifier: in
(21574,56-58): run-time error JS1137: 'in' is a new reserved word and should not be used as an identifier: in
 */
var siteUrl="/WebServicesAjax.asmx";$(document).ready(function(){$.cvAjaxSetup({url:siteUrl})}),function(n){var t=function(n,t){if(n.status==0)throw"You are offline!!\n Please Check Your Network.";else if(n.status==404)throw"Requested URL not found.";else if(n.status==500)throw"Internal Server Error.";else if(n.status===499)setTimeout(function(){throw"Invalid Session for Ajax Request - Ajax request was unable to be executed due to session identifier being different to what was expeected";},0);else if(t=="parsererror")throw"Error.\nParsing JSON Request failed.";else if(t=="timeout")throw"Request Time out.";else throw"AJAX Error: "+n.responseText;};n.cvAjaxSetup=function(i,r){var f=n.extend({type:"POST",contentType:"application/json",dataType:"json",error:t,headers:{"X-Requested-With":"XMLHttpRequest"}},r),u=n.extend({url:"",proxy:null},i);n.cvAjaxSettings=u,n.ajaxSetup(f)},n.cvAjax=function(i,r){var f=n.cvAjaxSettings.url+"/",u,h,o,s,e;return n.cvAjaxSettings.proxy&&(f=n.cvAjaxSettings.proxy+"?rand="+n.now()+"&wsurl="+n.cvAjaxSettings.url+"&method="),f=f+i,n.cvAjaxSettings.proxy||(f+="?rand="+n.now()),u=n.extend(r,{url:f}),u.jsonParse&&(h=u.success,u.success=function(t){var i=n.parseJSON(t.d);i.d=i,h&&h(i)}),n.isArray(u.error)||(o=u.error,u.error=[],o&&u.error.push(o)),u.error.push(function(n){n.status===499&&u.sessionTimeout&&u.sessionTimeout()}),u.error.push(t),u.jsonParse?(s=n.ajax(u),e=n.Deferred(),s.done(function(t){var i=n.parseJSON(t.d);i.d=i,e.resolve(i)}),s.fail(function(n){e.reject(n)}),e.promise()):n.ajax(u)}}(jQuery),function(n){function t(t,i){var u,r,f;i=i||"jsonParse;success",u=i.split(";"),r=n.extend(!0,{},t);for(f in u)delete r[u[f]];return JSON.stringify(r)}n.cssWebServicesAjax={},n.cssWebServicesAjax.settings={pageSize:20},n.cssWebServicesAjax.init=function(t){n.extend(n.cssWebServicesAjax.settings,t)},n.cssWebServicesAjax.login=function(t){var i=n.extend({username:"",password:"",success:function(){}},t);return n.cvAjax("Login",{data:'{ username: "'+i.username+'", password: "'+i.password+'" }',success:i.success,error:i.error})},n.cssWebServicesAjax.logout=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("Logout",{data:"{ }",success:i.success,error:i.error})},n.cssWebServicesAjax.isLoggedIn=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("IsLoggedIn",{data:"{ }",success:i.success,error:i.error})},n.cssWebServicesAjax.registerNewUser=function(t){var i=n.extend({username:"",password:"",doLogin:!1,userRegistrationJsonFieldGroup:"",userGeneralJsonFieldGroupData:null,success:function(){}},t);return n.cvAjax("RegisterNewUser",{data:'{ username: "'+i.username+'", password: "'+i.password+'", doLogin: '+i.doLogin+', userRegistrationJsonFieldGroup: "'+i.userRegistrationJsonFieldGroup+'", userGeneralJsonFieldGroupData: '+JSON.stringify(i.userGeneralJsonFieldGroupData)+" }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.registerNewUserWithAddressDetails=function(t){var i=n.extend({username:"",password:"",doLogin:!1,userRegistrationJsonFieldGroup:"",userGeneralJsonFieldGroupData:null,userAddressJsonFieldGroup:"",userAddressJsonFieldGroupData:null,addressRuleSet:"",success:function(){}},t);return n.cvAjax("RegisterNewUserWithAddressDetails",{data:'{ username: "'+i.username+'", password: "'+i.password+'", doLogin: '+i.doLogin+', userRegistrationJsonFieldGroup: "'+i.userRegistrationJsonFieldGroup+'", userGeneralJsonFieldGroupData: '+JSON.stringify(i.userGeneralJsonFieldGroupData)+', userAddressJsonFieldGroup: "'+i.userAddressJsonFieldGroup+'", userAddressJsonFieldGroupData: '+JSON.stringify(i.userAddressJsonFieldGroupData)+', addressRuleSet: "'+i.addressRuleSet+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changePassword=function(t){var i=n.extend({existingPwd:"",newPwd:"",newPwdCnfm:"",success:function(){}},t);return n.cvAjax("ChangePassword",{data:'{ existingPwd: "'+i.existingPwd+'", newPwd: "'+i.newPwd+'", newPwdCnfm: "'+i.newPwdCnfm+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changePasswordAndNotifyAddress=function(t){var i=n.extend({existingPwd:"",newPwd:"",newPwdCnfm:"",notifyEmailAdress:"",success:function(){}},t);return n.cvAjax("ChangePasswordAndNotifyAddress",{data:'{ existingPwd: "'+i.existingPwd+'", newPwd: "'+i.newPwd+'", newPwdCnfm: "'+i.newPwdCnfm+'", notifyEmailAdress: "'+i.notifyEmailAdress+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.retrievePassword=function(t){var i=n.extend({username:"",success:function(){}},t);return n.cvAjax("RetrievePassword",{data:'{ username: "'+i.username+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addFavourite=function(t){var i=n.extend({productCode:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("AddFavourite",{data:'{ productCode: "'+i.productCode+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.accountSearch=function(t){var i=n.extend({searchString:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,jsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("AccountSearch",{data:'{ searchString: "'+i.searchString+'", skip: '+i.skip+", take: "+i.take+', jsonFieldGroup: "'+i.jsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setCurrentAccount=function(t){var i=n.extend({accountCode:"",success:function(){}},t);return n.cvAjax("SetCurrentAccount",{data:'{ accountCode: "'+i.accountCode+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getCustomerWarningNote=function(t){var i=n.extend({customerCode:"",success:function(){}},t);return n.cvAjax("GetCustomerWarningNote",{data:'{ customerCode: "'+i.customerCode+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getProductAttributeValues=function(t){var i=n.extend({inputIdx:1,productCode:"",selectedTitleValue:"",selectDefaultProduct:!1,jsonParse:!1,success:function(){},error:function(){}},t);return n.cvAjax("GetProductAttributeValues",{data:"{ inputIdx: "+i.inputIdx+', productCode: "'+i.productCode+'", selectedTitleValue: "'+i.selectedTitleValue+'", selectDefaultProduct: '+i.selectDefaultProduct+" }",success:i.success,error:i.error,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getProductAttributePriceGst=function(t){var i=n.extend({productCode:"",attributeSequence:"",priceTaxType:"",priceType:"",jsonParse:!1,success:function(){},error:function(){}},t);return n.cvAjax("GetProductAttributePriceGst",{data:'{ productCode: "'+i.productCode+'", attributeSequence: "'+i.attributeSequence+'", priceTaxType: "'+i.priceTaxType+'", priceType: "'+i.priceType+'" }',success:i.success,error:i.error,jsonParse:i.jsonParse,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getProductAttributeAvailability=function(t){var i=n.extend({productCode:"",attributeSequence:"",jsonParse:!1,success:function(){},error:function(){}},t);return n.cvAjax("GetProductAttributeAvailability",{data:'{ productCode: "'+i.productCode+'", attributeSequence: "'+i.attributeSequence+'" }',success:i.success,error:i.error,jsonParse:i.jsonParse,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.productSearch=function(i){var r=n.extend({searchString:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,orderBy:"",requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("ProductSearch",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.productSearchWithFilter=function(i){var r=n.extend({searchString:"",filter:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,orderBy:"",requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("ProductSearchWithFilter",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.productSearchWithListTemplate=function(i){var r=n.extend({searchString:"",filter:"",skip:0,listTemplateName:"",take:n.cssWebServicesAjax.settings.pageSize,orderBy:"",requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("ProductSearchWithListTemplate",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getProductDetail=function(i){var r=n.extend({productCode:"",requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("GetProductDetail",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getCategoryDetail=function(i){var r=n.extend({categoryCode:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,requestedDeliveryDate:null,jsonFieldGroup:"",jsonFieldGroupCurrentCategoryOverride:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("GetCategoryDetail",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getProductCompanions=function(i){var r=n.extend({productCode:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("GetProductCompanions",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getProductAlternates=function(i){var r=n.extend({productCode:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("GetProductAlternates",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getProductAccessories=function(i){var r=n.extend({productCode:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("GetProductAccessories",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getProductAlternateStatus=function(i){var r=n.extend({productCode:"",checkAlternates:!0,checkAccessories:!0,success:function(){}},i);return n.cvAjax("GetProductAlternatesStatus",{data:t(r),success:r.success,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getCompanionProductOptions=function(i){var r=n.extend({masterProductCode:"",orderLineSeq:0,fieldsUsedForAlternatives:null,selectedCampProdCode:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,productJsonFieldGroup:"",companionProdJsonFieldGroup:"",requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("GetCompanionProductOptions",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.simpleAdminProductSearch=function(t){var i=n.extend({searchTerm:"",take:n.cssWebServicesAjax.settings.pageSize,jsonParse:!0,success:function(){}},t);return n.cvAjax("SimpleAdminProductSearch",{data:'{ searchTerm: "'+i.searchTerm+'", take: '+i.take+"}",success:i.success,jsonParse:!0,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getOrderDetail=function(t){var i=n.extend({orderNumber:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("GetOrderDetail",{data:'{ orderNumber: "'+i.orderNumber+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getCurrentOrder=function(t){var i=n.extend({jsonParse:!0,success:function(){}},t);return n.cvAjax("GetCurrentOrder",{data:"{ }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getCurrentCustomerOrderDefaults=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("GetCurrentCustomerOrderDefaults",{data:"{ }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.updateCurrentOrderDeliveryAddress=function(t){var i=n.extend({deliveryAddressJsonFieldGroup:"",deliveryAddressJsonFieldGroupData:null,delivMethodSeqValueForLookup:-1,setDeliveryOptions:!1,pickListOptionSelected:-1,jsonParse:!0,success:function(){}},t);return n.cvAjax("UpdateCurrentOrderDeliveryAddress",{data:'{ deliveryAddressJsonFieldGroup: "'+i.deliveryAddressJsonFieldGroup+'", deliveryAddressJsonFieldGroupData: '+JSON.stringify(i.deliveryAddressJsonFieldGroupData)+", delivMethodSeqValueForLookup: "+i.delivMethodSeqValueForLookup+", setDeliveryOptions: "+i.setDeliveryOptions+", pickListOptionSelected: "+i.pickListOptionSelected+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addToCurrentOrder=function(i){var r=n.extend({productCode:"",quantity:0,attributes:[],notes:"",noteIsExtendedLineDescription:!1,price:-1,discount:-1,requestedDeliveryDate:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("AddToCurrentOrder",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.addToCurrentOrderWithFieldGroup=function(i){var r=n.extend({productCode:"",quantity:0,attributes:[],notes:"",noteIsExtendedLineDescription:!1,price:-1,discount:-1,requestedDeliveryDate:null,orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("AddToCurrentOrderWithFieldGroup",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.addToCurrentUserStocktake=function(t){var i=n.extend({productCode:"",quantity:0,jsonParse:!0,success:function(){}},t);return n.cvAjax("AddToCurrentUserStocktake",{data:'{ productCode: "'+i.productCode+'", quantity: '+i.quantity+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changeCurrentOrderLine=function(t){var i=n.extend({sequence:0,qty:0,price:0,discount:0,deliveryDate:"",notes:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("ChangeCurrentOrderLine",{data:"{ sequence: "+i.sequence+", qty: "+i.qty+", price: "+i.price+", discount: "+i.discount+', deliveryDate: "'+i.deliveryDate+'", notes: "'+i.notes+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changeCurrentOrderLineWithCustomFields=function(t){var i=n.extend({sequence:0,qty:0,price:0,discount:0,deliveryDate:"",notes:"",customSetJsonFieldGroup:"",orderLineJsonFieldData:null,orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("ChangeCurrentOrderLineWithCustomFields",{data:"{ sequence: "+i.sequence+", qty: "+i.qty+", price: "+i.price+", discount: "+i.discount+', deliveryDate: "'+i.deliveryDate+'", notes: "'+i.notes+'", customSetJsonFieldGroup: "'+i.customSetJsonFieldGroup+'", orderLineJsonFieldData: '+JSON.stringify(i.orderLineJsonFieldData)+', orderJsonFieldGroup: "'+i.orderJsonFieldGroup+'", orderLineJsonFieldGroup: "'+i.orderLineJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changeCurrentOrderLines=function(t){var i=n.extend({data:[],jsonParse:!0,success:function(){}},t);return n.cvAjax("ChangeCurrentOrderLines",{data:" { newLineInfo: "+JSON.stringify(i.data)+" } ",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.changeCurrentOrderLinesWithCustomFields=function(t){var i=n.extend({orderJsonFieldData:[],customSetJsonFieldGroup:"",orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("ChangeCurrentOrderLinesWithCustomFields",{data:" { customSetJsonFieldGroup: '"+i.customSetJsonFieldGroup+"', orderLineJsonFieldData: "+JSON.stringify(i.orderLineJsonFieldData)+", orderJsonFieldGroup: '"+i.orderJsonFieldGroup+"', orderLineJsonFieldGroup: '"+i.orderLineJsonFieldGroup+"' } ",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.deleteCurrentOrderLine=function(t){var i=n.extend({sequence:0,jsonParse:!0,success:function(){}},t);return n.cvAjax("DeleteCurrentOrderLine",{data:"{ sequence: "+i.sequence+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.deleteCurrentOrderLineWithFieldGroup=function(t){var i=n.extend({sequence:0,orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("DeleteCurrentOrderLineWithFieldGroup",{data:"{ sequence: "+i.sequence+', orderJsonFieldGroup: "'+i.orderJsonFieldGroup+'", orderLineJsonFieldGroup: "'+i.orderLineJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addOrderLineNotes=function(t){var i=n.extend({sequence:0,notes:[],jsonParse:!0,success:function(){}},t);return n.cvAjax("AddOrderLineNotes",{data:"{ sequence: "+i.sequence+", notes: "+JSON.stringify(i.notes)+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addOrderLineNotesWithFieldGroup=function(t){var i=n.extend({sequence:0,notes:[],orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("AddOrderLineNotesWithFieldGroup",{data:"{ sequence: "+i.sequence+", notes: "+JSON.stringify(i.notes)+', orderJsonFieldGroup: "'+i.orderJsonFieldGroup+'", orderLineJsonFieldGroup: "'+i.orderLineJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.deleteOrderLineNotes=function(t){var i=n.extend({sequences:[],resequenceLines:!0,jsonParse:!0,success:function(){}},t);return n.cvAjax("DeleteOrderLineNotes",{data:"{ sequences: "+JSON.stringify(i.sequences)+", resequenceLines: "+i.resequenceLines+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.deleteOrderLineNotesWithFieldGroup=function(t){var i=n.extend({sequences:[],orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("DeleteOrderLineNotesWithFieldGroup",{data:"{ sequences: "+JSON.stringify(i.sequences)+', orderJsonFieldGroup: "'+i.orderJsonFieldGroup+'", orderLineJsonFieldGroup: "'+i.orderLineJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.updateOrderLineNote=function(t){var i=n.extend({sequence:0,note:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("UpdateOrderLineNote",{data:"{ sequence: "+i.sequence+', note: "'+i.note+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.updateOrderLineNoteWithFieldGroup=function(t){var i=n.extend({sequence:0,note:"",orderJsonFieldGroup:"",orderLineJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("UpdateOrderLineNoteWithFieldGroup",{data:"{ sequence: "+i.sequence+', note: "'+i.note+'", orderJsonFieldGroup: "'+i.orderJsonFieldGroup+'", orderLineJsonFieldGroup: "'+i.orderLineJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.saveCurrentOrderAsTemplate=function(t){var i=n.extend({templateName:"",allcust:!1,allreps:!1,custtemplate:!1,globalcustomermask:"",roletemplate:!1,jsonParse:!0,success:function(){}},t);return n.cvAjax("SaveCurrentOrderAsTemplate",{data:'{ templateName: "'+i.templateName+'", allcust: '+i.allcust+", allreps: "+i.allreps+", CustTemplate: "+i.custtemplate+', globalCustomerMask: "'+i.globalcustomermask+'", roleTemplate: '+i.roletemplate+" }",jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.putCurrentOrderOnHold=function(t){var i=n.extend({jsonParse:!0,success:function(){}},t);return n.cvAjax("PutCurrentOrderOnHold",{data:"{ }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addCompanionProductToCurrentOrder=function(t){var i=n.extend({parentLineSeq:0,masterProductCode:"",companionProductCode:"",qty:0,jsonParse:!0,success:function(){}},t);return n.cvAjax("AddCompanionProductToCurrentOrder",{data:"{ parentLineSeq: "+i.parentLineSeq+', masterProductCode: "'+i.masterProductCode+'", companionProductCode: "'+i.companionProductCode+'", qty: '+i.qty+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.addCompanionProductsToCurrentOrder=function(t){var i=n.extend({parentLineSeq:0,masterProductCode:"",companProdsAndQty:null,jsonParse:!0,success:function(){}},t);return n.cvAjax("AddCompanionProductsToCurrentOrder",{data:"{ parentLineSeq: "+i.parentLineSeq+', masterProductCode: "'+i.masterProductCode+'", companProdsAndQty: '+JSON.stringify(i.companProdsAndQty)+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setCurrentOrderLineCustomItemOptions=function(t){var i=n.extend({sequence:0,customItemOptions:null,jsonParse:!0,success:function(){}},t);return n.cvAjax("SetCurrentOrderLineCustomItemOptions",{data:"{ sequence: "+i.sequence+", customItemOptions: "+JSON.stringify(i.customItemOptions)+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setCurrentOrderLineUserImage=function(t){var i=n.extend({sequence:0,userImageId:0,success:function(){}},t);return n.cvAjax("SetCurrentOrderLineUserImage",{data:"{ sequence: "+i.sequence+", userImageId: "+i.userImageId+" }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getOrderLinesMissingMandatoryCompanionProducts=function(t){var i=n.extend({success:function(){},jsonParse:!0},t);return n.cvAjax("GetOrderLinesMissingMandatoryCompanionProducts",{data:"{ }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.validateCurrentOrderForCheckout=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("ValidateCurrentOrderForCheckout",{data:"{ }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.applyCurrentOrderPromotionalCode=function(t){var i=n.extend({promoCode:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("ApplyCurrentOrderPromotionalCode",{data:'{ promoCode: "'+i.promoCode+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.removeCurrentOrderPromotionalCode=function(t){var i=n.extend({promoCode:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("RemoveCurrentOrderPromotionalCode",{data:'{ promoCode: "'+i.promoCode+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.applyPreExistingPromotionalCodes=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("ApplyPreExistingPromotionalCodes",{data:"{ }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.freightCalculator=function(t){var i=n.extend({productCode:"",quantity:1,postCode:"",suburb:"",success:function(){}},t);return n.cvAjax("FreightCalculator",{data:"{ productCode: '"+i.productCode+"', quantity: "+i.quantity+" , postCode: '"+i.postCode+"', suburb: '"+i.suburb+"'}",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getFreight=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("GetFreight",{data:"{}",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setFreight=function(t){var r=n.extend({freightOptionIDs:[],success:function(){}},t),u=r.freightOptionIDs,i,f;if(u){i="[";for(f in u)i+=u[f]+",";i=i.replace(new RegExp("[,]+$"),"")+"]"}return n.cvAjax("SetFreight",{data:"{ freightOptionIDs: "+i+" }",success:r.success,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getPaymentOptions=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("getPaymentOptions",{data:"{ }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.applyPaymentSurcharge=function(t){var i=n.extend({orderNo:0,paymentMethod:"CardAny",success:function(){}},t);return n.cvAjax("applyPaymentSurcharge",{data:'{ orderNo: "'+i.orderNo+'", paymentMethod: "'+i.paymentMethod+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getPaymentProviderTitles=function(t){var i=n.extend({success:function(){}},t),r=function(n){i.success&&i.success(n.d)};return n.cvAjax("GetPaymentProviderTitles",{data:"{ }",success:r,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.processPayment=function(t){var i=n.extend({success:function(){},paymentType:"",paymentInfo:{}},t);return n.cvAjax("ProcessPayment",{data:"{ paymentType: '"+i.paymentType+"', paymentInfo: "+JSON.stringify(i.paymentInfo)+" }",success:function(n){var t,r;if(n.d.PaymentSuccessful&&n.d.AdditionalResultData){t=undefined;for(r in n.d.AdditionalResultData)if(n.d.AdditionalResultData[r].Key=="googleAnalyticsEcommerceScript"){t=n.d.AdditionalResultData[r].Value;break}t&&t.length>0&&(eval(t),window.cssGoogleAnalyticsPageTracker&&cssGoogleAnalytics_TrackOrder())}i.success&&i.success(n)},error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.eWayCreateAccessCode=function(i){var r=n.extend({clearRememberedPaymentInfo:!1,success:function(){}},i);return n.cvAjax("EWayCreateAccessCode",{data:t(r),success:r.success,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.addAndTestGiftCardBeforeFundsTransfer=function(t){var i=n.extend({giftCardNumber:"",giftCardPin:0,useTotalAmount:!0,amountToUse:0,customerPaymentReceiptNo:"",soOrderNo:0,success:function(){}},t);return n.cvAjax("AddAndTestGiftCardBeforeFundsTransfer",{data:'{ giftCardNumber: "'+i.giftCardNumber+'", giftCardPin: '+i.giftCardPin+", useTotalAmount: "+i.useTotalAmount+", amountToUse: "+i.amountToUse+', customerPaymentReceiptNo: "'+i.customerPaymentReceiptNo+'", soOrderNo: '+i.soOrderNo+" }",success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.removeGiftCardFromEnteredGiftCards=function(t){var i=n.extend({giftCardNumber:"",success:function(){}},t);return n.cvAjax("RemoveGiftCardFromEnteredGiftCards",{data:'{ giftCardNumber: "'+i.giftCardNumber+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getActiveUserImagesForUserByType=function(t){var i=n.extend({skip:0,take:n.cssWebServicesAjax.settings.pageSize,imageUseType:"",success:function(){}},t);return n.cvAjax("GetActiveUserImagesForUserByType",{data:"{ skip: "+i.skip+", take: "+i.take+', imageUseType: "'+i.imageUseType+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getCategoryHierarchy=function(t){var i=n.extend({catalogueCode:"",success:function(){},jsonParse:!0},t);return n.cvAjax("GetCategoryHierarchy",{data:'{ catalogueCode: "'+i.catalogueCode+'"}',jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getAdvancedCategoryDetails=function(i){var r=n.extend({catalogueCode:"",categoryCode:"",loadAvailableTemplates:!1,success:function(){},jsonParse:!0},i);return n.cvAjax("GetAdvancedCategoryDetails",{data:t(r),jsonParse:r.jsonParse,success:r.success,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getClusterLineTypes=function(t){var i=n.extend({success:function(){}},t);return n.cvAjax("GetClusterLineTypes",{data:"{ }",success:i.success,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.saveAdvancedCatalogueData=function(t){var i=n.extend({success:function(){},catalogueCode:"",data:{},deletedCategories:[],jsonParse:!0},t);return n.cvAjax("SaveAdvancedCatalogueData",{data:JSON.stringify({catalogueCode:i.catalogueCode,data:i.data,deletedCategories:i.deletedCategories}),success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.validateProductCodes=function(t){var i=n.extend({productCodes:[],success:function(){},jsonParse:!0,error:function(){}},t),r="",u,f,e;if(i.productCodes&&i.productCodes.length>0){r="",u=i.productCodes;for(f in u)u[f]&&u[f].length>0&&(r+="'"+u[f]+"',");r=r.substring(0,r.length-1)}return e="{ productCodes : ["+r+"] }",n.cvAjax("ValidateProductCodes",{data:e,success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getParsedTemplate=function(t){var i=n.extend({templateName:"",success:function(){}},t);return n.cvAjax("GetParsedTemplate",{data:'{ templateName: "'+i.templateName+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getTemplateData=function(t){var i=n.extend({name:"",type:"",success:function(){}},t);return n.cvAjax("GetTemplateData",{data:'{ name: "'+i.name+'", type: "'+i.type+'" }',success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.updateLivePromotionInfo=function(t){var i=n.extend({sequence:-1,productCode:"",quantityTaken:0,jsonParse:!0,success:function(){}},t);return n.cvAjax("UpdateLivePromotionInfo",{data:"{ sequence: "+i.sequence+', productCode: "'+i.productCode+'", quantityTaken: '+i.quantityTaken+" }",jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setCurrentOrderPricingAccount=function(t){var i=n.extend({pricingAccount:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("SetCurrentOrderPricingAccount",{data:'{ pricingAccount: "'+i.pricingAccount+'" }',jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getCustomTableData=function(t){var i=n.extend({key:"",jsonFieldGroupName:"",skip:0,take:n.cssWebServicesAjax.settings.pageSize,filter:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("GetCustomTableData",{data:'{ key: "'+i.key+'", jsonFieldGroupName: "'+i.jsonFieldGroupName+'", skip: '+i.skip+", take: "+i.take+', filter: "'+i.filter+'" }',jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setCustomTableData=function(t){var i=n.extend({key:"",jsonFieldGroupName:"",filter:"",data:null,createIfNotFound:!0,jsonParse:!0,success:function(){}},t);return n.cvAjax("SetCustomTableData",{data:'{ key: "'+i.key+'", jsonFieldGroupName: "'+i.jsonFieldGroupName+'", data: '+JSON.stringify(i.data)+', createIfNotFound: "'+i.createIfNotFound+'", filter: "'+i.filter+'" }',jsonParse:i.jsonParse,success:i.success,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getUserDetails=function(t){var i=n.extend({userDetailJsonFieldGroup:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("GetUserDetails",{data:'{ userDetailJsonFieldGroup: "'+i.userDetailJsonFieldGroup+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setUserDetails=function(t){var i=n.extend({userDetailJsonFieldGroup:"",userJsonFieldGroupData:null,jsonParse:!0,success:function(){}},t);return n.cvAjax("SetUserDetails",{data:'{ userDetailJsonFieldGroup: "'+i.userDetailJsonFieldGroup+'", userJsonFieldGroupData: '+JSON.stringify(i.userJsonFieldGroupData)+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getProductsViewed=function(t){var i=n.extend({skip:0,take:10,recordLimit:10,listTemplateName:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("GetProductsViewed",{data:"{ skip: "+i.skip+", take: "+i.take+', recordLimit: "'+i.recordLimit+'", listTemplateName: "'+i.listTemplateName+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.logProductViewed=function(t){var i=n.extend({productCode:"",price:"",jsonParse:!0,success:function(){}},t);return n.cvAjax("LogProductViewed",{data:'{ productCode: "'+i.productCode+'", price: "'+i.price+'" }',success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.userCustomerApproverSearch=function(i){var r=n.extend({emailAddress:"",customerCode:"",searchTerm:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("UserCustomerApproverSearch",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.loadUserCustomerApprovers=function(i){var r=n.extend({emailAddress:"",customerCode:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("LoadUserCustomerApprovers",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.saveUserCustomerApprovers=function(i){var r=n.extend({emailAddress:"",customerCode:"",selectedApprovers:[],jsonParse:!0,success:function(){}},i);return n.cvAjax("SaveUserCustomerApprovers",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getKitConfigurationOptionsForProductOrOrderLine=function(t){var i=n.extend({productCode:"",lineSeq:-1,serverTemplate:"",getMandatoryComponents:!1,jsonParse:!0,success:function(){}},t);return n.cvAjax("GetKitConfigurationOptionsForProductOrOrderLine",{data:'{ productCode: "'+i.productCode+'", lineSeq: '+i.lineSeq+', serverTemplate: "'+i.serverTemplate+'", getMandatoryComponents: '+i.getMandatoryComponents.toString()+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.setProductSelctedKitConfigurationOptions=function(t){var i=n.extend({prodcode:"",configurableBomLineTypesAndCompCodes:null,optionalCompCodesAndInput:null,lineSeq:-1,jsonParse:!0,success:function(){}},t);return n.cvAjax("SetProductSelctedKitConfigurationOptions",{data:'{ prodcode: "'+i.prodcode+'", configurableBomLineTypesAndCompCodes: '+JSON.stringify(i.configurableBomLineTypesAndCompCodes)+", optionalCompCodesAndInput: "+JSON.stringify(i.optionalCompCodesAndInput)+", lineSeq: "+i.lineSeq+" }",success:i.success,jsonParse:i.jsonParse,error:i.error,sessionTimeout:i.sessionTimeout})},n.cssWebServicesAjax.getWarehouseFreightAvailableWarehouses=function(i){var r=n.extend({jsonParse:!0,success:function(){}},i);return n.cvAjax("GetWarehouseFreightAvailableWarehouses",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getWarehouseFreightRegions=function(i){var r=n.extend({jsonParse:!0,success:function(){}},i);return n.cvAjax("GetWarehouseFreightRegions",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getWarehouseFreightRegionCalculations=function(i){var r=n.extend({regionName:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("getWarehouseFreightRegionCalculations",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.deleteWarehouseFreightRegion=function(i){var r=n.extend({regionName:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("DeleteWarehouseFreightRegion",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.addWarehouseFreightRegion=function(i){var r=n.extend({regionName:"",postCodeRanges:[],jsonParse:!0,success:function(){}},i);return n.cvAjax("AddWarehouseFreightRegion",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.saveWarehouseFreightRegion=function(i){var r=n.extend({regionName:"",newRegionName:"",postCodeRanges:[],calculations:[],jsonParse:!0,success:function(){}},i);return n.cvAjax("SaveWarehouseFreightRegion",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getWarehouseFreightMethods=function(i){var r=n.extend({jsonParse:!0,success:function(){}},i);return n.cvAjax("GetWarehouseFreightMethods",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.getWarehouseFreightMethodCalculations=function(i){var r=n.extend({productGroup:"",methodName:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("GetWarehouseFreightMethodCalculations",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.addWarehouseFreightMethod=function(i){var r=n.extend({freightMethodName:"",productGroup:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("AddWarehouseFreightMethod",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.saveWarehouseFreightMethod=function(i){var r=n.extend({methodName:"",newMethodName:"",group:"",newGroup:"",calculations:null,jsonParse:!0,success:function(){}},i);return n.cvAjax("SaveWarehouseFreightMethod",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.deleteWarehouseFreightMethod=function(i){var r=n.extend({productGroup:"",methodName:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("DeleteWarehouseFreightMethod",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cssWebServicesAjax.sendWishlist=function(i){var r=n.extend({recipients:[],message:"",jsonParse:!0,success:function(){}},i);return n.cvAjax("SendWishlist",{data:t(r),success:r.success,jsonParse:r.jsonParse,error:r.error,sessionTimeout:r.sessionTimeout})},n.cv=n.cv||{},n.cv.css=n.cv.css||{},n.cv.css=n.extend({},n.cssWebServicesAjax,n.cv.css)}(jQuery);

;

// Fixes ----------------------------------------------------------------------
//

// Date.now
if (!Date.now) {
    Date.now = function now() {
        return new Date().valueOf();
    };
}

// console.log
if (typeof console === "undefined") {
    var console = {
        log: function () { },
        info: function () { },
        warn: function () { },
        error: function () { }
    };
}


// String Prototype Extension (moved over from CSSGenericInclusions.js)
//

if (!String.prototype.endsWith) {
    String.prototype.endsWith = function (txt, ignoreCase) {
        var rgx;

        if (ignoreCase) {
            rgx = new RegExp(txt + '$', 'i');
        } else {
            rgx = new RegExp(txt + '$');
        }

        return this.match(rgx) != null;
    }
}

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function (prefix) {
        return (this.substr(0, prefix.length) === prefix);
    }
}

if (!String.prototype.format) {
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/\{(\d+)\}/g, function (m, n) { return args[n]; });
    };
}


// cv.util.js -----------------------------------------------------------------
// 

(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.util = $.cv.util || {};

    $.cv.util.toDate = function (value) {
        if ($.type(value) === "date")
            return value;
        var dateRegExp = /^\/Date\((.*?)\)\/$/;
        var date = dateRegExp.exec(value);
        var d = new Date(parseInt(date[1]));

        //return d;
        var localOffset = new Date().getTimezoneOffset();
        var auOffset = -600;//+10 is server time
        // if UTC date just add the local offset (ie when loading json stringified data fron local storage)
        var adjustedTime = new Date(d.getTime() + (d.getTimezoneOffset() == 0 ? (localOffset * 60000) : ((localOffset - auOffset) * 60000)));

        return adjustedTime;
    };

    $.cv.util.dateToServerDate = function (value) {
        var localOffset = new Date().getTimezoneOffset();
        var auOffset = -600;//+10 is server time
        // add extra localoffset value as this is taken off on json/kendo.stringify and server only adjusts by +10
        var adjustedTime = new Date(value.getTime() - ((localOffset - auOffset) * 60000));
        return adjustedTime;
    };

    // some kendo widgets have pop windows and other DOM mess that it leaves lying around
    $.cv.util.destroyKendoWidgets = function (target) {
        target.find("select, input")
            .each(function () {
                var dropdown = $(this).data("kendoDropDownList");
                if (dropdown) {
                    dropdown.element.remove();
                    dropdown.popup.element.unbind().remove();
                }
                var autoc = $(this).data("kendoAutoComplete");
                if (autoc) {
                    autoc.popup.element.unbind().remove();
                }
                var cbox = $(this).data("kendoComboBox");
                if (cbox) {
                    cbox.element.remove();
                    cbox.popup.element.unbind().remove();
                }
            });
    };

    /*  
        Returns value for a Query String Parameter 
        Parsed once on initial call.
    */
    $.cv.util.queryStringValue = function queryStringValue(key, options) {
        var opts = $.extend({
            decode: true
        }, options);

        if (_queryStringParameters == null) {
            _queryStringParameters = {};

            var kvPairs = window.location.search.substring(1).split("&");

            for (var i = 0; i < kvPairs.length; i++) {
                var kv = kvPairs[i].split("=");

                if (kv.length != 2) continue;

                _queryStringParameters[kv[0].toLowerCase()] = kv[1];
            }
        }

        var rawResult = _queryStringParameters[key.toLowerCase()];

        // Return Raw Encoded Value as we've been asked not to decode it
        if (options != null && options.decode != null && options.decode !== true) {
            return rawResult;
        }

        if (rawResult != null) {
            return decodeURIComponent(rawResult.replace(/\+/g, " "));
        }
    };

    /*  
        Required for IE8 as it does not have Object.keys
    */
    if (!Object.keys) {
        Object.keys = function (obj) {
            var keys = [];

            for (var i in obj) {
                if (obj.hasOwnProperty(i)) {
                    keys.push(i);
                }
            }

            return keys;
        };
    }

    var _queryStringParameters = null;


    /*  
        Returns the redirect page including query string parameters
    */
    $.cv.util.queryStringRedirectValue = function queryStringRedirectValue(redirectKey, options) {
        if (_queryStringParameters == null) {
            //populate if it is null
            $.cv.util.queryStringValue("");
        }

        if (typeof _queryStringParameters[redirectKey.toLowerCase()] !== "undefined") {
            var rawResult = _queryStringParameters[redirectKey.toLowerCase()].toString();
            var queryStringParams = [];

            //get the query string parameters
            if (rawResult != null && Object.keys(_queryStringParameters).length > 1) {
                for (var propertyName in _queryStringParameters) {
                    if (propertyName !== "undefined" && propertyName !== redirectKey.toLowerCase()) {
                        queryStringParams.push(propertyName.toString() + "=" + _queryStringParameters[propertyName.toLowerCase()].toString());
                    }
                }
            }

            //append the params to the redirect url
            if (queryStringParams.length > 0) {
                rawResult += "?" + queryStringParams.join("&");
            }

            // Return Raw Encoded Value as we've been asked not to decode it
            if (options != null && options.decode != null && options.decode !== true) {
                return rawResult;
            }

            if (rawResult != null) {
                return decodeURIComponent(rawResult.replace(/\+/g, " "));
            }
        }
        else {
            return "";
        }
    };

    /**
     * WARNING: Array items are Unions. Each item in that
     * array index is then the filter option for that union.
    **/
    $.cv.util.getFilterFeatures = function (options) {
        var opts = $.extend({
            ignoreUnions: false
        }, options);

        var value = $.cv.util.queryStringValue('filterfeature', { decode: false }),
            results = []; // <-- Union Array

        if (value) {
            // Note: 241 is ÃƒÂ± character %C3%B1 is URI encoded value for ÃƒÂ±. 
            // If we have a value with a : character in it it breaks parsing, but this
            // character (if part of a value) must be encoded before putting it into the URI 
            // unless : is actually the filter separator. We need to therefore get the 
            // FilterFeatures in it's Uri encoded form and split that by ÃƒÂ± and then ,.  
            // ÃƒÂ± is returned encoded though so we need to split using the Uri encoding characters
            // or '%C3%B1'

            // If url was encoded first look for encoded version of "ñ" union separator  (i.e. String.fromCharCode(241)
            var unionChar = String.fromCharCode(241),
                encdUnionChar = encodeURIComponent(unionChar),
                encdUnionCharLower = encdUnionChar.toLowerCase(),
                encdUnionCharUpper = encdUnionChar.toUpperCase();

            var unionItems = value.indexOf(encdUnionCharLower) > -1
                                ? value.split(encdUnionCharLower)
                                : value.indexOf(encdUnionCharUpper) > -1
                                    ? value.split(encdUnionCharUpper)
                                    : value.split(unionChar);

            _.each(unionItems, function (section) {
                var filterList = [];
                // If url was encoded first look for encoded version of "|" separator 
                var hasORCombinationFilter = section.indexOf('%7c') > -1 || section.indexOf('%7C') > -1 || section.indexOf('|') > -1;
                if (!hasORCombinationFilter) {
                    var filterList = filterList = [section];
                } else {
                    // If url was encoded first look for encoded version of "|" separator 
                    var filterList = section.indexOf('%7c') > -1
                                        ? section.split("%7c")
                                        : section.indexOf('%7C') > -1
                                            ? section.split("%7C")
                                            : section.split("|");
                }
                _.each(filterList, function (filter) {
                    (function () {
                        var unionFilters = [],
                            // If url was encoded first look for encoded version of "," separator 
                            filters = filter.indexOf('%2c') > -1
                                        ? filter.split("%2c")
                                        : filter.indexOf('%2C') > -1
                                            ? filter.split("%2C")
                                            : filter.split(",");

                        _.each(filters, function (cond) {
                            // Again look for encoded version of this separator ":" first
                            var parts = cond.indexOf('%3a') > -1
                                            ? cond.split("%3a")
                                            : cond.indexOf('%3A') > -1
                                                ? cond.split("%3A")
                                                : cond.split(":"),
                                key = parts[0],
                                condition = parts.length > 1 ? parts[1] : '', 
                                rawValue = parts.length > 2 ? parts[2] : ''; // Set defaults so don't break next bit.

                            var decodedValue =
                                decodeURIComponent(rawValue.replace(/\+/g, " ")
                                                           .replace('_(', '(')
                                                           .replace('_)', ')')
                                                           .replace('__', '_')); // < must be last for obvious reasons!!! filter.split("%2c")

                            unionFilters.push({
                                Key: key,
                                Condition: condition,
                                Value: decodedValue,
                                RawValue: rawValue
                            });
                        });
                        results.push(unionFilters);
                    })();
                });
            });
        }

        if (results.length > 0 && opts.ignoreUnions === true) {
            results = _.flatten(results, true); // <- true means flatten one level only
        }

        return results;
    };

    $.cv.util.encodeFilterFeatureValue = function (value) {
        var result = value.replace('_', '__') // Escape _ characters (2 underscores does this.
                          .replace('(', '_(') // THEN... we escape parenthesis!
                          .replace(')', '_)');

        return encodeURIComponent(result);
    };

    // redirect to same page replacing some querystring parameters
    // set replace=true to exclude new request from browser history

    $.cv.util.redirect = function (url, params, replace) {
        url = url || window.location.href || '';
        url = url.match(/\?/) ? url : url + '?';

        for (var key in params) {
            var re = RegExp(';?' + key + '=?[^&;]*', 'g');
            url = url.replace(re, '');
            if (params[key] != '')
                url += (url.charAt(url.length - 1) == '&' ? '' : '&') + key + '=' + params[key];
        }
        // cleanup url 
        url = url.replace(/[;&]$/, '');
        url = url.replace(/\?[;&]+/, '?');
        url = url.replace(/[&]{2,3}/g, '&');
        url = url.replace(/[;]{2}/g, ';');
        if (url.substr(url.length - 1) == '?')
            url = url.substr(0, url.length - 1);
        if (replace) {
            window.location.replace(url);
        }
        else {
            $(location).attr('href', url);
        }
    };

    // cast dates on ajax msg
    $.cv.util.parseMessageDates = function (data, setUtcForSend) {
        // check for string object that needs casting to javascript object
        data.data = $.cv.util.parseDates(data.data, setUtcForSend);

        return data;
    };

    // OBSOLETE: use parseDates()
    $.cv.util.parseDate = function (index, item, data, setUtcForSend) {
        // Note: old parseDate() would not deal with data that WAS a date or string
        // and though parseDates() does we can't (and don't need to) modify vallers
        // reference. 
        //
        // Short Version: parseDates() covers previous functionality. Therefore use it!
        //

        $.cv.util.parseDates(data, setUtcForSend);
    };

    $.cv.util.parseDates = function (value, setUtcForSend) {
        if (value == null)
            return value;

        // Convert dates to /Date(***)/ format
        if ($.type(value) === 'date' && setUtcForSend === true) {
            return $.cv.util.dateToServerDate(value);
        }

        // Convrt formatted strings from /Date(***)/ format to Date object
        if ($.type(value) === 'string') {
            var plus = value.indexOf('+');
            if (plus == -1) plus = value.indexOf(')');
            if (value.length > 6 && value.substr(0, 6) == '/Date(' && plus != -1) {
                return $.cv.util.toDate(value);
            }
        }

        if ($.type(value) === 'array' || $.type(value) === 'object') {
            $.each(value, function (i, item) {
                value[i] = $.cv.util.parseDates(item, setUtcForSend);
            });
        }

        return value;
    };

    // assign object keys and another data to batch data calls
    $.cv.util.setupBatchData = function (batchData, objectKey) {
        // need to set objectkey on each batch data call
        $.each(batchData, function (index, item) {
            if (!item["_objectKey"]) {
                item._objectKey = objectKey;
            }
        });
        return batchData;
    };

    $.cv.util.getFieldDisplayFromType = function (fieldType, lookup) {
        var Display = "";
        switch (fieldType) {
            case "varchar":
                Display = lookup ? "DropDown" : "TextBox";
                break;
            case "email":
                Display = "Email";
                break;
            case "webaddress":
                Display = "TextBox";
                break;
            case "bool":
                Display = "CheckBox";
                break;
            case "date":
                Display = "DatePicker";
                break;
            case "money":
                Display = "Money";
                break;
            case "double":
                Display = "NumericTextBox";
                break;
            case "list":
                Display = "DropDown";
                break;
            case "password":
                Display = "Password";
                break;
            case "int":
                Display = "NumericIntTextBox";
                break;
            case "datetime":
                Display = "DateTimePicker";
                break;
            case "radio":
                Display = "Radio";
                break;
            case "text":
                Display = "TextArea";
                break;
            case "suburb":
                Display = lookup ? "DropDown" : "TextBox";
                break;
        }
        return Display;
    };

    function _getEditPrompt(field) {
        return $.cv.css.usePlaceHolders && field.prompt ? field.prompt : '';
    };

    $.cv.util.fieldEditTemplates =
    {
        Email: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="email" class="form-email" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-value-update="keyup change" data-bind="paste: true, events: { change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" name="'
                    + field.fieldName
                    + '" '
                    + (field.mandatory ? 'required="required"' : ' ')
                    + (field.readonly ? 'readonly="readonly"' : ' ')
                    + (field.length ? ('maxlength="' + field.length + '"') : '') + ' />';
            else
                return check;
        },
        URL: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="url" class="form-url" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-value-update="keyup change"  data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" name="'
                    + field.fieldName
                    + '" '
                    + (field.mandatory ? 'required="required"' : ' ')
                    + (field.readonly ? 'readonly="readonly"' : ' ')
                    + (field.length ? ('maxlength="' + field.length + '"') : '') + ' />';
            else
                return check;
        },
        TextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="text" class="form-text" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-value-update="keyup change" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" name="'
                    + field.fieldName
                    + '" '
                    + (field.mandatory ? 'required="required"' : ' ')
                    + (field.readonly ? 'readonly="readonly"' : ' ')
                    + (field.length ? ('maxlength="' + field.length + '"') : '') + ' />';
            else
                return check;
        },
        TextArea: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<textarea class="form-textarea" data-value-update="keyup change" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" name="'
                    + field.fieldName + '" '
                    + (field.mandatory ? 'required="required"' : ' ')
                    + (field.readonly ? 'readonly="readonly"' : ' ') + '></textarea>';
            else
                return check;
        },
        CheckBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="checkbox" class="form-checkbox" name="'
                    + field.fieldName
                    + '"  data-value-update="keyup change" data-bind="events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },checked:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" value=""'
                    + (field.readonly ? 'readonly="readonly"' : ' ') + '/>';
            else
                return check;
        },
        DatePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);

            if (check.length == 0) {
                if ($.cv.css.browser().isIE === true && $.cv.css.browser().version < 8) {
                    return '<input            type="text" class="form-datepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                } else {
                    return '<input data-role="datepicker" class="form-datepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                }
            } else {
                return check;
            }
        },
        TimePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);

            if (check.length == 0) {
                if ($.cv.css.browser().isIE === true && $.cv.css.browser().version < 8) {
                    return '<input type="text" class="form-timepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                } else {
                    return '<input data-role="timepicker" class="form-timepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                }
            } else {
                return check;
            }
        },
        DateTimePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);

            if (check.length == 0) {
                if ($.cv.css.browser().isIE === true && $.cv.css.browser().version < 8) {
                    return '<input type="text" class="form-datetimepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                } else {
                    return '<input data-role="datetimepicker" class="form-datetimepicker" name="'
                        + field.fieldName
                        + '" data-value-update="keyup change"  data-bind="events: {change: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' },value: '
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" '
                        + (field.mandatory ? ' required="required" ' : '')
                        + (field.readonly ? ' readonly="readonly" ' : ' ') + ' />';
                }
            } else {
                return check;
            }
        },
        NumericTextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-role="numerictextbox" class="form-numerictextbox" placeholder="'
                    + _getEditPrompt(field)
                    + '" name="'
                    + field.fieldName
                    + '" data-format="n'
                    + (field.length ? field.length : 2)
                    + '" data-value-update="keyup change" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value: '
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" '
                    + (field.mandatory ? 'required="required"' : '')
                    + (field.readonly ? 'readonly="readonly"' : ' ') + ' />';
            else
                return check;
        },
        NumericIntTextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-step="1" class="form-numericinttextbox" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-format="n0" name="'
                    + field.fieldName
                    + '" data-value-update="keyup change"  data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value: '
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" '
                    + (field.mandatory ? 'required="required"' : '')
                    + (field.readonly ? 'readonly="readonly"' : ' ') + ' />';
            else
                return check;
        },
        Html: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<textarea id="editor" class="form-html"  name="'
                    + field.fieldName
                    + '" rows="' + (field.rows ? field.rows : 5)
                    + '" data-value-update="keyup change"  cols="' + (field.cols ? field.cols : 30)
                    + '" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" '
                    + (field.mandatory ? 'required="required"' : '')
                    + (field.readonly ? 'readonly="readonly"' : ' ') + ' />';
            else
                return check;
        },
        Password: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-value-update="keyup change" class="form-password" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value:'
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" name="'
                    + field.fieldName
                    + '" type="password" '
                    + (field.mandatory ? 'required="required"' : '')
                    + (field.readonly ? 'readonly="readonly"' : ' ')
                    + (field.length ? ('maxlength="' + field.length + '"') : '') + ' />';
            else
                return check;
        },
        Money: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-step="0.01" class="form-money" placeholder="'
                    + _getEditPrompt(field)
                    + '" data-decimals="2" data-value-update="keyup change" data-format="n2" name="'
                    + field.fieldName
                    + '" data-bind="paste: true, events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value: '
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled" '
                    + (field.mandatory ? 'required="required"' : '')
                    + (field.readonly ? 'readonly="readonly"' : ' ') + ' />';
            else
                return check;
        },
        DropDown: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0) {
                var input = '<select class="form-select" data-text-field="Text" data-value-field="Value" data-value-update="keyup change" name="'
                    + field.fieldName
                    + '" data-bind="events: {change: dataChanged'
                    + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                    + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                    + ' },value: '
                    + field.fieldName
                    + ', enabled:'
                    + field.fieldName
                    + '_isEnabled, source:'
                    + field.fieldName + '_lookup"'
                    + (field.readonly ? 'readonly="readonly"' : ' ') + ' >';
                input = input + "</select>";
                return input;
            } else
                return check;
        },
        Radio: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0 && field.Lookup.length > 0) {
                var radioList = "";
                $.each(field.Lookup, function (index, item) {
                    radioList += '<label class="radioLabel form-label">'
                        + item.Text
                        + '<input type="radio" class="form-radio" name="'
                        + field.fieldName
                        + '"  data-bind="events: {click: dataChanged, blur: dataChanged'
                        + (field.addKeyupEvent ? ", keyup: dataKeyup" : "")
                        + (field.addKeydownEvent ? ", keydown: dataKeydown" : "")
                        + ' }, checked:'
                        + field.fieldName
                        + ', enabled:'
                        + field.fieldName
                        + '_isEnabled" value="'
                        + item.Value + '"'
                        + (field.readonly ? 'readonly="readonly"' : ' ') + ' /></label>';
                });
                return radioList;
            } else
                return check;
        }
    };

    $.cv.util.string = $.cv.util.string || {};

    $.cv.util.string.endsWith = function (data, txt, ignoreCase) {
        var rgx;
        if (ignoreCase) {
            rgx = new RegExp(txt + '$', 'i');
        } else {
            rgx = new RegExp(txt + '$');
        }
        return data.match(rgx) != null;
    };

    $.cv.util.string.startsWith = function (data, prefix) {
        return (data.substr(0, prefix.length) === prefix);
    };

    $.cv.util.string.format = function (data) {
        var args = Array.prototype.slice.call(arguments, 0);
        args = args.slice(1);
        return data.replace(/\{(\d+)\}/g, function (m, n) { return args[n]; });
    };

    $.cv.util.string.capitaliseFirstLetter = function (str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    };

    $.cv.util.stripHtml = function (html) {
        var tmp = document.createElement("DIV");
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText;
    };

    $.cv.util.buildArrayFromJson = function (options) {
        var opts = $.extend({
            obj: null,
            elementProperty: 'property',
            addKeyAsPrompt: false
        }, options);
        if (opts == null)
            return opts.obj;
        var array = new Array();
        array = [];
        if (typeof opts.obj != 'object')
            return [];
        for (key in opts.obj) {
            if (opts.obj.hasOwnProperty(key)) {
                var el = {};
                el[opts.elementProperty] = opts.obj[key];
                if (opts.addKeyAsPrompt)
                    el["keyPrompt"] = key;
                array.push(el);
            }
        }
        return array;
    };

    /*
        Allows wireup of a variety of elements (inputs) to force clicking
        of a button(target) on pressing enter in the source elements

        selectorsArray : jQuery selectors in an array. All selectors will be 
            queried and keypressed events added to all matching elements
        target : id of the element to click when enter is pressed in any
            of the matching source elemetns
     */
    $.cv.util.clickTargetOnEnter = function (selectorsArray, target) {
        $.each(selectorsArray, function (i, selector) {
            $(selector).keypress(function (event) {
                return _clickTargetOnEnterKeyPressed(event.originalEvent, target);
            });
        });
    };

    // Internal function
    function _clickTargetOnEnterKeyPressed(event, target) {
        if (event.keyCode == 13) {
            var src = event.srcElement || event.target;
            if (src &&
				((src.tagName.toLowerCase() == 'input') &&
					(src.type.toLowerCase() == 'submit' || src.type.toLowerCase() == 'button')) ||
				((src.tagName.toLowerCase() == 'a') &&
					(src.href != null) && (src.href != '')) ||
				(src.tagName.toLowerCase() == 'textarea')) {
                return true;
            }
            var defaultButton;
            if (__nonMSDOMBrowser) {
                defaultButton = document.getElementById(target);
            }
            else {
                defaultButton = document.all[target];
            }
            if (defaultButton && typeof (defaultButton.click) != 'undefined') {
                defaultButton.click();
                event.cancelBubble = true;
                if (event.stopPropagation) event.stopPropagation();
                return false;
            }
        }
        return true;
    };

    $.cv.util.filterEscaper = function (value) {
        var retVal = value.replace(/_/g, "__");
        retVal = retVal.replace(/:/g, "_:");
        retVal = retVal.replace(/\)/g, "_)");
        retVal = retVal.replace(/\(/g, "_(");
        return retVal;
    };

    $.cv.util.getValueFromKeyValueArray = function (variable, objectContainingArray) {
        for (var i = 0; i < objectContainingArray.length; i++) {
            if (variable == objectContainingArray[i].Key) {
                return objectContainingArray[i].Value;
            }
        }
        return null;
    };

    $.cv.util.validateField = function (data, fieldType) {
        // validation regexs
        var date1 = new RegExp(/^\d{1,2}-\d{1,2}-\d{4}$/);
        var date2 = new RegExp(/^\d{1,2}\/\d{1,2}\/\d{4}$/);
        var email = new RegExp(/^((([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
        var url = new RegExp(/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi);
        var valid = true;

        if (fieldType === "date") {
            if (!(data instanceof Date) && !date1.test(data) && !date2.test(data) && data != "") {
                valid = false;
            }
        } else if (fieldType === "email") {
            if (!email.test(data) && data != "") {
                valid = false;
            }
        } else if (fieldType === "money" || fieldType === "double") {
            if (data != null && data.toString().search(/^\$?[\d,]+(\.\d*)?$/) < 0) {
                valid = false;
            }
        } else if (fieldType === "webaddress") {
            if (!url.test(data) && data != "") {
                valid = false;
            }
        }
        return valid;
    };

    $.cv.util.getFieldItemData = function (field) {
        var fieldItem = $.extend({ addKeyupEvent: false, addKeyDownEvent: false }, field, { canEdit: true });
        fieldItem.fieldName = field.FieldName;
        fieldItem.errorMessage = "";
        // to make backwards compatible with delivery and orderCompleteFields widget
        fieldItem.inputErrorClass = "";
        fieldItem.hasNonFieldValidMessage = false;

        // Prompt
        if (field.Prompt != undefined)
            fieldItem.prompt = field.Prompt;
        else
            fieldItem.prompt = "";

        // Mandatory
        if (field.Mandatory != undefined)
            fieldItem.mandatory = field.Mandatory;
        else
            fieldItem.mandatory = false;

        // Mandatory message
        if (field.MandatoryMessage != undefined) {
            fieldItem.mandatoryMessage = $.cv.util.string.format(
                field.MandatoryMessage, fieldItem.prompt);
        } else {
            fieldItem.mandatoryMessage = $.cv.util.string.format(
                $.cv.css.mandatoryFieldIncompleteMessage, fieldItem.prompt);
        }

        // Readonly
        if (field.Readonly != undefined)
            fieldItem.readonly = field.Readonly;
        else
            fieldItem.readonly = false;

        // Help
        if (field.Help != null)
            fieldItem.help = field.Help;
        else
            fieldItem.help = "";

        // Protected
        fieldItem["protected"] = field.Protected;

        // Hidden
        fieldItem.hidden = field.Hidden;

        // Has error
        if (field.HasError != undefined)
            fieldItem.hasError = field.HasError;
        else
            fieldItem.hasError = false;

        // Input error class
        if (field.ClassForErrors != undefined)
            fieldItem.classForErrors = field.ClassForErrors;
        else
            fieldItem.classForErrors = $.cv.css.inputError;

        // Length - INFO: Length for decimals is no. decimal places.
        // 50 is a fallback
        fieldItem.length = field.Length ? field.Length : 50;

        // Rows and Cols - TextArea
        fieldItem.rows = (field.Rows && field.Rows > 0) ? field.Rows : 5;
        fieldItem.cols = (field.Columns && field.Columns > 0) ? field.Columns : 30;

        // determine Type
        var display = $.cv.util.getFieldDisplayFromType(field.FieldType, (_.size(field.Lookup) > 0 && field.Lookup["regex"] == undefined));

        var dataItem = { prompt: field.Prompt, fieldItem: fieldItem };
        if (field.Value != null && (field.FieldType === "date" || field.FieldType === "datetime")) {
            field.Value = kendo.toString($.cv.util.toDate(field.Value), "dd/MM/yyyy");
        }

        dataItem[fieldItem.fieldName] = dataItem["value"] = field.Value;
        dataItem[fieldItem.fieldName + "_isEnabled"] = true;

        // indicate if prompt is empty
        dataItem.emptyPrompt = dataItem.prompt == '';

        // add lookup item to order field
        if (field.Lookup !== null) {

            var lookupToUse = [];
            if (field.Lookup["regex"] != undefined) {
                dataItem["regex"] = field.Lookup["regex"];
            } else {
                dataItem["regex"] = $.cv.util.getDefaultRegexForType(fieldItem);
            }
            if (_.size(field.Lookup) > 0 && field.Lookup["regex"] == undefined) {
                $.each(field.Lookup, function (index, item) {
                    lookupToUse.push({ Value: index, Text: item });
                });
            }

            dataItem[fieldItem.fieldName + "_lookup"] = lookupToUse;
            // set lookup for field data, needed to generate radio buttons in template
            if ((!dataItem["value"] || dataItem["value"] == "") && lookupToUse.length > 0) {
                dataItem["value"] = lookupToUse[0].Value;
                dataItem[fieldItem.fieldName] = lookupToUse[0].Value;
            }
            fieldItem.Lookup = lookupToUse;
        } else {
            dataItem[fieldItem.fieldName + "_lookup"] = [];
        }

        // parse template
        dataItem.fieldTemplate = $.cv.util.fieldEditTemplates[display](fieldItem);

        // field validation function, to be triggered from data change function
        dataItem.fieldValid = function (e, displayMessages) {
            var fieldItem = e.data["fieldItem"],
                value = e.data[fieldItem.fieldName] == null ? "" : (e.data[fieldItem.fieldName] instanceof Date ? kendo.toString(e.data[fieldItem.fieldName], "dd/MM/yyyy") : e.data[fieldItem.fieldName].toString().trim()), // trim off any empty spaces
                valid = $.cv.util.testRegEx(eval(dataItem.regex), value, !fieldItem.mandatory),
                invalidMessage = "",
                inputErrorClass = "",
                displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : (fieldItem.get("hasNonFieldValidMessage") !== undefined ? !fieldItem.get("hasNonFieldValidMessage") : true);
            if (!valid) {
                inputErrorClass = fieldItem.classForErrors;
                if (fieldItem.mandatory && value.length == 0) {
                    if (fieldItem.FieldType != "date" || (fieldItem.FieldType == "date" && e.data[fieldItem.fieldName] != null)) {
                        invalidMessage = fieldItem.mandatoryMessage;
                    } else {
                        invalidMessage = $.cv.util.getInvalidMessageForType(fieldItem, value);
                    }
                } else {
                    invalidMessage = $.cv.util.getInvalidMessageForType(fieldItem, value);
                }
            }
            if (displayMessages) {
                dataItem.setError(fieldItem, invalidMessage, inputErrorClass);
            }
            return valid;
        };

        dataItem.setError = function (fieldItem, message, errorClass) {
            fieldItem.set("errorMessage", message);
            fieldItem.set("hasError", message.length > 0);
            fieldItem.set("inputErrorClass", errorClass);
        };

        // change event for the fields as some use keyup, others use click/blur etc
        // to be overridden / implemented by the widget using these fields
        dataItem.dataChanged = function (e) {
            dataItem.fieldValid(e);
        };

        return dataItem;
    };

    /*
        Useful in templateing engines to encode strings that contain quotes (i.e. ' or ")
        that would break the element attribute thus stuffing up rendering.
    */
    $.cv.util.attrEnc = function (text) {
        if (text === undefined || text === null)
            return '';

        var newText = text.replace(new RegExp("'", 'g'), "&#39;")
                    .replace(new RegExp('"', 'g'), "&#34;");
        return newText;
    };

    // Util parseTemplate registration
    //

    $.cv.util.parseTemplate = function (template, data) {
        return kendo.template(template)(data);
    };

    /*
     * Kendo validator validates hidden fields. To get around this we will
     * wrap each of the default rules functions for the different validation types
     * and only execute the original function ONLY IF the field is actually
     * visible. There seems to be no other way to turn this off at the moment.
    **/
    $.cv.util.preventHiddenFieldValidation = function (kendoValidator) {
        if (kendoValidator && kendoValidator.options && kendoValidator.options.rules) {
            var cache = kendoValidator.options.rules;

            // Wrap current functions and assign back to reference
            if (cache.date)
                cache.date = wrapRule(cache.date);
            if (cache.email)
                cache.email = wrapRule(cache.email);
            if (cache.max)
                cache.max = wrapRule(cache.max);
            if (cache.min)
                cache.min = wrapRule(cache.min);
            if (cache.pattern)
                cache.pattern = wrapRule(cache.pattern);
            if (cache.required)
                cache.required = wrapRule(cache.required);
            if (cache.step)
                cache.step = wrapRule(cache.step);
            if (cache.url)
                cache.url = wrapRule(cache.url);
        }
    };

    $.cv.util.consoleMessage = function (data) {
        var display = $.cookie &&
                      $.cookie("CVBUNDLEDEBUG") != undefined &&
                      $.cookie("CVBUNDLEDEBUG").toLowerCase() === 'true';
        if (display) {
            switch (data.options.type) {
                case $.cv.css.messageTypes.error:
                    console.error(data.options);
                    break;
                case $.cv.css.messageTypes.warning:
                    console.warn(data.options);
                    break;
                case $.cv.css.messageTypes.info:
                    console.info(data.options);
                    break;
                default:
                    console.log(data.options);
                    break;
            }
        }
    };

    $.cv.util.notify = function (vmOrAllOptions, message, type, options) {
        var result = $.Deferred(),
            resultData = {
                viewModelMessageSet: false,
                publishResult: null
            },
            opts;

        // ************************
        // *** Method Overloads ***
        // ************************
        if (kendo && (vmOrAllOptions instanceof kendo.Observable)) {
            // Example:
            // $.cv.util.notify(viewModel, message, type);
            opts = $.extend({}, $.cv.util.notify._defaults, options);
            opts.viewModel = vmOrAllOptions;
            opts.message = message;
            opts.type = type;
        } else {
            // Example:
            // $.cv.util.notify({
            //   viewModel: vm,
            //   message: 'sdfskf'.
            //   type: 'error'
            // });
            opts = $.extend({}, $.cv.util.notify._defaults, vmOrAllOptions);
        }

        // Does View Model require us to trigger messages or not?
        if (opts.viewModel != null && opts.triggerMessages == null) {
            opts.triggerMessages = opts.viewModel.get("triggerMessages");
        }

        // Get values from VM if needed
        if (opts.viewModel != null && opts.clearExisting == null) {
            opts.clearExisting = opts.viewModel.get("clearWidgetMessages");
        }

        // Set Message on ViewModel if Present
        if (opts.viewModel != null) {
            if (opts.viewModel.name != null) {
                opts.source = opts.viewModel.name;
            }

            opts.viewModel.set("message", opts.message);

            resultData.viewModelMessageSet = true;
        }

        resultData.options = opts;

        // Trigger it!
        if (opts.triggerMessages === true) {
            $.cv.css.trigger($.cv.css.eventnames.message, {
                message: opts.message,
                type: opts.type,
                source: opts.source,
                clearExisting: opts.clearExisting
            }).done(function (triggerResult) {
                resultData.publishResult = triggerResult;

                result.resolve(resultData);
            });
        } else {
            result.resolve(resultData);
        }

        $.cv.util.consoleMessage(resultData);

        return result.promise();
    };

    $.cv.util.notify._defaults = {
        viewModel: null,

        message: '',
        type: '',
        source: '',

        triggerMessages: null,  // true/false, we will set from VM if not passed in
        clearExisting: null     // true/false, we will set from VM if not passed in
    }

    /* @viewModel could be extended to potentially be options later as needed */
    $.cv.util.clearNotifications = function (vmOrAllOptions) {
        var opts;

        // ************************
        // *** Method Overloads ***
        // ************************
        if (kendo && (vmOrAllOptions instanceof kendo.Observable)) {
            // Example:
            // $.cv.util.clearNotifications(viewModel);
            opts = $.extend({}, $.cv.util.notify._defaults);
            opts.viewModel = vmOrAllOptions; /* < View Model here */
        } else {
            // Example:
            // $.cv.util.notify({
            //   viewModel: vm,
            //   triggerMessages: true // we might not ever or very rarely actually manually set this! Just an example!
            // });
            opts = $.extend({}, $.cv.util.notify._defaults, vmOrAllOptions /* < options here */);
        }

        if (opts.viewModel == null)
            return;

        opts.viewModel.set("message", "");

        if (opts.viewModel.get("triggerMessages"))
            $.cv.css.trigger($.cv.css.eventnames.message, {
                message: "",
                type: '',
                source: opts.viewModel.name,
                clearExisting: true
            });
    };

    // DEPRICATED: used $.cv.util.notify
    $.cv.util.setMessage = function (message, type) {
        if (message == null || type == undefined)
            return;

        $.cv.util.notify(this, message, type);
    };

    // DEPRICATED: used $.cv.util.clearNotifications;
    $.cv.util.clearMessage = function () {
        this.set("message", "");
        if (this.get("triggerMessages"))
            $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: this.name, clearExisting: true });
    };

    $.cv.util.testRegEx = function (regExString, testString, allowEmpty) {
        var regEx = new RegExp(regExString);
        if (testString == "" && allowEmpty) {
            return true;
        } else if (!regEx.test(testString) || testString == "") {
            return false;
        } else {
            return true;
        }
    };

    function wrapRule(currentValidateFunction) {
        // a is jquery object that contains the element that needs validation by kendo validator.
        return function (a) {
            if (a.is(":visible"))
                return currentValidateFunction(a);

            return true; // Field is valid if hidden
        };
    };

    $.cv.util.getInvalidMessageForType = function (fieldItem, value) {
        var invalidMessage = "", fieldName = (fieldItem.prompt && fieldItem.prompt.trim().length > 0) ? fieldItem.prompt : "Field";
        invalidMessage = $.cv.util.string.format($.cv.css.invalidFieldMessage, fieldName);
        return invalidMessage;
    };

    $.cv.util.getDefaultRegexForType = function (fieldItem) {
        var regex = "";
        switch (fieldItem.FieldType) {
            case "date":
                regex = /^([1-9]|0[1-9]|[12][0-9]|3[01])[- /.]([1-9]|0[1-9]|1[012])[- /.](19|20|30)\d\d$/; // allows formats dd/mm/yyyy or d/m/yyyy with separators -/. or space
                break;
            case "email":
                regex = /^((([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i;
                break;
            case "webaddress":
                regex = /^[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?$/gi;
                break;
            case "money":
            case "double":
                regex = /^\$?[\d,]+(\.\d*)?$/;
                break;
            case "int":
                regex = /^-?\d*$/;
                break;
            default:
                regex = "";
        }
        return regex;
    };

    // jQuery Ajax Converter: converts the result of a service call
    // where the underlying method returns a simple boolean value... this
    // converts it to an actual boolean instead of keeping the serialized string.
    $.cv.util._booleanResponseConverter = function (response) {
        var result = jQuery.parseJSON(response);
        var data = result.data;

        if (data && $.type(data) === 'string') {
            result.data = data === "True";
        }

        return result;
    };

    // Gets the function from it's string path or if it is a function just returns it.
    $.cv.util.getFunctionFromString = function (value) {
        if ($.type(value) === 'string') {
            return (new Function("return " + value))();
        }

        if ($.type(value) === 'function') {
            return value;
        }
    };

    $.cv.util.whiteSpaceAndNullReplacer = function (invar) {
        if (invar == "null" || invar == "" || invar == null) {
            return "&#8203;"; // like &nbsp; doesn't collapse the container, but also doesn't put a space in the container
        }

        return invar;
    };

    // Returns true if passed in 'thing' is not null and not undefined!
    $.cv.util.hasValue = function (ref) {
        return ref !== null && ref !== undefined;
    };

    $.cv.util.isNullOrWhitespace = function (str) {
        if ($.cv.util.hasValue(str) === false) {
            return true;
        }

        if ($.type(str) === 'string') {
            return $.trim(str).length === 0;
        } else {
            // Anything that is not a string: ex: 15 (number) {} an object, [] and empty array
            // should not be considered to be null or whitespace
            return false;
        }
    };

    $.cv.util.htmlEncode = function (value) {
        // Bail Conditions
        if ($.cv.util.hasValue(value) === false || $.type(value) !== 'string') {
            return '';
        }

        // Replace 
        return value.toString()
                    .replace(/</g, "&lt;")
                    .replace(/>/g, "&gt;");
    };

    // html encodes a string then replaces newlines with br tags
    // the result should be rendered as html not as text!!!
    $.cv.util.stringToFormattedHtml = function (str) {
        // Encode first because we will be replacing things with html tags
        // and this text would just be rendered unencoded
        var result = $.cv.util.htmlEncode(str);
        return result.replace(/\n/g, function (str, match) { return '<br>'; });

        // Note:
        // We don't do tabs because at least in the current situationj
        // we don't allow tabs to be entered (it will just move to the next field
        //.replace(/\t/g, function(str, match) { return '&nbsp;&nbsp;&nbsp;&nbsp;'; })
        //
        // We don't replace spaces as this prevents the text from wrapping... we don't 
        // want it to go off the screen
        //.replace(/\s/g, function(str, match) { return '&nbsp;'; });
    };

    $.cv.util.kendoNumericTextBoxChange = function (el) {
        if ($(el.element[0]).attr("force-step-change")) {
            var value = el.value(), step = el.options.step, remainder = value % step;
            if (remainder != 0) {
                value = value - remainder;
                $(el.element[0]).val(value).blur();
            }
        }
    };
    $.cv.util.kendoNumericTextBoxSpin = function (el) {
        if ($(el.element[0]).attr("force-step") || $(el.element[0]).attr("force-step-change")) {
            var value = el.value(), step = el.options.step, remainder = value % step;
            if (remainder != 0) {
                value = value - remainder;
                $(el.element[0]).val(value);
            }
        }
        $(el.element[0]).blur();
    };
    $.cv.util.kendoNumericTextBox = function (el) {
        var k = $(el).data("kendoNumericTextBox");
        if (k == undefined) {
            $(el).kendoNumericTextBox({
                change: function () {
                    $.cv.util.kendoNumericTextBoxChange(this);
                },
                spin: function () {
                    $.cv.util.kendoNumericTextBoxSpin(this);
                }
            });
        } else {
            if (k.options.spin == undefined) {
                k.bind("spin", function () {
                    $.cv.util.kendoNumericTextBoxSpin(this);
                });
            }
            if (k.options.change == undefined) {
                k.bind("change", function () {
                    $.cv.util.kendoNumericTextBoxChange(this);
                });
            }
        }
    };

})(jQuery);

;
//     Underscore.js 1.4.2
//     http://underscorejs.org
//     (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
//     Underscore may be freely distributed under the MIT license. 
(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=r.unshift,l=i.toString,c=i.hasOwnProperty,h=r.forEach,p=r.map,d=r.reduce,v=r.reduceRight,m=r.filter,g=r.every,y=r.some,b=r.indexOf,w=r.lastIndexOf,E=Array.isArray,S=Object.keys,x=s.bind,T=function(e){if(e instanceof T)return e;if(!(this instanceof T))return new T(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=T),exports._=T):e._=T,T.VERSION="1.4.2";var N=T.each=T.forEach=function(e,t,r){if(e==null)return;if(h&&e.forEach===h)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(T.has(e,o)&&t.call(r,e[o],o,e)===n)return};T.map=T.collect=function(e,t,n){var r=[];return e==null?r:p&&e.map===p?e.map(t,n):(N(e,function(e,i,s){r[r.length]=t.call(n,e,i,s)}),r)},T.reduce=T.foldl=T.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return e==null?r:m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return e==null?r:(N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r)},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return e==null?i:g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return e==null?i:y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return e==null?n:b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},T.shuffle=function(e){var t,n=0,r=[];return N(e,function(e){t=T.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return T.isFunction(e)?e:function(t){return t[e]}};T.sortBy=function(e,t,n){var r=k(t);return T.pluck(T.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t);return N(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};T.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(T.has(e,t)?e[t]:e[t]=[]).push(n)})},T.countBy=function(e,t,n){return L(e,t,n,function(e,t,n){T.has(e,t)||(e[t]=0),e[t]++})},T.sortedIndex=function(e,t,n,r){n=n==null?T.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},T.toArray=function(e){return e?e.length===+e.length?u.call(e):T.values(e):[]},T.size=function(e){return e.length===+e.length?e.length:T.keys(e).length},T.first=T.head=T.take=function(e,t,n){return t!=null&&!n?u.call(e,0,t):e[0]},T.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},T.last=function(e,t,n){return t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},T.rest=T.tail=T.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},T.compact=function(e){return T.filter(e,function(e){return!!e})};var A=function(e,t,n){return N(e,function(e){T.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};T.flatten=function(e,t){return A(e,t,[])},T.without=function(e){return T.difference(e,u.call(arguments,1))},T.uniq=T.unique=function(e,t,n,r){var i=n?T.map(e,n,r):e,s=[],o=[];return N(i,function(n,r){if(t?!r||o[o.length-1]!==n:!T.contains(o,n))o.push(n),s.push(e[r])}),s},T.union=function(){return T.uniq(a.apply(r,arguments))},T.intersection=function(e){var t=u.call(arguments,1);return T.filter(T.uniq(e),function(e){return T.every(t,function(t){return T.indexOf(t,e)>=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=T.pluck(e,""+r);return n},T.object=function(e,t){var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},T.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=T.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(b&&e.indexOf===b)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},T.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(w&&e.lastIndexOf===w)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},T.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};T.bind=function(t,n){var r,i;if(t.bind===x&&x)return x.apply(t,u.call(arguments,1));if(!T.isFunction(t))throw new TypeError;return i=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=t.prototype;var e=new O,s=t.apply(e,i.concat(u.call(arguments)));return Object(s)===s?s:e}return t.apply(n,i.concat(u.call(arguments)))}},T.bindAll=function(e){var t=u.call(arguments,1);return t.length==0&&(t=T.functions(e)),N(t,function(t){e[t]=T.bind(e[t],e)}),e},T.memoize=function(e,t){var n={};return t||(t=T.identity),function(){var r=t.apply(this,arguments);return T.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},T.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},T.defer=function(e){return T.delay.apply(T,[e,1].concat(u.call(arguments,1)))},T.throttle=function(e,t){var n,r,i,s,o,u,a=T.debounce(function(){o=s=!1},t);return function(){n=this,r=arguments;var f=function(){i=null,o&&(u=e.apply(n,r)),a()};return i||(i=setTimeout(f,t)),s?o=!0:(s=!0,u=e.apply(n,r)),a(),u}},T.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},T.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},T.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},T.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r<e;r++)t.call(n,r)},T.random=function(e,t){return t==null&&(t=e,e=0),e+(0|Math.random()*(t-e+1))};var _={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);;
/*
    cv.ajax.js
    
    Description:
        Core library for setup and execution of dynamic services web methods.

    Usage:
        Just include the file. Setup will be done automatically. 

        If you need to pass in a proxy or timeoutRedirectUrl or change the 
        url then you will need to call $.cv.ajax.setup() with the required
        options IMMEDIATELY after this file is included.

    Dependencies:
        cv.util.js
        jquery
            In Script folder or http://jquery.com/

        jquery.cookie
            In Script folder or https://github.com/carhartl/jquery-cookie

*/

;

(function ($, undefined) {

    $.cv = $.cv || {};

    // $.cv.ajax object definition
    //
    $.cv.ajax = $.cv.ajax || {};

    $.cv.ajax.settings = {};

    $.cv.ajax.setup = function (wssettings) {
        var wsopts = $.extend({
            url: '/service',
            ajaxSettings: null,
            proxy: null,
            timeoutPreRedirectFunction: $.cv.ajax.timeoutPreRedirectFunction,
            timeoutRedirectUrl: '',
            timeoutRedirectMessage: '',
            cvApplicationType: 'cssnet' // cssnet | maf | unknown
        }, wssettings);

        // Store settings
        this.settings = wsopts;

        var ajaxopts = $.extend({
            type: 'POST',
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            error: function (xhr, status, errorthrown) {
                window.alert('AJAX Error: ' + status + ' ' + errorthrown);
            }
        }, wsopts.ajaxSettings);

        // Setup Ajax
        $.ajaxSetup(ajaxopts);
    };

    $.cv.ajax.call = function (method, ajaxsettings) {
        var wsurl = this.settings.url + "/",
            params = {},
            paramobj;

        if (this.settings.proxy) {  // add random fix for ios6 safari caching ajax posts
            wsurl = this.settings.proxy + "?rand=" + ($.now()) + "&wsurl=" + this.settings.url + "&method=";
        } 
               
        if (ajaxsettings.parameters) {
            // turn the parameters into a data string
            params = (typeof (ajaxsettings.parameters) == "function") ? ajaxsettings.parameters() : ajaxsettings.parameters;

            if ($.cookie) {
                // current session id to request
                var valFromCookie = $.cookie("dynamicServiceSessionId");
                if (valFromCookie) {
                    params._sessionId = valFromCookie;
                }
            }

            // before stringify adjust utc time
            if ($.cv.util) {
                params = $.cv.util.parseDates(params, true);
            }
        } else if (!ajaxsettings.data) {
            if ($.cookie) {
                // current session id to request
                var valFromCookie = $.cookie("dynamicServiceSessionId");
                if (valFromCookie) {
                    params._sessionId = valFromCookie;
                }
            }
        }

        // Specify the application type so that server-side code
        // can destinguish and act accordingly.
        if (this.settings.cvApplicationType) {
            params._applicationType = this.settings.cvApplicationType;
        }

        paramobj = {
            data: JSON.stringify(params)
        };

        $.extend(ajaxsettings, paramobj);
        
        wsurl = wsurl + method;

        // Add random fix for ios6 safari caching ajax posts
        if (!this.settings.proxy) {
            wsurl += "?rand=" + ($.now());
        }

        var opts = $.extend(ajaxsettings, { url: wsurl });

        // handle session timeout messages
        var error = opts.error;
        opts.error = function (data) {
            var response = null;

            try {
                // If server error this will fail!
                response = data.xhr ? $.parseJSON(data.xhr.responseText) : $.parseJSON(data.responseText);
            } catch (e) {}

            if (response && response.sessionHasTimedOut) {
                $.cv.ajax.redirectFromTimeout();
            } else {
                if (error) {
                    error(data);
                }
            }

            if (data.status && data.status == 400) {
                $.cv.css.received400 = true;
            }
        };

        var success = opts.success;
        opts.success = function(data) {
            if (data.sessionHasTimedOut) {
                 $.cv.ajax.redirectFromTimeout();
            }
            else {
                // set any dates correctly if needed
                if ($.cv.util) {
                    data = $.cv.util.parseMessageDates(data);
                }
                // Do whatever needs to be done here.
                if(success)
                    success(data);
            }
        };

        return $.ajax(opts);
    };

    $.cv.ajax.redirectFromTimeout = function () {
        var _this = this, redirectUrl = _this.settings.timeoutRedirectUrl;
        if (_this.settings.timeoutRedirectUrl) {
            var def1 = $.Deferred(), p1 = def1.promise();
            if ($.isFunction(_this.settings.timeoutPreRedirectFunction)) {
                $.cv.css.bind($.cv.css.eventnames.localOrderChanged, function () {
                    def1.resolve();
                });
                _this.settings.timeoutPreRedirectFunction.apply(this, arguments);
                $.cv.css.trigger($.cv.css.eventnames.refreshOrderData);
            } else {
                def1.resolve();
            }
            $.when(p1).done(function () {
                redirectUrl = _this.settings.timeoutRedirectMessage.length > 0 ? redirectUrl + "?message=" + _this.settings.timeoutRedirectMessage : redirectUrl;
                window.location.href = redirectUrl;
            });
        }
    };

    $.cv.ajax.timeoutPreRedirectFunction = function () {
        $.cv.css.clearLocalStorage();
    };

    // For parsing Deferred.fail() ajax request response data to parse the error message
    $.cv.ajax.getFailResponseText = function (response) {
        // :op - have to parse the responseText in fail scenario???
        try {
            response = JSON.parse(response.responseText);
        } catch (d) { };

        return response;
    };

    // Setup
    //

    /**
     * NOTE: If the defaults are not appropriate just call $.cv.ajax.setup() immediately
     * after this file is included in the page. Otherwise you never need to worry about calling it.
    **/
    $.cv.ajax.setup();

})(jQuery);

;

;

// cv widget factory for Kendo UI
(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.ui = $.cv.ui || {};

    // Widget Registration
    //

    $.cv.ui.widget = function (w) {

        // Cater for no options object - can be if extending
        w.options = w.options || {};
        // Add widget name to options
        w.options.name = w.name;

        // object to extend
        w.extend = w.extend || "Widget";

        // used for calling base widget functions
        w.base = kendo.ui[w.extend].fn;

        // define kendo init function
        w.init = function (el, o) {
            var instance = this,
                $el = $(el),
                fixedOptions;

            if (window.cvcsswidgetsettings && window.cvcsswidgetsettings[this.options.name]) {
                // we have a setting object
                var settings = window.cvcsswidgetsettings[this.options.name];
                $.each(settings, function (key, item) {
                    if (o != undefined && o != null && o.hasOwnProperty(key)) {
                        // now check for setting      
                        if (o[key] === w.options[key])
                            instance.options[key] = settings[key];
                    } else
                        instance.options[key] = settings[key];
                });
            }

            w.base.init.call(this, el, o);

            if (w.initialise) {
                fixedOptions = $.cv.ui.widget._optionFixer($el, this.options);
                w.initialise.call(this, $el, fixedOptions);
            }

            // Remove the "kendo" data element prefix
            $el.data(w.name, $el.data("kendo" + w.name));
            $el.data("kendo" + w.name, undefined);
            $el.removeData("kendo" + w.name);
        };

        // check for extended events
        if (w.extendEvents) {
            w.events = kendo.ui[w.extend].prototype.events.concat(w.extendEvents);
        }
        // Register the widget
        var widgetfn = kendo.ui[w.extend].extend(w);
        kendo.ui.plugin(widgetfn);

        // Cleanup - Remove the "kendo" default prefix
        $.fn[w.name] = $.fn["kendo" + w.name];
        $.fn["kendo" + w.name] = undefined;
    };

    // looks for numeric option values and checks 
    // whether there is a data- attribute, and will replace
    // with the actual data- attribute if the value looks
    // 
    function optionFixer($el, options) {
        if ($.cv.ui.widget._optionFixer.enabled === true && $el && options) {
            var key,
                newKey,
                value,
                i,
                currentChar,
                attrValue;

            for (key in options) {
                // Skip indexes, ensure not inherited item...
                if (typeof (key) === "number" || !options.hasOwnProperty(key)) {
                    continue;
                }

                value = options[key];

                // Do this stuff if value is actually a number
                if (value != null && typeof (value) === 'number') {
                    // Transform key
                    newKey = "data-";

                    for (i = 0; i < key.length; i += 1) {
                        currentChar = key.charAt(i);

                        if (currentChar === currentChar.toUpperCase()) {
                            newKey += "-" + currentChar.toLowerCase();
                            continue;
                        }

                        newKey += currentChar;
                    }

                    // We have new key data-product-code for example
                    // check attribute.
                    attrValue = $el.attr(newKey);

                    if (attrValue != null && attrValue !== value.toString()) {
                        options[key] = $el.attr(newKey);
                    }
                }
            }
        }

        return options;
    };

    $.cv.ui.widget._optionFixer = optionFixer;
    $.cv.ui.widget._optionFixer.enabled = true;

    (function (k) {
        /**
         * Customer Binding for adding a class to an element in the view based 
         * on object in the View Model
         *
         * Example: (template)
         *   <div data-bind="addClass: HighlightInfo"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          HighlighInfo: {
         *              add: true, // Designates whether the class should be added
         *              cssClass: 'highligh-feature' // Designates the css class to ensure is on the element.
         *          }
         *      }
        **/
        k.data.binders.addClass = k.data.Binder.extend({
            refresh: function () {
                var value = this.bindings["addClass"].get();

                if (value && value.add && value.cssClass) {
                    var $el = $(this.element);

                    if (!$el.hasClass()) {
                        $el.addClass(value.cssClass);
                    }
                }
                if (value && !value.add && value.cssClass) {
                    var $el = $(this.element);

                    if ($el.hasClass(value.cssClass)) {
                        $el.removeClass(value.cssClass);
                    }
                }
            }
        });

        /**
         * Custom Binding for adding a is processing class to an element in the view based 
         * on object in the View Model
         *
         * Example: (template)
         *   <div data-bind="addIsProcessingClass: isProcessing"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          isProcessing: true
         *      }
        **/
        k.data.binders.addIsProcessingClass = k.data.Binder.extend({
            refresh: function () {
                var value = this.bindings["addIsProcessingClass"].get();
                var $el = $(this.element);
                if (value) {
                    if (!$el.hasClass()) {
                        $el.addClass($.cv.css.isProcessingClass);
                    }
                } else {
                    if ($el.hasClass($.cv.css.isProcessingClass)) {
                        $el.removeClass($.cv.css.isProcessingClass);
                    }
                }
            }
        });

        /**
         * Custom Binding for adding a class to an element in the view based 
         * on object in the View Model.
         * This will append or remove the class not clobber the class names already on the element
         *
         * Example: (template)
         *   <div data-bind="toggleOnClass: { error: titleIsEmpty }"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          titleIsEmpty: true
         *      }
        **/
        k.data.binders.toggleOnClass = k.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                // get list of class names from our complex binding path object
                this._lookups = [];
                for (var key in this.bindings.toggleOnClass.path) {
                    this._lookups.push({
                        key: key,
                        path: this.bindings.toggleOnClass.path[key]
                    });
                }
            },
            refresh: function () {
                var lookup, value;

                for (var i = 0; i < this._lookups.length; i++) {
                    lookup = this._lookups[i];

                    // set the binder's path to the one for this lookup,
                    // because this is what .get() acts on.
                    this.bindings.toggleOnClass.path = lookup.path;
                    value = this.bindings.toggleOnClass.get();

                    // add or remove CSS class based on if value is truthy
                    if (value) {
                        $(this.element).addClass(lookup.key);
                    } else {
                        $(this.element).removeClass(lookup.key);
                    }
                }
            }
        });

        /**
         * Custom Binding for adding a class to an element in the view based 
         * on object in the View Model.
         * This will append or remove the class not clobber the class names already on the element
         *
         * Example: (template)
         *   <div data-bind="toggleOffClass: { error: titleIsEmpty }"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          titleIsEmpty: true
         *      }
        **/
        k.data.binders.toggleOffClass = k.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                // get list of class names from our complex binding path object
                this._lookups = [];
                for (var key in this.bindings.toggleOffClass.path) {
                    this._lookups.push({
                        key: key,
                        path: this.bindings.toggleOffClass.path[key]
                    });
                }
            },
            refresh: function () {
                var lookup, value;

                for (var i = 0; i < this._lookups.length; i++) {
                    lookup = this._lookups[i];

                    // set the binder's path to the one for this lookup,
                    // because this is what .get() acts on.
                    this.bindings.toggleOffClass.path = lookup.path;
                    value = this.bindings.toggleOffClass.get();

                    // add or remove CSS class based on if value is truthy
                    if (!value) {
                        $(this.element).addClass(lookup.key);
                    } else {
                        $(this.element).removeClass(lookup.key);
                    }
                }
            }
        });

        k.data.binders.appendClass = kendo.data.Binder.extend({
            refresh: function () {
                var value = this.bindings["appendClass"].get();
                var $el = $(this.element);
                if (value) {
                    if (!$el.hasClass()) {
                        $el.addClass(value);
                    }
                }
            }
        });

        /**
         * Example: (template)
         *   <span data-bind="date: viewModelValue" data-dateformat="yyyy-MM-dd"></span>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          myDate : new Date(), // i.e. assign the date here....
         *      }
        **/

        // Source: http://jsfiddle.net/korchev/urAPV/1/
        k.data.binders.date = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                this.dateformat = $(element).data("dateformat");
            },
            refresh: function () {
                var data = this.bindings["date"].get();
                if (data) {
                    var dateObj = new Date(data);
                    if (dateObj != "Invalid Date") {
                        $(this.element).text(kendo.toString(dateObj, this.dateformat));
                    } else {
                        if (typeof data == 'string') {
                            var plus = data.indexOf('+');
                            if (plus == -1) plus = data.indexOf(')');
                            if (data.length > 6 && data.substr(0, 6) == '/Date(' && plus != -1) {
                                dateObj = $.cv.util.toDate(data);
                                $(this.element).text(kendo.toString(dateObj, this.dateformat));
                            }
                        }
                    }
                }
            }
        });

        /**
         * Examples: (template)
         *
         *   <!-- below moneyformat defaults to "c" -->
         *   <span data-bind="money: viewModelValue"></span>
         *   
         *   <!-- below moneyformat is explicity specified -->
         *   <span data-bind="money: viewModelValue" data-moneyformat="2c"></span>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          myMoney : 124.24, // i.e. assign the money value here....
         *      }
        **/
        k.data.binders.money = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                this.$el = $(element);

                var format = this.$el.data("moneyformat");
                if (!format) {
                    format = "c";
                }

                this.moneyformat = format;
            },
            refresh: function () {
                var data = this.bindings["money"].get();

                if (!data) {
                    data = 0;
                }

                this.$el.text(kendo.toString(data, this.moneyformat));
            }
        });

        /**
         * Examples: (template)
         *
         *   <span data-bind="percent: nonDecimalPercent" data-percentmode="nondecimal" data-percentformat="p0"></span>
         *  
         *   output: 10%     // notice no decimal places
         *
         *   <span data-bind="percent: decimalPercent" data-percentmode="decimal" data-percentformat="p"></span>
         *
         *   output: 10.00%  // notice decimal places
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          nonDecimalPercent : 10,  // Non-Decimal Representation - DEFAULT
         *          decimalPercent    : 0.10 // Decimal Representation
         *      }
        **/
        k.data.binders.percent = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                this.$el = $(element);

                var format = this.$el.data("percentformat");
                if (!format) {
                    format = "p0";
                }

                var mode = this.$el.data("percentmode");
                if (!mode) {
                    mode = "nondecimal";
                }

                this.percentformat = format;
                this.percentmode = mode;
            },
            refresh: function () {
                var data = this.bindings["percent"].get();

                if (!data) {
                    data = 0;
                }

                if (this.percentmode.toLowerCase() === 'nondecimal') {
                    data = data / 100; // i.e. 10 to 0.10 for proper formatting below.
                }

                this.$el.text(kendo.toString(data, this.percentformat));
            }
        });


        /**
         * OVERRIDE KENDO TEXT TO SUPPORT GP-MASKING
         *
         * <span data-bind="attr: { data-gp-mask: maskGPData }, text: value"></span>
        **/
        k.data.binders.text = kendo.data.Binder.extend({
            refresh: function () {
                var $el = $(this.element),
                    value = this.bindings["text"].get(),
                    maskGPData = $el.attr('data-gp-mask') == 'true';

                if (value == null) {
                    value = "";
                }

                if (maskGPData) {
                    var fieldName = this.bindings.text.path;
                    $el.text($.cv.css.maskGPValue(fieldName, value, kendo));
                } else {
                    $el.text(value);
                }
            }
        });

        /**
         * Examples: (template)
         *
         *   <!-- below numberformat defaults to "n2" -->
         *   <span data-bind="number: viewModelValue"></span>
         *   
         *   <!-- below numberformat is explicity specified -->
         *   <span data-bind="number: viewModelValue" data-numberformat="n2"></span>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          myNumber : 124.24, // i.e. assign the value here....
         *      }
        **/
        k.data.binders.number = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                var format = $(element).data("numberformat");
                if (!format) {
                    format = "n2";
                }

                this.numberformat = format;
            },
            refresh: function () {
                var data = this.bindings["number"].get();

                if (!data) {
                    data = 0;
                }

                $(this.element).text(kendo.toString(data, this.numberformat));
            }
        });

        k.data.binders.tabindex = k.data.Binder.extend({
            refresh: function () {
                var value = this.bindings["tabindex"].get();
                var $el = $(this.element);
                if ($el.attr("tabindex") != value) {
                    $el.attr("tabindex", value);
                }
            }
        });

        k.data.binders.widget.tabindex = k.data.Binder.extend({
            init: function (widget, bindings, options) {
                //call the base constructor
                k.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
            },
            refresh: function () {
                var that = this,
                value = that.bindings["tabindex"].get(); //get the value from the View-Model
                $(that.element).parent().find(".k-input").attr("tabindex", value);
            }
        });


        // this will pull out of the input box the default value and update the viewModel
        k.data.binders.valueWithDefault = k.data.Binder.extend({
            init: function (element, bindings, options) {
                //call the base constructor
                k.data.Binder.fn.init.call(this, element, bindings, options);

                var that = this;
                if (that.element.defaultValue != "") {
                    $(that.element).val(that.element.defaultValue);
                    that.bindings["valueWithDefault"].set(that.element.defaultValue); //update the View-Model
                }
                //listen for the change event of the element
                $(that.element).on("change", function () {
                    that.change(); //call the change function
                });
            },
            refresh: function () {
                var that = this,
                    value = that.bindings["valueWithDefault"].get(); //get the value from the View-Model
                if ((value == "" || value == undefined) && that.element.defaultValue != "") { // if the value is set to empty, default it to the default value
                    that.bindings["valueWithDefault"].set(that.element.defaultValue); //update the View-Model
                }
                else
                    $(that.element).val(value); //update the HTML input element
            },
            change: function () {
                var value = this.element.value;
                this.bindings["valueWithDefault"].set(value); //update the View-Model
            }
        });

        // this will update the viewModel when paste from the mouse or Ctrl + V shortcuts are used
        // you only need to bind this when you have also set the data-value-update= property on an element
        // it will also only work if data-value-update contains keyup
        /**
         * Examples: (template)
         *
         *   <span data-value-update="keyup change" data-bind="paste: true"></span>
         *
        **/
        k.data.binders.paste = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                $(element).bind("paste", function () {
                    var self = this;
                    setTimeout(function (e) {
                        $(self).keyup();
                    }, 0);
                });
            },
            refresh: function () {

            }
        });

        /**
         * Custom Binding for showing or hiding an element in the view based 
         * on object in the View Model.
         *
         * Example: (template)
         *   <div data-bind="displayAnimation: { slide: showElement }"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          showElement: true
         *      }
        **/
        k.data.binders.displayAnimation = k.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                // get list of display actions from our complex binding path object
                this._lookups = [];

                // get the speed of the animation
                var speed = $(element).data("displaySpeed");
                if (!speed) {
                    speed = "fast";
                }
                for (var key in this.bindings.displayAnimation.path) {
                    this._lookups.push({
                        key: key + "Toggle",
                        path: this.bindings.displayAnimation.path[key],
                        speed: speed
                    });
                }
            },
            refresh: function () {
                var lookup, value;

                for (var i = 0; i < this._lookups.length; i++) {
                    lookup = this._lookups[i];

                    // set the binder's path to the one for this lookup,
                    // because this is what .get() acts on.
                    this.bindings.displayAnimation.path = lookup.path;
                    value = this.bindings.displayAnimation.get();

                    // toggle element class based on if value is true
                    if (value) {
                        $(this.element).hide()[lookup.key](lookup.speed);
                    } else {
                        $(this.element).hide();
                    }
                }
            }
        });

        /**
         * Custom Binding for showing or hiding an element in the view based 
         * on object in the View Model.
         *
         * Example: (template)
         *   <div data-bind="displayAnimationInOut: { fade: showElement }"></div>
         *   <div data-bind="displayAnimationInOut: { fade: showElement }" data-animation-delay="200"></div>
         *   <div data-bind="displayAnimationInOut: { fade: showElement }" data-display-speed="slow" data-animation-delay="200"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          showElement: true
         *      }
        **/
        k.data.binders.displayAnimationInOut = k.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                // get list of display actions from our complex binding path object
                this._lookups = [];

                // get the speed of the animation
                var speed = $(element).data("displaySpeed");
                if (!speed) {
                    speed = "fast";
                }
                var animationDelay = $(element).data("animationDelay");
                if (!animationDelay) {
                    animationDelay = $.cv.css.animationDelay;
                }
                for (var key in this.bindings.displayAnimationInOut.path) {
                    this._lookups.push({
                        key: key + "Toggle",
                        path: this.bindings.displayAnimationInOut.path[key],
                        speed: speed,
                        animationDelay: animationDelay
                    });
                }
            },
            refresh: function () {
                var lookup, value;

                for (var i = 0; i < this._lookups.length; i++) {
                    lookup = this._lookups[i];

                    // set the binder's path to the one for this lookup,
                    // because this is what .get() acts on.
                    this.bindings.displayAnimationInOut.path = lookup.path;
                    value = this.bindings.displayAnimationInOut.get();

                    // toggle element class based on if value is true
                    if (value) {
                        $(this.element).hide()[lookup.key](lookup.speed).delay(lookup.animationDelay)[lookup.key](lookup.speed);
                    }
                }
            }
        });

        /**
         * Custom Binding for setting the max value on a date or datetime picker
         * Works with data-role="datepicker" or data-role="datetimepicker"
         *
         * Example: (template)
         *   <div data-bind="max: maxDate" data-role="datepicker"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          maxDate: new Date()
         *      }
        **/

        kendo.data.binders.widget.max = kendo.data.Binder.extend({
            init: function (widget, bindings, options) {
                //call the base constructor
                kendo.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
            },
            refresh: function () {
                var value = this.bindings["max"].get(); //get the value from the View-Model
                if ($(this.element).data("kendoDatePicker")) {
                    $(this.element).data("kendoDatePicker").max(value); //update the widget
                }
                if ($(this.element).data("kendoDateTimePicker")) {
                    $(this.element).data("kendoDateTimePicker").max(value); //update the widget
                }
            }
        });

        /**
         * Custom Binding for setting the min value on a date or datetime picker
         * Works with data-role="datepicker" or data-role="datetimepicker"
         *
         * Example: (template)
         *   <div data-bind="min: minDate" data-role="datepicker"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          minDate: new Date()
         *      }
        **/

        kendo.data.binders.widget.min = kendo.data.Binder.extend({
            init: function (widget, bindings, options) {
                //call the base constructor
                kendo.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
            },
            refresh: function () {
                var value = this.bindings["min"].get(); //get the value from the View-Model
                if ($(this.element).data("kendoDatePicker")) {
                    $(this.element).data("kendoDatePicker").min(value); //update the widget
                }
                if ($(this.element).data("kendoDateTimePicker")) {
                    $(this.element).data("kendoDateTimePicker").min(value); //update the widget
                }
            }
        });

        /**
         * Custom binding that executes a ViewModel method only when Enter is 
         * pressed.
         *
         * Example: (template)
         *   <input type="text" data-bind="enterPressed: doSearch" value="mySearchTerm"></div>
         *
         * Example: (view model)
         *      var viewModel = ... {
         *          doSearch: function(e) {
         *             // This method will only execute when you press ENTER!
         *          }
         *      }
        **/
        k.data.binders.enterPressed = k.data.binders.widget.enterPressed = kendo.data.Binder.extend({
            init: function (element, bindings, options) {
                kendo.data.Binder.fn.init.call(this, element, bindings, options);

                var binder = bindings.enterPressed,
                    source = binder.source,
                    path = binder.path;

                // Handle odd cases like the input field is a kendo autocomplete... try and find
                // the ACTUAL element
                if (!$(element).is(":input")) {

                    // enterPressed on a KendoAutoComplete control will have the element
                    // in the element field
                    if (element.element) {
                        element = element.element;
                    }
                }

                $(element).bind($.cv.css.enterPressedEvent, function (e) {
                    if (e.which == 13) {
                        // Prevent any form submission or handling by something up the DOM
                        e.preventDefault();
                        e.stopPropagation();

                        // Execute the ViewModel Method
                        if (source[path] != null && $.type(source[path]) === 'function') {
                            source[path](e);
                        }
                    }
                });
            },
            refresh: $.noop
        });

    })(kendo);

})(jQuery);

;
/// <reference path="jquery-1.7.2.js" />

/*
cv.data.kendo.js
    
Core library for MVVVM binding datasurces.

Dependencies:
jquery
In Script folder or http://jquery.com/

cv.util.js
In Script folder

*/

;
(function ($, undefined) {  /// <param name="$" type="jQuery" />

    //$ = jQuery;

    $.cv = $.cv || {};
    
    $.cv.data = $.cv.data || {};

    $.cv.data.dataSource = function (options) {
        var defopts = {
            method: '',
            url: '',
            updateurl: '',
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            type: "POST",
            change: null,
            pageSize: 15,
            params: {},
            sort: {},
            serverFiltering: true,
            serverPaging: true,
            serverSorting: true,
            update: false,
            destroy: false,
            create: false,
            batch: false,
            error: function(e) {
                var response = e.xhr ? $.parseJSON(e.xhr.responseText) : $.parseJSON(e.responseText);
                if (response.sessionHasTimedOut) {
                    $.cv.ajax.redirectFromTimeout();
                }
                else {
                    var msg = "An error occurred. Please try again. Close your browser and login again if the problem persists";
                    if (response.errorMessage) {
                        msg = msg + '. Error Message : ' + response.errorMessage;
                    }
                    if ($.cv.ui && $.cv.ui.alert) {
                        $.cv.ui.alert({
                            title: 'Datasource Error',
                            message: msg
                        });
                    }
                    else {
                        alert('ERROR: ' + msg);
                    }
                }
            }
        };

        var opts = $.extend(defopts, options);

        if (opts.method) {
            opts.url = $.cv.ajax.settings.url + '/' + opts.method;
        }

        var getSort = function (sortString) {
            var results = [];

            sortString = $.trim(sortString);

            if (sortString) {
                // Separate into separate field dir parts
                // i.e. from: "Description asc, ProductCode desc"
                // i.e.   to: ["Description asc", "ProductCode desc"]
                var fieldDetails = sortString.split(","); 

                if (fieldDetails.length > 0) {
                    for (var i = 0; i < fieldDetails.length; i++) {
                        // Split into field direction parts
                        // i.e. from: "Description asc"
                        // i.e.   to: ["Description", "asc"]
                        var fieldDir = $.trim(fieldDetails[i]).split(" ");

                        // Cater for situations where there is only field name and no direction
                        // as well as when both as specified
                        if (fieldDir.length == 1) 
                            results.push({ field:fieldDir[0], dir: 'asc'});
                        else if (fieldDir.length == 2)
                            results.push({ field:fieldDir[0], dir: fieldDir[1]});
                    }
                }
            }

            return results;
        };

        var dsconfig = {
            transport: {
                read: {
                    url: opts.url + "?rand=" + ($.now()),
                    dataType: opts.dataType,
                    contentType: opts.contentType,
                    type: opts.type
                },
                parameterMap: function (options, type) {
                    var params = options;

                    // Handle sort that is a string. i.e. Description desc
                    if (options.sort && typeof options.sort === 'string') {
                        params.sort = getSort(options.sort);
                    }

                    // Extend Params from function/object
                    if (typeof (opts.params) == "function")
                        $.extend(params, opts.params(options, type));
                    else
                        $.extend(params, opts.params);

                    // If params are filtering parameters (i.e. they filter the data), remove the standard filter params
                    if (opts.filterParams) {
                        delete params.filter;
                    }

                    // Add session id for handling session expiry
                    if ($.cookie) {
                        params._sessionId = $.cookie("dynamicServiceSessionId");
                    }

                    // do any date adjusting here
                    params = $.cv.util.parseDates(params, true);

                    return kendo.stringify(params);
                }
            },
            serverPaging: opts.serverPaging,
            serverSorting: opts.serverSorting,
            serverFiltering: opts.serverFiltering,
            pageSize: opts.pageSize,
            batch: opts.batch,
            sort: opts.sort,

            schema: {
                data: function (result) {
                    return result.data;
                },
                total: function (result) {
                    return result.recordCount;
                },
                parse: function(data) {
                    // check for string object that needs casting to javascript object
                    if (data.data) {
                        if (data.data.length) {
                            $.each(data.data, function(index, item) {
                                $.cv.data.parseDate(index, item, data);
                            });
                        } else {
                            var itemToParse = data.data;
                            $.cv.data.parseDate(null, itemToParse, data.data);
                        }
                    }
                return data;
                }
            }
        };
       
        if (opts.model) {
            dsconfig.schema.model = opts.model;
        }

        if (opts.change) {
            dsconfig.change = opts.change;
        }

        if (opts.updateurl != '') {
            dsconfig.transport.update = {
                url: opts.updateurl,
                dataType: opts.dataType,
                contentType: opts.contentType,
                type: opts.type
            };
        }
        else if (opts.update) {
            dsconfig.transport.update = {
                url: opts.url + '-update',
                dataType: opts.dataType,
                contentType: opts.contentType,
                type: opts.type
            }
        }

        if (opts.destroy) {
            dsconfig.transport.destroy = {
                url: opts.url + '-destroy',
                dataType: opts.dataType,
                contentType: opts.contentType,
                type: opts.type
            }
        }
        if (opts.create) {
            dsconfig.transport.create = {
                url: opts.url + '-create',
                dataType: opts.dataType,
                contentType: opts.contentType,
                type: opts.type
            }
        }

        if (!dsconfig.schema.model) {
            dsconfig.schema.model = {
                id: "_objectKey"
            };
        }

        if (opts.fields) {
            dsconfig.schema.model.fields = opts.fields;
        }

        if (opts.error) {
            dsconfig.error = opts.error;
        }

        if (opts.options != undefined) {
            $.extend(dsconfig, opts.options);
        }

         // setup data
        if (opts.data) {
            // clear transport if no method set
            if (!opts.method || opts.method.length == 0)
                delete dsconfig.transport;
            dsconfig.data = opts.data;
            delete dsconfig.serverPaging;
            delete dsconfig.serverSorting;
            delete dsconfig.serverFiltering;
            delete dsconfig.batch;
            delete dsconfig.schema;
            delete dsconfig.sort;
            delete dsconfig.pageSize;
        }

        var ds = new kendo.data.DataSource(dsconfig);

        return ds;
    };

    $.cv.data.parseDate = function (index, item, data) {
        $.each(item, function(currindex, currentitem) {
            if (typeof currentitem == 'string') {
                var plus = currentitem.indexOf('+');
                if (plus == -1) plus = currentitem.indexOf(')');
                if (currentitem.length > 6 && currentitem.substr(0, 6) == '/Date(' && plus != -1) {
                    if (index != null)
                        data.data[index][currindex] = $.cv.util.toDate(currentitem);
                    else 
                        data.data[currindex] = $.cv.util.toDate(currentitem);
                }
            }
        });
    };

    $.cv.data.getLookupText = function (lookup, values) {
        if (lookup === null) {
            lookup = '';
        }
        var result = lookup === '' ? '' : lookup.toString() + ' - Unknown';
        var found = false;
        $.each(values, function(index, item) {
            if (item.Value === lookup.toString()) {
                result = item.Text;
                found = true;
            }
        });
        if (!found && lookup === 0) {
            result = '';
        }
        return result;
    }

    $.cv.data.getGrid = function (container) {
        return $(container).closest("div[data-role='grid']").data("kendoGrid");
    }

    $.cv.data.getGridColumn = function (grid, col) {
        var colToReturn = null;
        $.each(grid.columns,function(index,item) {
            if (item.field == col) 
                colToReturn = item;    
        });
        return colToReturn;
    }

    $.cv.data.checkEditableField = function (container, options) {
        var theGrid = $.cv.data.getGrid(container);
        var colData = $.cv.data.getGridColumn(theGrid,options.field);
        if ((options.model.isNew() && !colData.canAdd) || (!options.model.isNew() && !colData.canEdit)) {
            $('<span data-bind="text:' + options.field + '"></span>').appendTo(container);
            return false;
        } else {
            return true;
        }
    }

    $.cv.data.gridColumnEditors = {
        Email: function (container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input type="email" data-bind="value:' + options.field + '" class="k-input k-textbox" />').appendTo(container);
        },
        URL: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
            $('<input type="url" data-bind="value:' + options.field + '" class="k-input k-textbox" />').appendTo(container);
        },
        TextBox: function (container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-bind="value:' + options.field + '" class="k-input k-textbox" />').appendTo(container);
        },
        CheckBox: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input type="checkbox" name="' + options.field + '" data-bind="checked: ' + options.field + '" value="" />').click(function () { var checked = $(this).attr("checked") == "checked" ? "True" : "False"; $(this).attr("value", checked); }).appendTo(container);
        },
        DatePicker: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-role="datepicker" data-bind="value: ' + options.field + '" />').appendTo(container);
        },
        TimePicker: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-role="timepicker" data-bind="value: ' + options.field + '" />').appendTo(container);
        },
        DateTimePicker: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-role="datetimepicker" data-bind="value: ' + options.field + '" />').appendTo(container);
        },
        NumericTextBox: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-role="numerictextbox" data-format="n' + options.length + '" data-bind="value: ' + options.field + '" />').appendTo(container);
        },
        NumericIntTextBox: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-format="n0" data-bind="value: ' + options.field + '" />').appendTo(container).kendoNumericTextBox({ step: 1 });
        }, 
        Html: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<textarea id="editor" rows="' + options.rows + '" cols="30" data-bind="value:' + options.field + '" />').appendTo(container).kendoEditor();
        },  
        Password: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-bind="value:' + options.field + '" class="k-input k-password" type="password" />').appendTo(container);
        },
        Money: function(container, options) {
            if ($.cv.data.checkEditableField(container, options))
                $('<input data-format="n2" data-bind="value: ' + options.field + '" />').appendTo(container).kendoNumericTextBox({ step: 0.01, decimals: 2 });
        },
        DropDown: function(container, options) {
            if ($.cv.data.checkEditableField(container, options)) {
                // options - field, model
                var field = options.model.fields[options.field];
                var ddoptions = {
                    autoBind: false,
                    dataSource: new kendo.data.DataSource({
                        data: field.lookupValues
                    })
                };
                if (!field.mandatory) {
                    ddoptions.optionLabel = 'Select ..';
                }
                $('<input data-text-field="Text" data-value-field="Value" data-bind="value:' + options.field + '"/>')
                    .appendTo(container)
                    .kendoDropDownList(ddoptions);
            }
        },
        DropDownDynamic: function(container, options) {
            if ($.cv.data.checkEditableField(container, options)) {
                var field = options.model.fields[options.field];
                var comboOptions = {
                    autoBind: false,
                    dataSource: $.cv.data.dataSource({
                        method: field.method + '-lookup-' + options.field
                    }),
                    filter: "contains"
                };
                $("<input data-text-field='Text' data-value-field='Value' data-bind='value:" + options.field + "' />")
                    .appendTo(container)
                    .kendoComboBox(comboOptions);
            }
        }
    }

    $.cv.data.gridColumnTemplates = {
        Email: function(field) {
            return '#= kendo.toString(' + field.fieldName + ') #';
        },
        TextBox: function(field) {
            return '#= kendo.toString(' + field.fieldName + ') #';
        },
        TimePicker: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"hh:mm tt") #';            
        },
        NumericTextBox: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"n' + field.length + '") #';            
        },
        NumericIntTextBox: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"n0") #';            
        }, 
        Html: function(field) {
            return '#= kendo.toString(' + field.fieldName + ') #';
        },  
        Password: function(field) {
            return '#= kendo.toString(' + field.fieldName + ') == null ? "" : kendo.toString(' + field.fieldName + ').replace(/./g,"x") #';
        },
        Money: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"c") #';            
        }, 
        CheckBox:  function(field) {
            return '#= ' + field.fieldName + ' ? "Yes" : "No" #';
        },

        DatePicker: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"dd-MMM-yyyy") #';
        },

        DateTimePicker: function(field) {
            return '#= kendo.toString(' + field.fieldName + ',"dd-MMM-yyyy hh:mm tt") #';
        },

        DropDown: function(field) {
            return '#= ( data.hasOwnProperty("' + field.fieldName + '__DisplayText") ? ' + field.fieldName + '__DisplayText : ' + field.fieldName + ' ) # '; 
        },

        DropDownDynamic: function(field) {
            return '#= ( data.hasOwnProperty("' + field.fieldName + '__DisplayText") ? ' + field.fieldName + '__DisplayText : ' + field.fieldName + ' ) # '; 
        }

    };

  
    // creates a column object for use in the kendo grid
    //
    $.cv.data.gridColumn = function(field, method) {
        var colObj = {
            field: field.fieldName,
            title: field.prompt
        };
        // set default displayType
        if (!field.displayType) {
            if (field.type == "date") field.displayType = "DatePicker";
            if (field.type == "datetime") field.displayType = "DateTimePicker";
            if (field.type == "bit") field.displayType = "Checkbox";
        }
        // text
        if (field.type == "text") {
            colObj.sortable = false;
        }
        // primary key and protected settings to allow add/edit
        colObj.canAdd = field.type != 'identity';
        colObj.canEdit = !field.protected && !field.primarykey;
        // column editor
        if ($.cv.data.gridColumnEditors[field.displayType]) { //&& !field.protected
            colObj.editor = $.cv.data.gridColumnEditors[field.displayType];
        }
        // column template
        if ($.cv.data.gridColumnTemplates[field.displayType]) {
            colObj.template = $.cv.data.gridColumnTemplates[field.displayType](field);
        }
        return colObj;
    };

    // field object for mvvm binding
    $.cv.data.fieldMVVM = function(field, method) {
        var fieldObj = field;

        // primary key and protected settings to allow add/edit
        fieldObj.canAdd = field.type != 'identity';
        fieldObj.canEdit = !field.protected && !field.primarykey;

        if (field.displayType == "DropDownDynamic") {
            fieldObj.method = method; // store the method for use in the autocomplete datasource
        }
        return fieldObj;
    };

    // creates a field object for use in a kendo datasource
    //
    $.cv.data.field = function(field, method) {
        var fieldObj = {
            type: "string"
        };
        // nullable stuffs up the DD editor
        if (field.displayType != "DropDown" && field.displayType != "DropDownDynamic") {
            fieldObj.nullable = true;
        }

        // set defaultValue if available
        if (field.defaultValue) {
            fieldObj.defaultValue = field.defaultValue;
        }

        // set the name so we can see this when creating new objects based on fieldObj collection
        if (field.name) {
            fieldObj.name = field.name;
        }

        if (field.mandatory) {
            fieldObj.validation = { required: true };
            // workaround for grid dropdown only binding value on "change" of dropdown.
            if (field.lookupValues && field.lookupValues.length > 0) {
                fieldObj.defaultValue = field.lookupValues[0].Value;
            }
        }
        if (field.lookupValues) {
            fieldObj.lookupValues = field.lookupValues;
        }
        if (field.displayType == "DropDownDynamic") {
            fieldObj.method = method; // store the method for use in the autocomplete datasource
        }
        // dateTime
        if (field.type == "date" || field.type == "datetime") {
            fieldObj.type = "date";
        }
        // numeric
        if (field.type == "int" || field.type == "decimal") {
            fieldObj.type = "number";
            // workaround for grid bug where dropdown cannot change a null value
            if (field.displayType == "DropDown") {
                fieldObj.parse = function(val) {
                    if (val === null) {
                        return 0;
                    }
                    return kendo.parseFloat(val);
                };
            }
            // workaround for comboBox not setting values
            if (field.displayType == "DropDownDynamic") {
                fieldObj.type = "string";
                fieldObj.parse = function(val) {
                    if (val === null) {
                        return '';
                    }
                    return val.toString();
                };
            }
        }
        // boolean
        if (field.type == "bit") {
            fieldObj.type = "boolean";
        }
        if (field.type == "varchar" && field.displayType == "DropDown") {
            // when binding dropdown need to pull out value from object
            fieldObj.parse = function(val) {
                if (val) {
                    if (val === null) {
                        return '';
                    }
                    if (val.value)
                        return val.value.toString();
                    else
                    return val.toString();
                } else {
                    return '';
                }
            };
        }
       

        return fieldObj;
    };

    // form field templates the same as grid templates
    $.cv.data.fieldDisplayTemplates = $.cv.data.gridColumnTemplates;

    // check if field editable based on cvfield primary key/identity rules
    $.cv.data.fieldEditTempateCanEdit = function (field, add) {
        if ((add && !field.canAdd) || (!add && !field.canEdit))
            return '<span data-bind="text:' + field.fieldName + '"></span>';
        else
            return "";
    }

    $.cv.data.fieldEditTemplates = {
        Email: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="email" data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" name="' + field.fieldName + '" ' + (field.mandatory ? 'required="required"' : ' ') + ' class="k-input k-textbox" />';
            else
                return check;
        },
        URL: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="url" data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" name="' + field.fieldName + '" ' + (field.mandatory ? 'required="required"' : ' ') + ' class="k-input k-textbox" />';
            else
                return check;
        },
        TextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" name="' + field.fieldName + '" ' + (field.mandatory ? 'required="required"' : ' ') + ' class="k-input k-textbox" />';
            else
                return check;
        },
        CheckBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input type="checkbox" name="' + field.fieldName + '" data-bind="checked:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" value="" />';
            else
                return check;
        },
        DatePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-role="datepicker" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },
        TimePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-role="timepicker" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },
        DateTimePicker: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-role="datetimepicker" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },
        NumericTextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-role="numerictextbox" name="' + field.fieldName + '" data-format="n' + (field.length ? field.length : 2) + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },
        NumericIntTextBox: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-step="1" data-format="n0" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        }, 
        Html: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<textarea id="editor" name="' + field.fieldName + '" rows="' + (field.rows ? field.rows : 5) + '" cols="' + (field.cols ? field.cols : 30) + '" data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },  
        Password: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" name="' + field.fieldName + '" class="k-input k-textbox k-password" type="password" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        },
        Money: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<input data-step="0.01" data-decimals="2" data-format="n2" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        }, 
        DropDown: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0) {
                var input = '<select data-text-field="Text" data-value-field="Value" data-role="dropdownlist" name="' + field.fieldName + '" data-bind="value: ' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled, source:' + field.fieldName + '_lookup" >';
                input = input + "</select>";
                return input;
            }
            else
                return check;
        },
        DropDownDynamic: function (field, add) {
            var check = $.cv.data.fieldEditTempateCanEdit(field, add);
            if (check.length == 0)
                return '<select data-text-field="Text" data-value-field="Value" data-role="combobox" name="' + field.fieldName + '" data-bind="value:' + field.fieldName + ', enabled:' + field.fieldName + '_isEnabled, source:' + field.fieldName + '_lookup" ' + (field.mandatory ? 'required="required"' : '') + ' />';
            else
                return check;
        }
    };

    $.cv.data.fieldContainerTemplate = function(field, controlTemplate, help) {
        return "<div class='fieldContainer' data-bind='visible:" + field.fieldName + "_isVisible, enabled:" + field.fieldName + "_isEnabled'><label>" + field.prompt + ":&nbsp;</label>" + help + controlTemplate + "</div>";
    };

    // generate markup for help tooltip if help defined on field
    $.cv.data.fieldHelpTemplate = function(field) {
        if (field.help && field.help.length > 0)
            return "<span class='cv-tooltip'>?<span class='cv-tooltip-message'>" + field.help + "</span></span>";
        else
            return "";
    }

    $.cv.data.formTemplate = {
        Start: function () {
            return '<div class="list-item-container">';
        },
        End: function () {
            return '</div>';
        }
    }

    $.cv.data.fieldTemplateCollection = function (fields, edit, add) {
        var template = $.cv.data.formTemplate["Start"]();
        $.each(fields, function (index, field) {
           template = template + $.cv.data.fieldTemplate(field, edit,add);
        });
        template = template + $.cv.data.formTemplate["End"]();
        return template;
    };

    $.cv.data.fieldTemplate = function(field, edit, add) {
        // set default displayType
        if (!field.displayType) {
            if (field.type == "date") field.displayType = "DatePicker";
            if (field.type == "datetime") field.displayType = "DateTimePicker";
            if (field.type == "bit") field.displayType = "Checkbox";
        }
        var foundControlTemplate;
        if (add) {
            foundControlTemplate = $.cv.data.fieldEditTemplates[field.displayType](field, add);
        } else if (edit) {
             foundControlTemplate = $.cv.data.fieldEditTemplates[field.displayType](field);
        } else {
             foundControlTemplate = $.cv.data.fieldDisplayTemplates[field.displayType](field);
        }
        // now get help span if help defined on field
        var helpMarkup = $.cv.data.fieldHelpTemplate(field);

        return $.cv.data.fieldContainerTemplate(field, foundControlTemplate, helpMarkup);
    };
    
    // extend object for Kendo UI MVVM databind features visible, enabled and datasources
    $.cv.data.extendObject = function(item, schema) {
         $.each(schema.properties, function (index, field) {
            item[field.fieldName + "_isVisible"] = true;
            item[field.fieldName + "_isEnabled"] = true;
            // check for lookups
            if (field.lookupValues && $.isArray(field.lookupValues)) {
                // check if not mandatory, if so add a select entry if one not already in there
                if (!field.mandatory && $.grep(field.lookupValues, function(n) { return n.Value == "-1"; }) == false)
                    field.lookupValues.splice(0,0,{Text:'Select...',Value: "-1"});
                item[field.fieldName + "_lookup"] = $.cv.data.dataSource({ dataType: '', data: field.lookupValues, transport: null });
            } else if (field.lookup && field.lookup.length > 0) {
                // there is a lookup defined( a method or static), so we can call dynamic service with <servicename>-lookup-<fieldname>
                item[field.fieldName + "_lookup"] = $.cv.data.dataSource({
                    method: field.method + '-lookup-' + field.fieldName
            });
            }
         })
    };

    // clean up properties added to observable for mvvm form binding but not persisted data
    $.cv.data.reduceObject = function(item, schema) {
         $.each(schema.properties, function (index, field) {
            // check for lookups
            if (item[field.fieldName + "_lookup"])
                delete item[field.fieldName + "_lookup"];
            if (item[field.fieldName + "_isVisible"]) 
                delete item[field.fieldName + "_isVisible"];
            if (item[field.fieldName + "_isEnabled"]) 
                delete item[field.fieldName + "_isEnabled"];
         })
    };

    // create a new object based on the schema object from $.cv.data.loadSchema
    $.cv.data.newObject = function(ds) {
        var objectToReturn = {};
        // iterate over each field and set property and default if defined
        $.each(ds.properties, function (index, field) {
            if (field.defaultValue) {
                var itemToAssign = null;
                if (field.type == "date") {
                    itemToAssign = $.cv.util.toDate(field.defaultValue);
                } else if (field.type == "bit") {
                    itemToAssign = field.defaultValue === "1" ? true : false;
                } else {
                    itemToAssign = field.defaultValue;
                }
                objectToReturn[field.fieldName] = itemToAssign;
            } else {
                if (field.type == "int" || field.type == "bit" || field.type == "decimal" || field.type == "date" || field.type == "datetime") {
                    objectToReturn[field.fieldName] = null;
                } else {
                     objectToReturn[field.fieldName] = '';
                }
            }

        });
        return objectToReturn;
    };

    $.cv.data.loadSchema = function (options) {

        var opts = $.extend({
            method: '',
            gridColumnWidths: '',
            schemaLoaded: $.noop,
            mode: '', // can be grid/listview/mvvmform
            gridColumns: '',
            formFields: '',
            params: ''
        }, options);

        return $.cv.ajax.call(opts.method + '-schema', {
            parameters: opts.params,
            success: function (msg) {
                // construct on object and pass it as a param to schemaLoaded
                // object properties: properties, dataSourceFields, gridColumns
                var s = {}; 
                s.properties = msg.data;
                s.dataSourceFields = {};
                s.gridColumns = [];
                s.formTemplate = {};
                s.formEditTemplate = {};
                s.formAddTemplate = {};
               
                if (opts.mode == '' || opts.mode.indexOf('grid') != -1 ) {
                    $.each(s.properties, function (index, field) {
                        var fieldObj = $.cv.data.field(field, opts.method);
                        var colObj = $.cv.data.gridColumn(field, opts.method);
                        s.dataSourceFields[field.fieldName] = fieldObj;
                        if (opts.gridColumns == '' || $.inArray(field.fieldName,opts.gridColumns.split(',')) != -1) {
                            s.gridColumns.push(colObj);
                        }
                    });
                }

                // field displays for list/mvvm form
                if (opts.mode == '' || opts.mode.indexOf('listview') != -1 || opts.mode.indexOf('mvmmform') != -1 ) {
                    
                    // filter down set of fields if set in formfields
                    var fieldsForForm = [];
                    $.each(s.properties, function (index, field) {
                    if (opts.formFields == '' || $.inArray(field.fieldName,opts.formFields.split(',')) != -1) {
                        var fieldOb = $.cv.data.fieldMVVM(field, opts.method);
                        fieldsForForm.push(fieldOb);
                        }
                    });

                    if (opts.mode == '' || opts.mode.indexOf('listview') != -1 || opts.mode.indexOf('mvmmform') != -1  ) {
                        s.formTemplate = $.cv.data.fieldTemplateCollection(fieldsForForm, false);
                        s.formEditTemplate = $.cv.data.fieldTemplateCollection(fieldsForForm, true);
                        s.formAddTemplate = $.cv.data.fieldTemplateCollection(fieldsForForm, false, true);
                    }

                }

                // apply the col widths to the col objects
                var widths = opts.gridColumnWidths.split(',');
                $.each(s.gridColumns, function(index, col) {
                    if (widths[index]) {
                        col.width = widths[index];
                    };
                });

                // call the schemaLoaded event function
                opts.schemaLoaded(s);
            }
        });

    }

})(jQuery);;
/// <reference path="../jquery-1.7.2.js" />
/*
 * cv.css.js
 * Author: Chad Paynter
 * Date: 14/09/2012
 * Description: cv.css plugin for calls to CSS dynamic services
 * Dependencies:
 * jquery - In Script folder or http://jquery.com/
 * cv.ajax -  In Script folder cv.ajax.js
 * cv.util - In Script folder cv.util.js
 */
;
(function ($, undefined) {

    $.cv = $.cv || {};

    // $.cv.css object definition
    //
    $.cv.css = $.cv.css || {};

    /* events */
    $.cv.css.events = $.cv.css.events || {};


    // Global Unique Identifier
    (function () {
        var guidValue = 0;

        $.cv.css.guid = function () {
            return guidValue++;
        };
    })();

    /* message types */
    $.cv.css.messageTypes = { error: 'alert-box alert', warning: 'alert-box warning', info: 'alert-box info', success: 'alert-box success' };

    /* global processing class */
    $.cv.css.isProcessingClass = 'cv-is-processing';

    /* global animation delay */
    $.cv.css.animationDelay = 3000;

    /* global mandatory field empty message, {0} is a place holder the field prompt */
    $.cv.css.mandatoryFieldIncompleteMessage = "The {0} field is required";

    /* global incomplete field message, {0} is a place holder the field prompt */
    $.cv.css.invalidFieldMessage = "{0} is incorrect or incomplete";

    /* global input error class */
    $.cv.css.inputError = "error";

    /* enter pressed binder event */
    $.cv.css.enterPressedEvent = "keyup";

    /* event names */
    $.cv.css.eventnames = {
        accountChanged: 'accountChanged',
        userChanged: 'userChanged',
        login: 'login',
        loginLogoutSuccess: 'loginLogoutSuccess',
        logout: 'logout',
        loggingOut: "loggingOut",
        orderChanged: 'orderChanged',
        orderSubmitted: 'orderSubmitted',
        catalogueChanged: 'catalogueChanged',
        message: 'message',
        checkoutMessages: 'checkoutMessages',
        userStocktakeChanged: 'userStocktakeChanged',
        userStocktakeItemRemoved: 'userStocktakeItemRemoved',
        
        // Approvals
        approvalConfirmed: 'approvalConfirmed',
        approvalNotSpecified: 'approvalNotSpecified',
        approvalError: 'approvalError',

        addressChanged: 'addressChanged',
        addressBeingEdited: 'addressBeingEdited',
        addressValidated: "addressValidated",
        billingAddressChanged: 'billingAddressChanged',
        addressSaved: 'addressSaved',
        cartItemsChanging: 'cartItemsChanging',
        cartItemsChanged: 'cartItemsChanged',
        refreshOrderData: 'refreshOrderData',
        preOrderSubmit: 'preOrderSubmit',
        preOrderSubmitComplete: 'preOrderSubmitComplete',
        widgetPreOrderSubmitComplete: 'widgetPreOrderSubmitComplete',
        giftCardsChanged: 'giftCardsChanged',
        branchChanged: 'branchChanged',
        deliveryAddressModeChanged: "deliveryAddressModeChanged",
        pickupWarehouseChanged: "pickupWarehouseChanged",
        templatedListChanged: "templatedListChanged",
        freightValidated: "freightValidated",
        freightQuoteSelected: "freightQuoteSelected", // raised from freightCarrier.js when opt to submit order as quote to get freight quote.
        freightQuoteInstructsVisisble: "freightQuoteInstructsVisisble", // raised from freightCarrier.js when option to choose a freight quote is made visible.
        metadataSummaryFilterChanged: "metadataSummaryFilterChanged", // raised from metaDataDummary.js when the filter changes and widget is set to not redirect
        cartLinesRendered: "cartLinesRendered",
        documentReady: "documentReady",
        promoCodeApplied: "promoCodeApplied",
        promoCodeRemoved: "promoCodeRemoved",
        productExtendedPriceUpdated: "productExtendedPriceUpdated",
        orderCompleteFieldsValidated: "orderCompleteFieldsValidated",

        // local storage events
        localOrderChanged: 'localOrderChanged',
        localdeliveryAddressChanged: "localdeliveryAddressChanged",

        // MyStampsApi
        myStampsApiRefreshImage: "myStampsApiRefreshImage",
        myStampsApiShowEditor: "myStampsApiShowEditor",
        myStampsEditorClosed: "myStampsEditorClosed",


        // Rewards
        //

        // ** Notification only (doesn't execute queries/commands)
        loyaltyRewardsChanged:    'loyaltyRewardsChanged',

        confirmRewardApplication: 'confirmRewardApplication',
        confirmRewardRemoval:     'confirmRewardRemoval',

        loyaltyRewardsConfirmationClosed: 'loyaltyRewardsConfirmationClosed',

        // Order Validation
        validateOrderStarted: 'validateOrderStarted',
        validateOrderComplete: 'validateOrderComplete',

        // Product Price Filter
        priceFilterSliderChange: "priceFilterSliderChange"
    };

    /* variable specifiying if a 400 error returned from a service call */
    $.cv.css.received400 = false;

    /* variable used to turn on or of widget field placeholders */
    $.cv.css.usePlaceHolders = true;

    /* widgets with validation errors or have not yet passed all validation requirements */
    $.cv.css.pageValidationErrors = {}; /* widgets with validation errors or have not yet passed all validation requirements */
    $.cv.css.preOrderSubmitPromiseObjects = {}; /* local storage data currently being loaded, stops multiple calls to the same service */
    $.cv.css.dataLoadingPromiseObjects = {};
    $.cv.css.addRemovePageValidationError = function (valid, widget) {
        if (!valid && !_.contains(_.keys($.cv.css.pageValidationErrors), widget)) {
            $.cv.css.pageValidationErrors[widget] = widget;
        }
        else if (valid && _.contains(_.keys($.cv.css.pageValidationErrors), widget)) {
            $.cv.css.pageValidationErrors = _.omit($.cv.css.pageValidationErrors, widget);
        }
    };

    $.cv.css.addRemoveSubmitPromiseObjects = function (add, widget, deferred) {
        if (add) {
            $.cv.css.preOrderSubmitPromiseObjects[widget] = deferred;
        }
        else {
            $.cv.css.preOrderSubmitPromiseObjects = _.omit($.cv.css.preOrderSubmitPromiseObjects, widget);
        }
    };

    $.cv.css.loadingPromiseObjectExists = function (key) {
        return (_.contains(_.keys($.cv.css.dataLoadingPromiseObjects), $.cv.css.localStorageKeys.currentOrder));
    };
    $.cv.css.addRemoveLoadingPromiseObjects = function (add, key, deferred) {
        if (add) {
            $.cv.css.dataLoadingPromiseObjects[key] = deferred;
        }
        else {
            $.cv.css.dataLoadingPromiseObjects = _.omit($.cv.css.dataLoadingPromiseObjects, key);
        }
    };

    $.cv.css.bind = function (eventCode, handler, insertBefore) {
        if (window.DEBUG === true) {
            console.info("EVENT BOUND: " + eventCode);
        }

        // Don't bind anything where the eventCode is not a valid string
        if (eventCode == null || eventCode === '' || $.trim(eventCode) === '') {
            return;
        }

        var eventCodes = [];

        if ($.type(eventCode) === 'array') {
            eventCodes = eventCode;
        } else {
            eventCodes.push(eventCode);
        }

        for (var ec in eventCodes) {
            (function () {
                var code = eventCodes[ec];
                var handlerArray = $.cv.css.events[code];
                insertBefore = typeof insertBefore !== 'undefined' ? insertBefore : false;
                if (!handlerArray) {
                    handlerArray = $.cv.css.events[code] = [];
                }
                if (!insertBefore)
                    handlerArray.push(handler);
                else {
                    handlerArray.unshift(handler);
                }
            })();
        }
    };


    $.cv.css.unbind = function (eventcode, handler) {
        if (window.DEBUG === true) {
            console.info("EVENT UNBOUND: " + eventcode);
        }

        var handlerArray = $.cv.css.events[eventcode];

        if (handlerArray == null)
            return;

        handlerArray = _.without(handlerArray, handler);

        $.cv.css.events[eventcode] = handlerArray;
    };

    $.cv.css.trigger = function (eventCode, params) {
        if (window.DEBUG === true) {
            console.info("EVENT TRIGGERED: " + eventCode);
        }

        var eventArgs = {
            cancel: false,
            stopHandlerExecution: false,
            event: eventCode,
            params: params
        };

        var cancelled = false,
            handlers = $.cv.css.events[eventCode],
            results = [],
            allDonePromise = $.Deferred();

        // Don't bind anything where the eventCode is not a valid string
        if (eventCode == null || eventCode === '' || $.trim(eventCode) === '') {
            handlers = false; // go into below if... no handlers! 
        }

        if (!handlers) {
            var noHandlerResponseData = {
                event: eventCode,
                params: params || null,
                cancelled: false,
                handlerCount: 0,
                results: []
            };

            allDonePromise.resolve(noHandlerResponseData);

            return $.extend(allDonePromise.promise(), noHandlerResponseData);
        }

        $.each(handlers, function (index, item) {
            // Results into returned object's results argument as array
            // so that caller can $.when() results if they are expected to be promises for example.
            // Anything that is not a Deferred will be considered auto-resolved!
            results.push(item(params, eventArgs));

            if (eventArgs.cancel === true) {
                cancelled = true;
            }

            if (eventArgs.stopHandlerExecution === true) {
                return false;
            }

            return true;
        });

        // Resolves when all results are resolved. handlers that return
        // promises will resolve when done, any other object will resolve instantly...
        // when they are all done this allDonePromise will resolve.
        allDonePromise = $.when.apply($, results);

        // Response 
        var response = $.Deferred();

        var responseData = {
            event: eventCode,
            params: params || null,
            cancelled: cancelled,
            handlerCount: handlers ? handlers.length : 0,
            results: results
        };

        allDonePromise.done(function () {
            response.resolve(responseData);
        });

        return $.extend(response.promise(), responseData);
    };

    /* events end */

    /* settings */

    $.cv.css.settings = $.cv.css.settings || {};

    /* settings end */

    /* statics */

    // Rendermodes as used by localget/setRenderMode
    $.cv.css.renderModes = { Tablet: 'Tablet', Phone: 'Phone' };

    /* statics END */

    /* public methods */

    $.cv.css.getSystemSettings = function (options) {
        // wire up loading for 'livesearch' and pagesize
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('setting/live', {
            parameters: {},
            success: function (msg) {
                var data = msg.data;
                if (data.length == 1) {

                    // set data to localsettings
                    $.cv.css.settings = data[0];
                    $.cv.css.localSetSettings(data[0]);
                }
                if (opts.success)
                    opts.success(data);
            }
        });
    };

    /* Crud operations */

    // send the service name and data to update (requires at least _objectKey and a property) to update
    $.cv.css.updateObject = function (options) {
        return $.cv.css.modifyObject(options, '-update');
    };

    // send the service name and data of object to delete (requires at least _objectKey)
    $.cv.css.deleteObject = function (options) {
        return $.cv.css.modifyObject(options, '-delete');
    };

    $.cv.css.modifyObject = function (options, operation) {
        var opts = $.extend({
            serviceName: '',
            success: function (msg) { },
            data: null
        }, options);
        return $.cv.ajax.call(opts.serviceName + operation, {
            parameters: opts.data,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };


    /* END Crud operations */

    /* Authentication */

    $.cv.css.login = function (username, password, options) {
        var opts = $.extend({
            accessThroughWeb: true,
            loadOrdersAfterLogin: false,
            triggerEvents: true,
            success: function () { }
        }, options);
        opts.isLogin = true;
        opts.loadCurrentUser = true;
        return $.cv.ajax.call('user/login', {
            parameters: {
                username: username,
                password: password,
                accessThroughWeb: opts.accessThroughWeb
            },
            success: function (msg) {
                var data = msg.data;
                // 0 or 15/16(force password change) allows a successful load of order details
                if (data.result == 0 || data.result == 15 || data.result == 16) {
                    if (opts.triggerEvents === true) {
                        $.cv.css.trigger($.cv.css.eventnames.loginLogoutSuccess, { data: data, opts: opts });
                    }
                } else {
                    // login didn't go through
                    if (opts.success) {
                        opts.success(data);
                    }
                }
            }
        });
    };

    $.cv.css.logout = function (options) {
        var opts = $.extend({
            loadOrdersAfterLogout: false,
            logoutRedirectUrl: "/",
            ociLogoutRedirectUrl: "/OCIOrderSubmit.aspx",
            ociLogoutParameters: { logout: true },
            triggerEvents: true,
            skipLoadUser: false,
            success: function () { }
        }, options);

        opts.loadOrdersAfterLogin = opts.loadOrdersAfterLogout;
        opts.isLogin = false;
        opts.loadCurrentUser = false;

        var resultProm = $.Deferred();
        var userRetrieved = $.Deferred();

        if (opts.triggerEvents === true) {
            $.cv.css.trigger($.cv.css.eventnames.loggingOut);
        }

        if (opts.skipLoadUser === true) {
            // Use what is in local storage as it should be set 
            // proper
            userRetrieved.resolve();
        } else {
            userRetrieved = $.cv.css.getCurrentUser({ triggerEvents: opts.triggerEvents });
        }

        userRetrieved.always(function (x) {
            var user = $.cv.css.localGetUser();

            if (user && user.IsOCISession) {
                $.cv.css.logoutOCI({
                    logoutRedirectUrl: opts.ociLogoutRedirectUrl,
                    logoutParameters: opts.ociLogoutParameters
                });
            } else {
                $.cv.ajax.call('user/logout', {
                    success: function(msg) {
                        var data = msg.data;
                        // Raise logout event
                        if (opts.triggerEvents === true) {
                            $.cv.css.trigger($.cv.css.eventnames.logout, data);
                            $.cv.css.trigger($.cv.css.eventnames.loginLogoutSuccess, { data: data, opts: opts });
                        }

                        if (opts.success) {
                            opts.success(data);
                        }
                    }
                }).done(function(response) {
                    resultProm.resolve(response);
                }).fail(function() {
                    resultProm.reject.apply(resultProm, Array.prototype.slice.call(arguments, 0));
                });
            }
        });

        return resultProm;
    };

    $.cv.css.logoutOCI = function (options) {
        var opts = $.extend({
            logoutRedirectUrl: "/OCIOrderSubmit.aspx",
            logoutParameters: { logout: true },
            success: function () { }
        }, options);
        $.cv.css.clearLocalStorage();
        $.cv.util.redirect(opts.logoutRedirectUrl, opts.logoutParameters, false);
    };

    /* Authentication END */

    /* User */

    // DEPRECIATED use $.cv.css.user.changeUserPassword instead
    $.cv.css.changeUserPassword = function (options) {
        if ($.cv.css.user && $.cv.css.user.changeUserPassword)
            return $.cv.css.user.changeUserPassword(options);
        else
            throw new Error("The cv.css.user.js file is required");
    };

    // DEPRECIATED use $.cv.css.user.retrieveUserPassword instead
    $.cv.css.retrieveUserPassword = function (options) {
        if ($.cv.css.user && $.cv.css.user.retrieveUserPassword)
            return $.cv.css.user.retrieveUserPassword(options);
        else
            throw new Error("The cv.css.user.js file is required");
    };

    // DEPRECIATED use $.cv.css.user.getCurrentUser instead
    $.cv.css.getCurrentUser = function (options) {
        if ($.cv.css.user && $.cv.css.user.getCurrentUser)
            return $.cv.css.user.getCurrentUser(options);
        else
            throw new Error("The cv.css.user.js file is required");
    };

    // DEPRECIATED use $.cv.css.user.setCurrentUser instead
    $.cv.css.setCurrentUser = function (options) {
        if ($.cv.css.user && $.cv.css.user.setCurrentUser)
            return $.cv.css.user.setCurrentUser(options);
        else
            throw new Error("The cv.css.user.js file is required");
    };


    /* User END */

    /* Account */

    $.cv.css.getCurrentAccount = function (options) {
        var opts = $.extend({
            triggerChange: true,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('customer/getcurrent', {
            parameters: {},
            success: function (msg) {
                if (!msg.errorMessage || msg.errorMessage.length == 0) {
                    // set local 
                    $.cv.css.localSetCurrentAccount(msg.data[0]);

                    // trigger changed as it may have changed
                    if (opts.triggerChange) {
                        $.cv.css.trigger($.cv.css.eventnames.accountChanged);
                    }

                    // raise success
                    if (opts.success) {
                        opts.success(msg);
                    }
                }
            }
        });
    };

    $.cv.css.setCurrentAccount = function (options) {
        var prom = $.Deferred(),
            opts;

        if ($.type(options) !== 'object') {
            (function () {
                var account = options;

                options = {
                    account: account
                };
            })();
        }

        opts = $.extend({
            account: '',
            triggerChange: true,
            success: function (msg) { }
        }, options);

        var acctSetProm = $.cv.ajax.call('customer/accountset', {
            parameters: {
                accountCode: opts.account
            }
        });

        acctSetProm.done(function (response) {
            if (!response.errorMessage || response.errorMessage.length == 0) {
                $.cv.css.getCurrentAccount({ triggerChange: opts.triggerChange })
                 .always(function () {
                     prom.resolve();
                 });
            } else {
                prom.resolve();
            }
        }).fail(function () {
            prom.resolve();
        });

        return prom.promise();
    };

    $.cv.css.accountSearch = function (options) {
        var opts = $.extend({
            searchString: '',
            skip: 0,
            take: 10,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('customer/accountsearch', {
            parameters: { searchString: opts.searchString, skip: opts.skip, take: opts.take, sort: "Code" },
            success: opts.success
        });
    };

    /* Account END */


    /* Category */

    // DEPRECIATED - use cv.css.category.js
    $.cv.css.categorySearch = function (options) {
        if ($.cv.css.category && $.cv.css.category.categorySearch)
            return $.cv.css.category.categorySearch(options);
    };

    // DEPRECIATED - use cv.css.category.js
    $.cv.css.categoryProducts = function (options) {
        if ($.cv.css.category && $.cv.css.category.categoryProducts)
            return $.cv.css.category.categoryProducts(options);
    };

    /* Category END */


    /* Order */

    $.cv.css.orderSearch = function (options) {
        var opts = $.extend({
            liveSearch: false,
            startorderdate: '',
            endorderdate: '',
            onlySearchCurrentUser: true,
            forwardOrderSearch: false,
            completedOrders: false,
            status: '',
            reference: '',
            productsearch: '',
            serialnum: '',
            invnum: '',
            startDeliveryDate: null,
            endDeliveryDate: null,
            forceCurrentCustomer: false,
            skip: 0,
            take: 10,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/search', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.canRepOverrideDiscount = function (discount, discountoverridethreshold, discountvalue, originalprice, pricevalue, priceoverridethreshold) {
        var canOverride = true;
        //check discountoverride by adding discount + discountoverridethreshold and ensure value entered is less than or equal to discountvalue
        if (discountvalue !== null && discountvalue !== 0) {
            if (discount !== null && discountvalue !== 0) {
                if (discountvalue > (discount + discountoverridethreshold))
                    canOverride = false;
            }
        }
        if (pricevalue !== null && pricevalue !== originalprice) {
            //check priceoverride by calculating pricevalue as percentage of originalprice if percentage is less than or equal to priceoverridethreshold
            var pricePercent = 1 - (pricevalue / originalprice);
            if (pricevalue > -1 && priceoverridethreshold != undefined && (priceoverridethreshold === 0 || pricePercent > (priceoverridethreshold / 100)))
                canOverride = false;
        }

        return canOverride;
    };

    $.cv.css.updateCurrentOrderLine = function (options) {
        var prom = $.Deferred();
        var opts = $.extend({}, $.cv.css.updateCurrentOrderLine.optionDefaults, options);

        var updateProm = $.cv.ajax.call('orders/updatecurrentorderline', {
            parameters: opts,
            success: function (msg) { }
        });

        updateProm.done(function (msg) {
            var result = [];
            var lines = [];
            if (msg.data) {
                var data = msg.data;
                if (data.message == "") {
                    result.push("true");
                } else {
                    result.push("false");
                }
                if (data.result.length > 0) {
                    lines.push(data.result[0]);
                }
            }
            $.cv.css.localUpdateCurrentOrderLines(lines);
            prom.resolve({ data: result });
            // get order again
            var p1 = $.cv.css.getCurrentOrder();
            p1.always(function () {
                // raise success once details reloaded
                if (opts.success)
                    opts.success(msg);
                $.cv.css.trigger($.cv.css.eventnames.orderChanged);
            });
        }).fail(function () {
            prom.resolve();
        });
        return prom.promise();
    };

    $.cv.css.updateCurrentOrderLine.optionDefaults = {
        sequence: null,
        quantity: null,
        price: "-1.0", // price override
        discount: "-1.0", // disc override
        deliveryDate: null,
        note: "",
        costCentreCode: "",
        nonContractReason: "",
        success: $.noop
    };

    /**
     * Adds a product to the users current order. Throttles calls so that 
     * multiple calls within a short period of time (opts.refreshOrderTimeout)
     * will be sent as a batch to orders/addlinetocurrent via call to 
     * $.cv.css.orders.addToCurrentOrderBulk()
     *
     * Returns: A jQuery Primise. The resolved object structure is:
     * {
     *    data: {
     *        editOrderOk: true, // N.B. Lowercase "k" in ok
     *        message: '',
     *        newLineSeq: 6,
     *        result : [
     *           // Pronto Sales Order Line fields here. One record only
     *           // contains the line after it was added
     *        ]
     *    }
     * }
     *
     * N.B. you should check the "editOrderOk" variable to determine whether
     * the line was added successfully
    **/
    $.cv.css.addToCurrentOrder = function (options) {
        var selCampaign = $.cv.css.localGetSelectedCampaign();

        // CampaignCode default
        $.cv.css.addToCurrentOrder.optionDefaults.campaignCode = (selCampaign ? selCampaign : '');

        var opts = $.extend({}, $.cv.css.addToCurrentOrder.optionDefaults, options);

        $.cv.css.temp = $.cv.css.temp || {};

        // Set up batch collection
        if ($.cv.css.temp.batchAddToCartData == null)
            $.cv.css.temp.batchAddToCartData = [];
        if ($.cv.css.temp.batchAddToCartDeferred == null)
            $.cv.css.temp.batchAddToCartDeferred = [];

        // Trigger event to indicate items changing
        $.cv.css.trigger($.cv.css.eventnames.cartItemsChanging);

        var productParams = {
            productCode: opts.productCode,
            quantity: opts.quantity,
            campaignCode: opts.campaignCode,
            price: opts.price,
            discount: opts.discount,
            notes: opts.notes,
            noteIsExtendedLineDescription: opts.noteIsExtendedLineDescription,
            attributes: opts.attributes,
            costCentre: opts.costCentre,
            stampData: opts.stampData,
            stampProofData: opts.stampProofData
        };

        $.cv.css.temp.batchAddToCartData.push(productParams);

        var done = $.Deferred();

        // Clear the timeout if another call has come in
        if ($.cv.css.temp.tOut != null) {
            clearTimeout($.cv.css.temp.tOut);
        }

        // Waiting X seconds for another request to add a line if none come through, update the order
        $.cv.css.temp.tOut = setTimeout(function () {
            // Grab snapshot of data and deferreds
            var deferredSet = $.cv.css.temp.batchAddToCartDeferred;
            $.cv.css.temp.batchAddToCartDeferred = [];
            var batchSet = $.cv.css.temp.batchAddToCartData;
            $.cv.css.temp.batchAddToCartData = [];

            // Execute batch of add lines...
            $.cv.css.orders.addToCurrentOrderBulk({
                batchData: batchSet,
                triggerGetLines: opts.triggerGetLines
            }).done(function(msg) {
                $.cv.css.trigger($.cv.css.eventnames.cartItemsChanged);

                // Trigger success function and deferred object resolve for all batched calls...
                for (var i = 0; i < msg.data.length; i++) {
                    (function() {
                        var batchItemResponse = { data: msg.data[i], errorMessage: msg.errorMessage[i] };

                        // Call: Success()
                        if (deferredSet[i]._addToCurrentOrderOptions &&
                            deferredSet[i]._addToCurrentOrderOptions.success) {
                            deferredSet[i]._addToCurrentOrderOptions.success(batchItemResponse);
                        }

                        // Resolve Deferred.
                        deferredSet[i].resolve(batchItemResponse);
                    })();
                }

                // Call batchSuccess on completion of the batch
                // Note: you have to pass the handler trough to the LAST call in the batc
                // but just adding it to all of them should not be an issue. It will only be
                // called once!
                if (opts.batchSuccess) {
                    opts.batchSuccess(msg, batchSet);
                }
            });
        }, opts.refreshOrderTimeout);

        // Attach options to deferred object so that we can call success function if present
        done._addToCurrentOrderOptions = opts;

        $.cv.css.temp.batchAddToCartDeferred.push(done);

        return done.promise();
    };

    $.cv.css.addToCurrentOrder.optionDefaults = {
        productCode: '',
        quantity: 1,
        price: -1, // price override
        discount: -1, // disc override
        campaignCode: '', // set on first call.
        notes: '',
        costCentre: '',
        noteIsExtendedLineDescription: false,
        attributes: null,
        stampData: null,
        stampProofData: null,
        refreshOrderTimeout: 1, // timeout before the whole order is reloaded
        triggerGetLines: false,
        success: function (response) { },
        batchSuccess: function (response) { }
    };

    $.cv.css.getCurrentOrder = function (options) {
        var opts = $.extend({
            success: $.noop
        }, options);
        if (!$.cv.css.received400) {
            return $.cv.ajax.call('orders/current', {
                parameters: opts, // << variable undeclared args here, don't change.
                success: function (msg) {
                    // Set data in current order
                    $.cv.css.localSetCurrentOrder(msg.data[0]);

                    if (opts.success) {
                        opts.success(msg);
                    }
                }
            });
        } else {
            var d = $.Deferred();
            $.cv.css.localSetCurrentOrder([]);
            d.resolve();
            return d.promise();
        }
    };

    // DEPRECIATED - use $.cv.css.orders.updateCurrentOrder
    $.cv.css.updateCurrentOrder = function (options) {
        // Extract non-data options.
        var triggerOrderChangedEvent = true,
            triggerOrderReload = true;

        // If a value was sent through for these two options we need
        // to cache them in the variables above and delete them so that they
        // are not send to the service 
        if ($.cv.util.hasValue(options.triggerOrderChangedEvent)) {
            triggerOrderChangedEvent = options.triggerOrderChangedEvent;
            delete options.triggerOrderChangedEvent;
        }

        if ($.cv.util.hasValue(options.triggerOrderReload)) {
            triggerOrderReload = options.triggerOrderReload;
            delete options.triggerOrderReload;
        }

        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/current-update', {
            parameters: opts,

            success: function (msg) {
                if (triggerOrderReload === true) {
                    // Reload
                    $.cv.css.getCurrentOrder().done(function () {
                        if (opts.success) {
                            opts.success(msg);
                        }

                        // Notify
                        if (triggerOrderChangedEvent === true) {
                            $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                        }
                    });
                } else {
                    // Don't Reload
                    if (opts.success) {
                        opts.success(msg);
                    }

                    // Notify
                    if (triggerOrderChangedEvent === true) {
                        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                    }
                }
            }
        });
    };

    $.cv.css.getOrder = function (options) {
        var opts = $.extend({
            orderNo: '',
            isFromOrderSearch: true, // See Comment on underlying method for more information
            liveOrder: $.cv.css.settings.EnableLiveOrderEnquiryOffline !== undefined ? $.cv.css.settings.EnableLiveOrderEnquiryOffline : false,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call((opts.liveOrder ? 'orders/getorderlive' : 'orders/getorder'), {
            parameters: {
                orderNo: opts.orderNo.toString(),
                isFromOrderSearch: opts.isFromOrderSearch
            },
            success: function (msg) {
                if (opts.success) {
                    opts.success(msg);
                }
            }
        });
    };

    $.cv.css.getOrderLines = function (options) {
        var opts = $.extend({
            orderNo: '',
            suffix: '',
            isFromOrderSearch: true, // See Comment on underlying method for more information
            liveOrder: $.cv.css.settings.EnableLiveOrderEnquiryOffline !== undefined ? $.cv.css.settings.EnableLiveOrderEnquiryOffline : false,
            success: function (msg) { }
        }, options);

        // local orderline/getlines needs the suffix appended to the order number
        opts.orderNo = opts.liveOrder ? opts.orderNo : opts.orderNo.toString() + opts.suffix;
        opts.suffix = opts.liveOrder ? opts.suffix : "";

        return $.cv.ajax.call((opts.liveOrder ? 'orderline/getlineslive' : 'orderline/getlines'), {
            parameters: {
                orderNo: opts.orderNo.toString(),
                suffix: opts.suffix,
                isFromOrderSearch: opts.isFromOrderSearch
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };


    $.cv.css.setOrderTerm = function (options) {
        var opts = $.extend({
            orderterm: '',
            _objectKey: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/setorderterm', {
            parameters: { _objectKey: opts._objectKey, orderterm: opts.orderterm },
            success: function (msg) {
                // reload local order
                $.cv.css.getCurrentOrder();
                $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                if (opts.success)
                    opts.success(msg);

            }
        });
    };

    $.cv.css.submitOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/submit', {
            parameters: { _objectKey: opts._objectKey },
            success: function (msg) {
                // Trigger order submitted, reload local order
                $.cv.css.trigger($.cv.css.eventnames.orderSubmitted);

                $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines()).then(function () {
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });

                if (opts.success) {
                    opts.success(msg);
                }
            }
        });
    };

    $.cv.css._currentOrderLinesResponseCleanupConverter = function (response) {
        var result = jQuery.parseJSON(response);
        var data = result.data;

        if (data.length > 0) {
            // the line SolLIneDescription field is returned in Description
            // in all cases but it can be truncated so we will grab it off the
            // product record and assign it over the top to avoid the truncation.
            _.each(data, function (line) {
                if (line.Description &&
                    line.Product &&
                    line.Product[0] &&
                    line.Product[0].Description) {
                    line.Description = line.Product[0].Description;
                }
            });
        }

        return result;
    };

    $.cv.css.getCurrentOrderLines = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        if (!$.cv.css.received400) {
            return $.cv.ajax.call('orderline/currentorderlines', {
                parameters: {},
                success: function (msg) {
                    // set data in currenot order
                    $.cv.css.localSetCurrentOrderLines(msg.data);
                    if (opts.success)
                        opts.success(msg);
                },
                converters: {
                    "text json": $.cv.css._currentOrderLinesResponseCleanupConverter
                }
            });
        } else {
            var d = $.Deferred();
            $.cv.css.localSetCurrentOrderLines([]);
            d.resolve();
            return d.promise();
        }
    };

    $.cv.css.deleteCurrentOrderLine = function (options) {
        var opts = $.extend({
            seq: null,
            triggerOrderRefresh: true,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/deletecurrentorderline', {
            parameters: { seq: opts.seq },
            success: function (msg) {
                if (opts.triggerOrderRefresh === true) {
                    // reload order as totals would change
                    var p1 = $.cv.css.getCurrentOrder();
                    var p2 = $.cv.css.getCurrentOrderLines();
                    $.when(p1, p2).always(function () {
                        // raise success once details reloaded
                        if (opts.success)
                            opts.success(msg);
                        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                    });
                }
            }
        });
    };

    /* Order END */

    /* Product */

    // DEPRECIATED - use cv.css.product.js
    $.cv.css.attributeproduct = function (options) {
        if ($.cv.css.product && $.cv.css.product.attributeproduct)
            return $.cv.css.product.attributeproduct(options);
    };

    // DEPRECIATED - use cv.css.product.js
    $.cv.css.productSearch = function (options) {
        if ($.cv.css.product && $.cv.css.product.productSearch)
            return $.cv.css.product.productSearch(options);
    };

    // DEPRECIATED - use cv.css.product.js
    $.cv.css.productSearchFastAdd = function (options) {
        if ($.cv.css.product && $.cv.css.product.productSearchFastAdd)
            return $.cv.css.product.productSearchFastAdd(options);
    };

    // DEPRECIATED - use cv.css.product.js
    $.cv.css.getProductDetail = function (options) {
        if ($.cv.css.product && $.cv.css.product.getProductDetail)
            return $.cv.css.product.getProductDetail(options);
    };

    /* Product END */

    /* User */

    $.cv.css.getOrderTerms = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('user/orderterms', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    }; // DEPRECIATED use $.cv.css.deliveryAddress.getDeliveryAddressesForCurrentUser instead
    $.cv.css.getDeliveryAddresses = function (options) {
        if ($.cv.css.deliveryAddress && $.cv.css.deliveryAddress.getDeliveryAddressesForCurrentUser)
            return $.cv.css.deliveryAddress.getDeliveryAddressesForCurrentUser(options);
    }; /* User END */

    /* Reporting */

    $.cv.css.getSalesData = function (options) {
        var opts = $.extend({
            keyData: '',
            keyType: '',
            recordTypes: '',
            tableName: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('salesdata/report', {
            parameters: { keyData: opts.keyData, keyType: opts.keyType, recordTypes: opts.recordTypes, tableName: opts.tableName },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    }; /* Reporting END */

    /* Campaigns */

    $.cv.css.currentCampaign = null;

    $.cv.css.setCurrentCampaign = function (campaign) {
        $.cv.css.localSetSelectedCampaign(campaign);
    };
    $.cv.css.campaignList = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('campaign/all', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };
    $.cv.css.campaignProductSearch = function (options) {
        var selCampaign = $.cv.css.localGetSelectedCampaign();
        var opts = $.extend({
            campaignCode: (selCampaign ? selCampaign : ''),
            searchString: '',
            skip: 0,
            take: 10,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('products/campaignproductsearch', {
            parameters: { campaignCode: opts.campaignCode, searchString: opts.searchString, skip: opts.skip, take: opts.take },
            success: opts.success,
            converters: {
                'text json': $.cv.css.product._productResponseCleanupConverter
            }
        });
    };

    /* Campaigns END */


    /* Favourites */

    $.cv.css.favouritesSearch = function (options) {
        var opts = $.extend({
            searchString: '',
            skip: 0,
            take: 10,
            sort: '',
            success: function (msg) { }
        }, options);

        var parameters = {
            searchString: opts.searchString,
            skip: opts.skip,
            take: opts.take
        };

        // Allow Disabling of client sort order
        if ($.cv.css.favouritesSearch.config.enableSorting === true) {
            parameters.sort = opts.sort;
        }

        // Globally configured Sort Order (Custom Sort Order)
        if ($.cv.css.favouritesSearch.config.customSortField != "DEFAULT") {
            parameters.sort = $.cv.css.favouritesSearch.config.customSortField + " " +
                              $.cv.css.favouritesSearch.config.customSortDirection;
        }

        // Globally configured Month Limit (based on products purchased in
        // the last N months)
        if ($.cv.css.favouritesSearch.config.monthLimit != "ALL") {
            parameters.monthLimit = $.cv.css.favouritesSearch.config.monthLimit;
        }

        return $.cv.ajax.call('products/favourites', {
            parameters: parameters,
            success: opts.success,
            converters: {
                'text json': $.cv.css.product._productResponseCleanupConverter
            }
        });
    };

    $.cv.css.favouritesSearch.config = {
        // We need to let the server do the sorting sometimes so we want to 
        // stop sending a sort from the client side.
        enableSorting: true,
        customSortField: null,
        customSortDirection: null
    };

    $.cv.css.favouritesPriorPurchasesSearch = function (options) {
        var opts = $.extend({
            searchString: '',
            skip: 0,
            take: 10,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('products/priorpurchases', {
            parameters: { searchString: opts.searchString, skip: opts.skip, take: opts.take },
            success: opts.success,
            converters: {
                'text json': $.cv.css.product._productResponseCleanupConverter
            }
        });
    };

    /* Favourites END */

    /* Search */

    // Predictive search
    $.cv.css.predictiveSearch = function (options) {
        var opts = $.extend({
            searchTerm: '',
            serviceName: '',
            resultSeperator: ',',
            skip: 0,
            take: 10,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call(opts.serviceName, {
            parameters: { searchTerm: opts.searchTerm, skip: opts.skip, take: opts.take, seperator: opts.resultSeperator },
            success: opts.success
        });
    };

    $.cv.css.getAvailableProductFeatureValues = function (options) {
        var opts = $.extend({
            mode: "",
            featureFilter: "",
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('search/getAvailableProductFeatureValues', {
            parameters: { mode: opts.mode, featureFilter: opts.featureFilter },
            success: opts.success
        });
    };

    $.cv.css.getAllProductFeatureValues = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('search/getAllProductFeatureValues', {
            parameters: {},
            success: opts.success
        });
    };
    
    $.cv.css.getProductFeaturesWithFilter = function (options) {
        var opts = $.extend({
            productFeatureName: '',
            filter: '',
            requireAdvancedSearch: false,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('search/getProductFeaturesWithFilter', {
            parameters: { productFeatureName: opts.productFeatureName, filter: opts.filter, requireAdvancedSearch: opts.requireAdvancedSearch },
            success: opts.success
        });
    }; /* Search END */

    /* Systable */

    $.cv.css.getSystableData = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('systable/data', {
            parameters: opts,
            success: function (response) {
                // Set Into local Storage
                if (response.errorMessage == null || response.errorMessage == '') {
                    $.cv.css.localSetSysTable(response.data);
                }

                if (opts.success) {
                    opts.success(response);
                }
            }
        });
    };

    /* Systable END */

    /* Template Related Utilities START */

    $.cv.css.getParsedTemplate = function (options) {
        var opts;

        if ($.type(options) === 'string') {
            var tmp = { templateName: options };
            options = tmp;
        }

        opts = $.extend({
            templateName: '',
            success: $.noop
        }, options);

        return $.cv.ajax.call('getParsedTemplate', {
            parameters: { templateName: opts.templateName },
            success: opts.success
        });
    };

    /* Template Related Utilities END */


    /* Captcha Generation and Pre-Post Validation */

    $.cv.css.generateCaptcha = function () {
        // First argument is success function if provided
        var success = $.noop;

        if (arguments[0] && typeof arguments[0] === 'function') {
            success = arguments[0];
        }

        return $.cv.ajax.call('captcha', {
            parameters: {
                createNewCaptcha: true,
                captchaTextToValidate: ''
            },
            success: success
        });
    };

    $.cv.css.validateCaptcha = function (captchaText) {
        // Second argument is success function if provided
        var success = $.noop;

        if (arguments[1] && typeof arguments[1] === 'function') {
            success = arguments[1];
        }

        return $.cv.ajax.call('captcha', {
            parameters: {
                createNewCaptcha: false,
                captchaTextToValidate: captchaText
            },
            success: success
        });
    };

    /* Captcha Generation and Pre-Post Validation */

    /**
     * Allows client side code to ping the server to determine
     * availability of connection to the server.
     **/
    $.cv.css.ping = function (options) {
        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('ping', {
            parameters: opts,
            success: opts.success,
            converters: {
                'text json': $.cv.util._booleanResponseConverter
            }
        });
    };

    /* public methods END */


    /* localStorage keys */

    $.cv.css.localStorageKeys = {
        currentAccount: 'currentAccount',
        currentUser: 'currentUser',
        renderMode: 'renderMode',
        pageSize: 'pageSize',
        currentOrder: 'currentOrder',
        currentOrderLines: 'currentOrderLines',
        updatedCurrentOrderLines: 'updatedCurrentOrderLines',
        selectedCampaign: 'selectedCampaign',
        currentCatalogue: 'currentCatalogue',
        userCatalogues: 'userCatalogues',
        recentlyViewedProducts: 'recentlyViewedProducts',
        settings: 'settings',
        userStocktake: 'userStocktake',
        orderSummaryMessages: 'orderSummaryMessages',
        sysTable: 'sysTable',
        currentMultiBranchSite: 'currentMultiBranchSite',
        defaultRepCustomerSet: 'defaultRepCustomerSet',
        multiBranchSites: 'multiBranchSites',
        lastLoggedInUserID: 'lastLoggedInUserID',
        selectedOrder: 'selectedOrder',
        currentDeliveryAddress: 'currentDeliveryAddress',
        currentPickupAddress: 'currentPickupAddress',

        // Mobile
        menuPageId: "menuPageId"
    };

    /* localStorage keys end*/

    /* sessionStorage keys */

    $.cv.css.sessionStorageKeys = {
        currentAccount: 'currentAccount',
        currentUser: 'currentUser',
        pageSize: 'pageSize',
        currentOrder: 'currentOrder',
        currentOrderLines: 'currentOrderLines',
        updatedCurrentOrderLines: 'updatedCurrentOrderLines',
        currentCatalogue: 'currentCatalogue',
        userCatalogues: 'userCatalogues',
        recentlyViewedProducts: 'recentlyViewedProducts',
        settings: 'settings',
        userStocktake: 'userStocktake',
        orderSummaryMessages: 'orderSummaryMessages',
        sysTable: 'sysTable',
        defaultRepCustomerSet: 'defaultRepCustomerSet',
        currentMultiBranchSite: 'currentMultiBranchSite',
        multiBranchSites: 'multiBranchSites',
        selectedOrder: 'selectedOrder',
        currentDeliveryAddress: 'currentDeliveryAddress',
        currentPickupAddress: 'currentPickupAddress',

        // Mobile
        menuPageId: "menuPageId"
    };

    /* sessionStorage keys end*/

    /* clear storage exlusion keys */

    $.cv.css.clearStorageExclusionKeys = {
        recentlyViewedProducts: 'recentlyViewedProducts',
        lastLoggedInUserID: 'lastLoggedInUserID'
    };

    /* clear storage exlusion keys end*/

    /* localStorage methods */


    /* helper methods */

    /* GP data helper methods */

    // GP data methods
    $.cv.css.maskGPData = function (data, mask, formatter) {
        var gpPercentMask = "93900";
        var gpAmountMask = "29800";
        if (mask) {
            if (data["GPPercentageExTotal"] != undefined)
                data["GPPercentageExTotal"] = (gpPercentMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPPercentageExTotal"]), "n2")).replace('.', '');
            if (data["GPAmountExTotal"] != undefined)
                data["GPAmountExTotal"] = (gpAmountMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPAmountExTotal"]), "n2")).replace('.', '');
            if (data["GPPercentage"] != undefined)
                data["GPPercentage"] = (gpPercentMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPPercentage"]), "n2")).replace('.', '');
            if (data["GPPercentageEx"] != undefined)
                data["GPPercentageEx"] = (gpPercentMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPPercentageEx"]), "n2")).replace('.', '');
            if (data["GPAmount"] != undefined)
                data["GPAmount"] = (gpAmountMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPAmount"]), "n2")).replace('.', '');
            if (data["GPAmountEx"] != undefined)
                data["GPAmountEx"] = (gpAmountMask + formatter.toString($.cv.css.cleanGPNumbers(data["GPAmountEx"]), "n2")).replace('.', '');
            if (data["ReplacementCost"] != undefined)
                data["ReplacementCost"] = (gpAmountMask + formatter.toString($.cv.css.cleanGPNumbers(data["ReplacementCost"]), "n2")).replace('.', '');
        }
        return data;
    };

    // N.B. used currently in text binder.
    $.cv.css.maskGPValue = function (fieldName, value, formatter) {
        var gpPercentMask = "93900",
            gpAmountMask = "29800";

        if (!value) return value;

        var cleaned = (formatter.toString($.cv.css.cleanGPNumbers(value), "n2")).replace('.', '');

        if (_.contains(['GPPercentageExTotal', 'GPPercentage', 'GPPercentageEx'], fieldName)) {
            return gpPercentMask + cleaned;
        }

        if (_.contains(['GPAmountExTotal', 'GPAmount', 'GPAmountEx', 'ReplacementCost'], fieldName)) {
            return gpAmountMask + cleaned;
        }

        return value;
    };

    $.cv.css.cleanGPNumbers = function (val) {
        if (typeof (val) == 'string') {
            // remove characters
            val = val.replace('&nbsp;', '');
            val = val.replace('%', '');
            val = val.replace('$', '');
            val = val.replace(' ', '');
            // set to float
            val = val.length > 0 ? parseFloat(val) : 0.00;
            // set to zero if not valid
            val = isNaN(val) ? 0.00 : val;
        }
        return val;
    };

    $.cv.css.GPPercentagePrompt = function (mask) {
        return mask ? 'SSNP: ' : 'GP Percentage (ex GST): ';
    };

    $.cv.css.GPAmountPrompt = function (mask) {
        return mask ? 'SSN: ' : 'GP Amount (ex GST): ';
    };

    $.cv.css.replacementCostPrompt = function (mask) {
        return mask ? 'SSNC: ' : 'Cost: ';
    };

    /* GP data helper methods END */

    $.cv.css.fixDateFields = function (fieldList, data) {
        $.each(fieldList, function (index, item) {
            if (data[item] && data[item].length > 0)
                data[item] = $.cv.util.toDate(data[item]);
        });
        return data;
    };

    /* local storage helper methods */

    // Function to evaluate once whether the browser supports localstorage.
    (function () {
        var supports = false;
        var supportsSessionStorage = false;

        try {
            localStorage.setItem('key', 'value');
            localStorage.removeItem('key');
            supports = true;
        } catch (e) { }

        try {
            sessionStorage.setItem('key', 'value');
            sessionStorage.removeItem('key');
            supportsSessionStorage = true;
        } catch (e) { }

        $.cv.css.browserSupportsLocalStorage = function () {
            return supports;
        };
        $.cv.css.browserSupportsSessionStorage = function () {
            return supportsSessionStorage;
        };
        $.cv.css.browserSupportsStorage = function () {
            return supportsSessionStorage && supports;
        };
    })();

    // Detect if IE Browser and if so version number (>=5 only)
    // This is to support some code in cv.util.js that used to use jquery browser field in older versions
    // to check for ie versions < 8... This replicates the functionality without 
    // dependency on jQuery.browser.
    // WARNING: doesn't detect IE 10, but we don't care as we only need to know whether
    // browser is < IE 8 so we can do slightly different things.
    // https://gist.github.com/padolsey/527683

    var browserInfo = null;

    (function () {
        var getResult = function () {
            var ie = (function () {
                var undef,
                    v = 3,
                    div = document.createElement('div'),
                    all = div.getElementsByTagName('i');

                while (
                    div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
                    all[0]
                );

                return v > 4 ? v : undef;
            }());

            return {
                isIE: ie !== undefined,
                version: ie !== undefined ? ie : null
            };
        };

        $.cv.css.browser = function () {
            if (browserInfo === null) {
                browserInfo = getResult();
            }

            return browserInfo;
        };
    })();

    $.cv.css.useLocal = function (key) {
        if ($.cv.css.sessionStorageKeys[key] == undefined)
            return true;
        else {
            return $.cv.css.browserSupportsLocalStorage();
        }
    };

    // memoryLocalStorage used when no localStorage or cookies available
    $.cv.css.memoryLocalStorage = {
        setItem: function (key, value) {
            this.values[key] = value;
        },
        getItem: function (key, value) {
            return this.values[key];
        },
        removeItem: function (key) {
            delete this.values[key];
        },
        values: {}
    };

    $.cv.css.setLocalStorage = function (key, value) {
        value = $.cv.css._prepareForLocalStorage(value);

        if ($.cv.css.browserSupportsStorage()) {
            try {
                //throw 'cookie';
                if ($.cv.css.useLocal(key))
                    localStorage.setItem(key, JSON.stringify(value));
                else
                    sessionStorage.setItem(key, JSON.stringify(value));
            } catch (e) {
                $.cv.css.memoryLocalStorage.setItem(key, JSON.stringify(value));
            }
        }
        else
            $.cv.css.memoryLocalStorage.setItem(key, JSON.stringify(value));
    };

    $.cv.css.getLocalStorage = function(key) {
        var data;
        if ($.cv.css.browserSupportsStorage()) {
            try {
                //throw 'cookie';
                if ($.cv.css.useLocal(key))
                    data = localStorage.getItem(key);
                else
                    data = sessionStorage.getItem(key);
            } catch (e) {
                data = $.cv.css.memoryLocalStorage.getItem(key);
            }
        } else
            data = $.cv.css.memoryLocalStorage.getItem(key);

        // if the storage item is not defined data comes back as a string = "undefined"
        if (data && data != "undefined") {
            var result = JSON.parse(data);

            if (result) {
                result = $.cv.css._fixFromLocalStorage(result);
            }

            return result;
        } else
            return null;
    };

    $.cv.css.removeLocalStorage = function(key) {
        if ($.cv.css.browserSupportsStorage()) {
            try {
                //throw 'cookie';
                if ($.cv.css.useLocal(key))
                    localStorage.removeItem(key);
                else
                    sessionStorage.removeItem(key);
            } catch (e) {
                $.cv.css.memoryLocalStorage.removeItem(key);
            }
        } else
            $.cv.css.memoryLocalStorage.removeItem(key);
    };


    var LOCAL_STORAGE_DATE_OBJECT_FIELD_NAME = "__cv_date_object";

    $.cv.css._prepareForLocalStorage = function (value) {
        // Convert special types to special object

        // Date
        if ($.type(value) === 'date') {
            var dateStruct = {
                // Date
                year: value.getFullYear(),
                month: value.getMonth(),
                date: value.getDate(),

                // Time
                hour: value.getHours(),
                minutes: value.getMinutes(),
                seconds: value.getSeconds(),
                milliseconds: value.getMilliseconds()
            };

            var result = {};
            result[LOCAL_STORAGE_DATE_OBJECT_FIELD_NAME] = dateStruct;

            return result;
        }

        // Iterate Object/array
        if ($.type(value) === 'array' || $.type(value) === 'object') {
            // Clone object/array at this level. (i.e. done recursive)
            if ($.type(value) === 'array') {
                value = value.slice(0); // This is generally (most browsers) the quickest way to clone an array.
            }

            if ($.type(value) === 'object') {
                value = $.extend({}, value); // Easiest way! :o)
            }

            $.each(value, function (i, item) {
                value[i] = $.cv.css._prepareForLocalStorage(item);
            });
        }

        return value;
    };

    $.cv.css._fixFromLocalStorage = function (value) {
        // Reconstruct special types
        if (value == null)
            return null;

        // Date
        if (value[LOCAL_STORAGE_DATE_OBJECT_FIELD_NAME] != null) {
            var obj = value[LOCAL_STORAGE_DATE_OBJECT_FIELD_NAME];

            return new Date(obj.year, obj.month, obj.date
                  , obj.hour, obj.minutes, obj.seconds, obj.milliseconds);
        }

        // Iterate Object/array
        if ($.type(value) === 'array' || $.type(value) === 'object') {
            $.each(value, function (i, item) {
                value[i] = $.cv.css._fixFromLocalStorage(item);
            });
        }

        return value;
    };

    $.cv.css.clearLocalStorage = function () {
        var storageKeys = {};
        _.extend(storageKeys, $.cv.css.localStorageKeys, $.cv.css.sessionStorageKeys);
        $.each(storageKeys, function (key, value) {
            if ($.cv.css.clearStorageExclusionKeys[key] == undefined)
                $.cv.css.removeLocalStorage(key);
        });
    };

    /* local storage helper methods END */


    /* helper methods END */

    $.cv.css.localGetDefaultRepCustomerAssigned = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.defaultRepCustomerSet);
    };

    $.cv.css.localSetDefaultRepCustomerAssigned = function (isSet) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.defaultRepCustomerSet, isSet);
    };

    $.cv.css.localGetSysTable = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.sysTable);
    };

    $.cv.css.localSetSysTable = function (sysTableData) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.sysTable, sysTableData);
    };

    $.cv.css.localGetSettings = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.settings);
    };

    $.cv.css.localSetSettings = function (settingsForSite) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.settings, settingsForSite);
    };

    $.cv.css.localGetSelectedCampaign = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.selectedCampaign);
    };

    $.cv.css.localSetSelectedCampaign = function (campaign) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.selectedCampaign, campaign);
    };

    $.cv.css.localGetCurrentOrderLines = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
    };

    $.cv.css.localUpdateCurrentOrderLines = function (lines) {
        if (lines.length > 0) {
            var currentOrderLines = $.cv.css.localGetCurrentOrderLines();
            if (currentOrderLines && currentOrderLines != null && currentOrderLines.length != 0) {
                $.each(lines, function (idx, line) {
                    if (_.filter(currentOrderLines, function (item) { return line.LineSeq.toString() == item.LineSeq.toString(); }).length > 0) {
                        currentOrderLines = _.filter(currentOrderLines, function (item) { return line.LineSeq.toString() != item.LineSeq.toString(); });
                        currentOrderLines = _.union(currentOrderLines, line);
                    } else {
                        currentOrderLines = _.union(currentOrderLines, line);
                    }
                });
            } else {
                currentOrderLines = lines;
            }
            currentOrderLines = _.sortBy(currentOrderLines, function (item) { return item.LineSeq; });
            $.cv.css.localSetCurrentOrderLines(currentOrderLines);
        }
    };

    $.cv.css.localSetCurrentOrderLines = function (order) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentOrderLines, order);
    };

    $.cv.css.localGetUpdatedCurrentOrderLines = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.updatedCurrentOrderLines);
    };

    $.cv.css.localSetUpdatedCurrentOrderLines = function (lines) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.updatedCurrentOrderLines, lines);
    };

    $.cv.css.localRemoveUpdatedCurrentOrderLines = function () {
        $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.updatedCurrentOrderLines);
    };

    // Current Order ----------------------------------------------------------
    $.cv.css.localGetCurrentOrder = function (loadIfNull) {
        var d1 = $.Deferred();
        loadIfNull = typeof loadIfNull !== 'undefined' ? loadIfNull : false;
        var o = $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentOrder);

        if (o != null || !loadIfNull) {
            return o;
        } else {
            if (!$.cv.css.loadingPromiseObjectExists($.cv.css.localStorageKeys.currentOrder)) {
                d1 = $.cv.css.getCurrentOrder();
                $.cv.css.addRemoveLoadingPromiseObjects(true, $.cv.css.localStorageKeys.currentOrder, d1);
                $.when(d1).done(function () {
                    $.cv.css.addRemoveLoadingPromiseObjects(false, $.cv.css.localStorageKeys.currentOrder);
                });
                return d1;
            } else {
                return $.cv.css.dataLoadingPromiseObjects[$.cv.css.localStorageKeys.currentOrder];
            }
        }
    };

    $.cv.css.localSetCurrentOrder = function (order) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentOrder, order);
    };

    // Selected Order ---------------------------------------------------------
    // A "selected" order is one that is not your current order but
    // that you might need to do work on, like delete a line or edit a qty
    // if you are approving the order. It is a storage place for a working
    // non-current order in other words
    $.cv.css.localGetSelectedOrder = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.selectedOrder);
    };

    $.cv.css.localSetSelectedOrder = function (order) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.selectedOrder, order);
    };

    // Current Delivery Address ------------------------------------------------
    $.cv.css.localGetcurrentDeliveryAddress = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentDeliveryAddress);
    };

    $.cv.css.localSetcurrentDeliveryAddress = function (address) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentDeliveryAddress, address);
    };

    $.cv.css.localGetcurrentPickupAddress = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentPickupAddress);
    };

    $.cv.css.localSetcurrentPickupAddress = function (address) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentPickupAddress, address);
    };

    // Current Account --------------------------------------------------------
    $.cv.css.localGetCurrentAccount = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentAccount);
    };

    $.cv.css.localSetCurrentAccount = function (account) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentAccount, account);
    };

    $.cv.css.localGetUser = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.currentUser);
    };

    $.cv.css.localSetUser = function (user) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.currentUser, user);
    };

    $.cv.css.localGetLastLoggedInUserID = function () {
        var result = $.cv.css.getLocalStorage($.cv.css.localStorageKeys.lastLoggedInUserID);

        // Cleanup: empty for null and undefined or for any guest user account
        if (result == null || result.indexOf('guestuser') == 0) {
            result = '';
        }

        return result;
    };

    $.cv.css.localSetLastLoggedInUserID = function (userID) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.lastLoggedInUserID, userID);
    };

    // Set the render mode in local storage
    $.cv.css.localSetRenderMode = function (toRenderFor) {
        var myRender = toRenderFor;
        // calculate mode if auto to Tablet if width > 480 pixels
        if (toRenderFor === 'Auto')
            myRender = $(window).width() > 480 ? $.cv.css.renderModes.Tablet : $.cv.css.renderModes.Phone;

        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.renderMode, myRender);
    };

    // get render mode (Tablet/Phone)
    $.cv.css.localGetRenderMode = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.renderMode);
    };

    $.cv.css.localGetMenuPageId = function () {
        var menuPageId = $.cv.css.getLocalStorage($.cv.css.localStorageKeys.menuPageId);
        if (menuPageId != null) {
            return menuPageId;
        }

        return "mainPage"; // this is normally what the MAF main page is set to!
    };

    $.cv.css.localSetMenuPageId = function (id) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.menuPageId, id);
    };

    // get page size
    $.cv.css.localGetPageSize = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.pageSize);
    };

    // set page size
    $.cv.css.localSetPageSize = function (size) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.pageSize, size);
    };

    $.cv.css.localGetUserStocktake = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.userStocktake);
    };

    $.cv.css.localSetUserStocktake = function (stocktake) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.userStocktake, stocktake);
    };

    /* order summary messages */

    $.cv.css.localGetOrderSummaryMessages = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.orderSummaryMessages);
    };

    $.cv.css.localSetOrderSummaryMessages = function (messages) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.orderSummaryMessages, messages);
    };
    
    /* localStorage methods END */

    // This just allows unification of handling something that returns a 
    // promise or a value by wrapping it in a promise itself.
    $.cv.css.unifyPromiseOrValue = function (valueOrPromise, success, failure) {
        var response = $.Deferred(),
            isDeferred = false;

        // Promise?
        if ($.cv.util.hasValue(valueOrPromise) &&
            // ensure its a jQuery promise
            valueOrPromise.state &&
            valueOrPromise.then &&
            valueOrPromise.done &&
            valueOrPromise.fail &&
            valueOrPromise.always)
        {
            isDeferred = true;

            valueOrPromise.done(function (r) {
                success && success(r);
                response.resolve(r);
            }).fail(function (r) {
                failure && failure(r);
                response.reject(r);
            });
        }

        // Non-Promise
        if (!isDeferred) {
            success && success(valueOrPromise);
            response.resolve(valueOrPromise);
        }

        return response.promise();
    };
})(jQuery);

$(function () {
    // Global event binding for localstorage refreshing
    var refreshOrderData = function (options) {
        var opts = $.extend({
            triggerEvents: true
        }, options);

        var orderRefreshed = $.Deferred();

        // Need to check if the user logging in is a Rep
        // if so their current account may be empty so get current order and get current order lines will fail
        var user = $.cv.css.localGetUser();

        if ((user && user.IsUserSalesRep) || ($.cv.mobile && $.cv.mobile.offline)) {
            // only empty the local storage if the rep does not have a current account, which will be the case after they select an account from the account select widget
            if ($.cv.css.localGetCurrentAccount() != null) {
                orderRefreshed = $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines());
            } else {
                // if rep user, clear local storage values that may have been set by another logged in user
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrder);
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
                $.cv.css.localRemoveUpdatedCurrentOrderLines();
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.userCatalogues);

                orderRefreshed.resolve(null, null);
            }
        } else {
            orderRefreshed = $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines());
        }

        // Trigger local orderchanged - this will notify widgets
        orderRefreshed.done(function () {
            if (opts.triggerEvents === true) {
                $.cv.css.trigger($.cv.css.eventnames.localOrderChanged);
            }
        });

        return orderRefreshed.promise();
    };

    var loginLogoutSuccess = function (data) {
        data = data || {};
        var opts = data.opts || {};

        var haveLatestOrder = $.Deferred();
        var haveUser = $.Deferred();
        var haveSysTableData = $.Deferred();

        $.cv.css.bind($.cv.css.eventnames.localOrderChanged, function () {
            haveLatestOrder.resolve();
        });

        // Ensure user loaded if required
        if (opts.loadCurrentUser) {
            haveUser = $.cv.css.getCurrentUser();
        } else {
            haveUser.resolve();
        }

        // Ensure SysTable Data if necessary
        if (opts.isLogin) {
            haveSysTableData = $.cv.css.getSystableData();
        } else {
            haveSysTableData.resolve();
        }

        // wait on these deferred items then run triggers
        $.when(haveUser, haveSysTableData).always(function () {
            var accountReset = $.Deferred();
            var user = $.cv.css.localGetUser();

            if (user && !user.IsUserSalesRep && opts.isLogin) {
                accountReset = $.cv.css.getCurrentAccount({ triggerChange: false });
            } else {
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentAccount);
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrder);
                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
                accountReset.resolve();
            }

            var loadOrderPermitted = $.Deferred();

            // if non mobile, load after login and wait until local order has changed from
            // the global event binding userChanged
            accountReset.done(function () {
                if ($.cv.css.localGetCurrentAccount() && opts.loadOrdersAfterLogin) {
                    haveLatestOrder.done(function () {
                        loadOrderPermitted.resolve();
                    });
                } else {
                    loadOrderPermitted.resolve();
                }
            });

            loadOrderPermitted.done(function () {
                // raise login event return 0;
                if (opts.isLogin) {
                    $.cv.css.trigger($.cv.css.eventnames.login, data.data);
                } else if (opts.suppressRedirect !== true) {
                    if (opts.logoutRedirectUrl != undefined && opts.logoutRedirectUrl != "")
                        $.cv.util.redirect(opts.logoutRedirectUrl, {}, false);
                }
                if (opts.success)
                    opts.success(data);
            });
        });
    };

    var preOrderSubmit = function () {
        var preOrderSubmit = $.Deferred();

        preOrderSubmit.done(function () {
            $.cv.css.trigger($.cv.css.eventnames.preOrderSubmitComplete);
        });

        if (!_.isEmpty($.cv.css.preOrderSubmitPromiseObjects)) {
            $.cv.css.bind($.cv.css.eventnames.widgetPreOrderSubmitComplete, function () {
                var allResolved = true;
                $.each($.cv.css.preOrderSubmitPromiseObjects, function (idx, item) {
                    if (item.state() != "resolved")
                        allResolved = false;
                });

                if (allResolved)
                    preOrderSubmit.resolve();
            });
        } else {
            preOrderSubmit.resolve();
        }

        return preOrderSubmit.promise();
    };

    // order data localstorage refresh
    $.cv.css.bind($.cv.css.eventnames.refreshOrderData, refreshOrderData);
    $.cv.css.bind($.cv.css.eventnames.accountChanged, refreshOrderData);
    $.cv.css.bind($.cv.css.eventnames.userChanged, refreshOrderData);
    $.cv.css.bind($.cv.css.eventnames.loginLogoutSuccess, loginLogoutSuccess);
    $.cv.css.bind($.cv.css.eventnames.preOrderSubmit, preOrderSubmit);
});
;

;

(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.orders = $.cv.css.orders || {};

    /*
        Can be called in two ways

     */
    $.cv.css.orders.addToOrder = function (options) {
        var opts = $.extend({
            productCode: '',
            quantity: 0,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/AddToOrder', {
            parameters: {
                productCode: opts.productCode,
                quantity: opts.quantity
            },
            success: opts.success
        });
    };

    // getCurrentOrderForUser function 
    // Gets Current Order ProntoSalesOrder Recordset Type of current user, using setup default JSON Fieldgroup GetCurrentOrderForUser
    $.cv.css.orders.getCurrentOrderForUser = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/GetCurrentOrderForUser', {
            success: opts.success
        });
    };

    $.cv.css.orders.areAddressLinesOK = function (options) {
        var opts = $.extend({
            success: function (msg) { },
            addressLines: []
        }, options);

        var prom = $.cv.ajax.call('orders/AreAddressLinesOK', {
            parameters: {
                addressLines: opts.addressLines
            },
            success: function (results) {
                opts.success.call({
                    success: results.data.length === 0,
                    message: results.data
                });
            }
        });

        var def = $.Deferred();

        prom.done(function (results) {
            def.resolve({
                success: results.data.length === 0,
                message: results.data
            });
        });

        return def.promise();
    };

    $.cv.css.orders.getOrderCompleteFieldGroupData = function (options) {
        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/GetOrderCompleteFieldGroupData', {
            parameters: {},
            success: opts.success
        });
    };

    $.cv.css.orders.updateOrderCompleteFieldGroupData = function (options) {
        var opts = $.extend({
            newValues: {},
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/UpdateOrderCompleteFieldGroupData', {
            parameters: { newValues: opts.newValues },
            success: opts.success
        });
    };

    $.cv.css.orders.holdCurrentOrder = function (options) {
        var opts = $.extend({
            _objectKey: '',
            triggerOrderChanged: true,
            triggerOrderSubmitted: true,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/holdcurrentorder', {
            parameters: { _objectKey: opts._objectKey },
            success: function (msg) {
                // reload local order
                if (opts.triggerOrderSubmitted) {
                    $.cv.css.trigger($.cv.css.eventnames.orderSubmitted);
                }
                var p1 = $.cv.css.getCurrentOrder();
                var p2 = $.cv.css.getCurrentOrderLines();
                $.when(p1, p2).then(function () {
                    if (opts.triggerOrderChanged) {
                        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                    }
                });
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.orders.emptyCart = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/emptyCart', {
            parameters: {},
            success: function (msg) {
                // reload local order
                var p1 = $.cv.css.getCurrentOrder();
                var p2 = $.cv.css.getCurrentOrderLines();
                $.when(p1, p2).then(function () {
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });

                if (opts.success) {
                    opts.success(msg);
                }
            }
        });
    };

    $.cv.css.orders.updateCurrentOrder = function (options) {
        // Extract non-data options.
        var triggerOrderChangedEvent = true,
            triggerOrderReload = true;

        // If a value was sent through for these two options we need
        // to cache them in the variables above and delete them so that they
        // are not send to the service 
        if ($.cv.util.hasValue(options.triggerOrderChangedEvent)) {
            triggerOrderChangedEvent = options.triggerOrderChangedEvent;
            delete options.triggerOrderChangedEvent;
        }

        if ($.cv.util.hasValue(options.triggerOrderReload)) {
            triggerOrderReload = options.triggerOrderReload;
            delete options.triggerOrderReload;
        }

        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/current-update', {
            parameters: opts,

            success: function (msg) {
                if (triggerOrderReload === true) {
                    // Reload
                    $.cv.css.getCurrentOrder().done(function () {
                        if (opts.success) {
                            opts.success(msg);
                        }

                        // Notify
                        if (triggerOrderChangedEvent === true) {
                            $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                        }
                    });
                } else {
                    // Don't Reload
                    if (opts.success) {
                        opts.success(msg);
                    }

                    // Notify
                    if (triggerOrderChangedEvent === true) {
                        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                    }
                }
            }
        });
    };

    /**
     * Adds a product to the users current order
     *
     * Returns: A jQuery Primise. The resolved object structure is:
     * WARNING: each entry in data field array has the same structure
     * WARNING: each entry in data field array corresponds to the same index in errorMessages
     * WARNING: each data/errorMessage pair corresponds to one line added.
     * {
     *    data: [{
     *        editOrderOk: true, // N.B. Lowercase "k" in ok
     *        message: '',
     *        newLineSeq: 6,
     *        result : [
     *           // Pronto Sales Order Line fields here. One record only
     *           // contains the line after it was added
     *        ]
     *    }, 
     *    { ... }, 
     *    { ... },
     *    ...],
     *    errorMessage: [
     *      null, // N.B. can be null or a string value...
     *      '',
     *      "",
     *      ...
     *    ]
     * }
     *
     * N.B. you should check the "editOrderOk" variable to determine whether
     * the line was added successfully
    **/
    $.cv.css.orders.addToCurrentOrderBulk = function (options) {
        var opts = $.extend({
            batchData: [],
            triggerGetLines: false,
            success: function (response) { }
        }, options);

        return $.cv.ajax.call('orders/addlinetocurrent', {
            parameters: { batchData: opts.batchData },
            success: function (response) {
                var lines = [],
                    orderLoaded,
                    orderLinesLoaded = {}; // $.when() considers this as "resolved"

                // Call passed in Success function.
                if (opts.success) {
                    opts.success(response);
                }

                // Extract changed lines and update local storage currentOrderLines...
                if (response.data) {
                    var data = response.data;
                    $.each(data, function (idx, item) {
                        if (item.result.length > 0) {
                            lines.push(item.result[0]);
                        }
                    });
                }

                $.cv.css.localUpdateCurrentOrderLines(lines);

                // Call getCurrentOrder which will update the $.cv.css.localSetCurrentOrder object 
                // and trigger the 'orderChanged' event 
                orderLoaded = $.cv.css.getCurrentOrder();

                if (opts.triggerGetLines) {
                    orderLinesLoaded = $.cv.css.getCurrentOrderLines();
                }

                $.when(orderLoaded, orderLinesLoaded).always(function () {
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            }
        });
    };

    $.cv.css.orders.applyPaymentSurcharge = function (options) {
        var result = $.Deferred();
        var opts = $.extend({
            paymentMethod: 'CardAny',
            success: function (msg) { }
        }, options);
        $.cv.ajax.call('orders/applyPaymentSurcharge', {
            parameters: { paymentMethod: opts.paymentMethod },
            success: opts.success,
            error: opts.error,
            sessionTimeout: opts.sessionTimeout
        }).done(function (response) {
            $.when.apply($, $.cv.css.trigger($.cv.css.eventnames.refreshOrderData).results).done(function () {
                result.resolve(response);
            });
        });
        return result.promise();
    };

    $.cv.css.orders.removePaymentSurcharge = function (options) {
        var opts = $.extend({
            _objectKey: $.cv.css.localGetCurrentOrder()._objectKey,
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/removePaymentSurcharge', {
            parameters: opts,
            success: opts.success
        }).done(function (response) {
            $.when.apply($, $.cv.css.trigger($.cv.css.eventnames.refreshOrderData).results);
        });
    };

    $.cv.css.orders.updateCurrentOrderLineBulk = function (options) {
        var prom = $.Deferred();
        var opts = $.extend({
            batchData: [],
            triggerOrderRefresh: true,
            success: function (msg) { }
        }, options);

        // Extend passed in option from defaults so that we fill in default values that are not provided.
        for (var i in opts.batchData) {
            var newBatchDataItem = $.extend({}, $.cv.css.updateCurrentOrderLine.optionDefaults, opts.batchData[i]);
            opts.batchData[i] = newBatchDataItem;
        }

        var updateProm = $.cv.ajax.call('orders/updatecurrentorderline', {
            parameters: { batchData: opts.batchData },
            success: function (msg) { }
        });

        updateProm.done(function (msg) {
            var result = [];
            var lines = [];
            if (msg.data) {
                var data = msg.data;
                $.each(data, function (idx, item) {
                    if (item.message == "") {
                        result.push("true");
                    } else {
                        result.push("false");
                    }
                    if (item.result.length > 0) {
                        lines.push(item.result[0]);
                    }
                });
            }
            $.cv.css.localUpdateCurrentOrderLines(lines);
            prom.resolve({ data: result });
            if (opts.triggerOrderRefresh) {
                // get order again
                var p1 = $.cv.css.getCurrentOrder();
                p1.always(function () {
                    // raise success once details reloaded
                    if (opts.success) {
                        opts.success(msg);
                    }

                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            }
        }).fail(function () {
            prom.resolve();
        });
        return prom.promise();
    };

    $.cv.css.orders.updateOrderLine = function (options) {
        var opts = $.extend({}, $.cv.css.updateCurrentOrderLine.optionDefaults, options);

        return $.cv.ajax.call('orders/updateorderline', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.orders.deleteCurrentOrderLineBulk = function (options) {
        var opts = $.extend({
            batchData: [],
            triggerOrderRefresh: true,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/deletecurrentorderline', {
            parameters: { batchData: opts.batchData },
            success: function (msg) {
                if (opts.triggerOrderRefresh) {
                    // reload order as totals would change
                    var p1 = $.cv.css.getCurrentOrder();
                    var p2 = $.cv.css.getCurrentOrderLines();
                    $.when(p1, p2).always(function () {
                        // raise success once details reloaded
                        if (opts.success) {
                            opts.success(msg);
                        }

                        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                    });
                }
            }
        });
    };

    $.cv.css.orders.deleteOrderLine = function (options) {
        var opts = $.extend({
            _objectKey: null,
            seq: 0,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/deleteOrderLine', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.orders.determineCheckoutMessages = function (options) {
        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/determinecheckoutmessages', {
            parameters: options,
            success: function (msg) {
                if (opts.success) {
                    opts.success(msg);
                }
            }
        });
    };

    $.cv.css.orders.validateForCheckout = function (options) {
        var opts = $.extend({
            _objectKey: '',
            validationMode: 'checkout',
            linesToValidate: '',
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/validateforcheckout', {
            parameters: { _objectKey: opts._objectKey, validationMode: opts.validationMode, linesToValidate: opts.linesToValidate },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.orders.applyPromoCode = function (options) {
        var opts = $.extend({
            _objectKey: '',
            promoCode: '',
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/applypromocode', {
            parameters: { promoCode: opts.promoCode, _objectKey: opts._objectKey },
            success: function (msg) {
                // reload order as totals would change
                var p1 = $.cv.css.getCurrentOrder();
                var p2 = $.cv.css.getCurrentOrderLines();
                $.when(p1, p2).always(function () {
                    // raise success once details reloaded
                    if (opts.success)
                        opts.success(msg);
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            }
        });
    };

    $.cv.css.orders.removePromoCode = function (options) {
        var opts = $.extend({
            _objectKey: '',
            promoCode: '',
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('orders/removepromocode', {
            parameters: { promoCode: opts.promoCode, _objectKey: opts._objectKey },
            success: function (msg) {
                // reload order as totals would change
                var p1 = $.cv.css.getCurrentOrder();
                var p2 = $.cv.css.getCurrentOrderLines();
                $.when(p1, p2).always(function () {
                    // raise success once details reloaded
                    if (opts.success)
                        opts.success(msg);
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            }
        });
    };

    $.cv.css.orders.requestQuote = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/requestQuote', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.orders.submitQuote = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/submitQuote', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.orders.copySearchResultQuoteToCurrentOrder = function (options) {
        var opts = $.extend({
            soOrderNo: 0,
            soBoSuffix: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/copySearchResultQuoteToCurrentOrder', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };
    
    $.cv.css.orders.changeOrderAccount = function (options) {
        if ($.type(options) === "string") {
            var customerCode = options;

            options = {
                customerCode: customerCode
            };
        }

        var opts = $.extend({
            customerCode: '',
            confirmChangeCustomer: false,
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/changeOrderAccount', {
            parameters: {
                customerCode: opts.customerCode,
                confirmChangeCustomer: opts.confirmChangeCustomer
            },
            success: opts.success
        });
    };

    // Merge the lines on both accounts
    $.cv.css.orders.enhancedChangeOrderAccount = function (action, options) {
        var path = '';

        var opts = $.extend({
            oldCustomerCode: '',
            targetCustomerCode: ''
        }, options);

        var paramsToSend = {
            oldCustomerCode: opts.oldCustomerCode,
            targetCustomerCode: opts.targetCustomerCode
        };

        switch (action) {
            case "merge":
                path = "orders/mergeOrderToAccount";
                break;
            case "hold":
                path = "orders/holdOrderToAccount";
                paramsToSend.holdreference = opts.reference;
                break;
            case "delete":
                path = "orders/deleteOrderToAccount";
                break;
        }

        return $.cv.ajax.call(path, {
            parameters: paramsToSend,
            success: function (msg) {
                // set data in current order by triggering order changed
                var waiting = $.cv.css.getCurrentOrder();
                $.when(waiting).done(function () {
                    if (opts.success)
                        opts.success(msg);
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            }
        });
    };

    $.cv.css.orders.setCreateBackorderState = function (options) {
        if ($.type(options) === "boolean") {
            var yaynay = options;

            options = {
                createBackorder: yaynay
            };
        }

        var opts = $.extend({
            createBackorder: false
        }, options);

        return $.cv.ajax.call('orders/setCreateBackorderState', {
            parameters: {
                createBackorder: opts.createBackorder
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.orders.cancelQuoteOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('orders/cancelQuoteOrder', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
                $.cv.css.trigger($.cv.css.eventnames.refreshOrderData);
            }
        });
    };

    $.cv.css.orders.unsubmittedOrders = function (options) {
        var opts = $.extend({
            success: $.noop
        }, options);

        return $.cv.ajax.call('orders/unsubmittedOrders', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.orders.switchToOrder = function (options) {
        var opts = $.extend({
            reloadOrder: true,
            soOrderNo: ''
        }, options);

        var prom = $.Deferred();

        // Switch Orders
        var switchProm = $.cv.ajax.call('orders/switchToOrder', {
            parameters: opts
        });

        // Reload Orders
        switchProm.done(function () {
            if (opts.reloadOrder) {
                $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines())
                .always(function () {
                    prom.resolve();
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                });
            } else {
                prom.resolve();
            }
        }).fail(function () {
            prom.resolve();
        });;

        return prom.promise();
    };

    /*
        Changes to specified account and switches orders

        Note:   This is an abstraction around other dynamic service calls, it doesn't have a 
                corresponding service endpoint.

        Process:
        - triggers account change (silently) and loads new account (no one knows of account change yet!)
        - triggers switching to selected order (doesn't reload order yet!)
        - triggers accountChanged which triggers reloading of the order
        - once the order loading has finished (trigger now returns results from all handlers, which
          if promises are waited for resolved state) then we trigger orderChanged so that 
          widgets can update.
    */
    $.cv.css.orders.resumeOrder = function (options) {
        var opts = $.extend({
            customerCode: '',
            orderNo: ''
        }, options);

        var response = $.Deferred();

        // 1. change account
        var setAccountProm = $.cv.css.setCurrentAccount({ account: opts.customerCode, triggerChange: false });

        // 2. switch to order
        setAccountProm.done(function () {
            var switchProm = $.cv.css.orders.switchToOrder({ soOrderNo: opts.orderNo, reloadOrder: false });

            switchProm.done(function () {
                // 3. notify of Account Change - triggers order reload (silently)
                var accountChangedResponse = $.cv.css.trigger($.cv.css.eventnames.accountChanged);

                // Only trigger orderChanged when all handlers (that return promises anyway)
                // are resolved as otherwise we might refresh from stale local storage order.
                // In this case the accountChanged event triggers internal refresh of the orders 
                // which now returns a promises... When the order has reloaded
                // we can then trigger orderChanged and widgets can update themselves!
                $.when.apply($, accountChangedResponse.results).done(function () {
                    // 4. trigger order changed for widget updating...
                    $.when.apply($, $.cv.css.trigger($.cv.css.eventnames.orderChanged).results).done(function () {
                        response.resolve();
                    });
                });
            }).fail(function () {
                response.reject();
            });
        }).fail(function () {
            response.reject();
        });

        return response.promise();
    };

    $.cv.css.orders.setInvoiceReprint = function (options) {
        var opts = $.extend({}, $.cv.css.orders.setInvoiceReprint.optionDefaults, options);

        return $.cv.ajax.call('orders/setInvoiceReprint', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.orders.setInvoiceReprintBulk = function (options) {
        var opts = $.extend({
            batchData: [],
            triggerOrderRefresh: true,
            success: function (msg) { }
        }, options);

        // Extend passed in option from defaults so that we fill in default values that are not provided.
        for (var i in opts.batchData) {
            var newBatchDataItem = $.extend({}, $.cv.css.orders.setInvoiceReprint.optionDefaults, opts.batchData[i]);
            opts.batchData[i] = newBatchDataItem;
        }

        return $.cv.ajax.call('orders/setInvoiceReprint', {
            parameters: { batchData: opts.batchData },
            success: opts.success
        });
    };

    $.cv.css.orders.setInvoiceReprint.optionDefaults = {
        orderNo: "",
        suffix: "",
        reprintTo: "",
        expectedInvoiceNo: "",
        forceOrderCreation: false,
        ignoreCustomerChecks: false,
        outputFormat: "",
        success: $.noop
    };

})(jQuery);
;
/// <reference path="jquery-1.8.3.js" />

/**
 * Author: Chad Paynter
 * Date: 2013-03-06
 * Description: To get delivery options and set delivery address
 * Dependencies
 * - jQuery
 * - cv.css.js
**/

;
(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.deliveryAddress = $.cv.css.deliveryAddress || {};

    $.cv.css.deliveryAddress.getDeliveryAddressesForCurrentUser = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/deliveryaddressesforcurrentuser', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.getDeliveryAddressForCurrentOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/getdeliveryaddressforcurrentorder', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.setDeliveryAddressForCurrentOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/getdeliveryaddressforcurrentorder-update-validate', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.validateCurrentOrderDeliveryAddress = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/validateCurrentOrderDeliveryAddress', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.setDeliveryAddressFieldForCurrentOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/getdeliveryaddressforcurrentorder-update', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.updateDeliveryAddressForCurrentOrder = function (options) {
        var opts = $.extend({
            updatedAddressDetails: null,
            selectAddressMoniker: '',
            validateMissingFields: true,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/UpdateDeliveryAddressForCurrentOrder', {
            parameters: {
                updatedAddressDetails: opts.updatedAddressDetails,
                selectAddressMoniker: opts.selectAddressMoniker,
                validateMissingFields: opts.validateMissingFields
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    // Validates suburb and postcode to see whether the system considers
    // them a valid combination.
    // 
    // @suburb string - the suburb name to validate
    // @postcode string - the postcode to validate
    // @country string - the country to validate 
    //      Default: defaults to "Australia" if not set.
    // @returns bool - true if the combination is valid, false if not.
    $.cv.css.deliveryAddress.validateSuburbAndPostCode = function (options) {
        var opts = $.extend({
            suburb: '',
            postcode: '',
            country: '', // Server Defaults to "Australia" if not set!

            success: $.noop
        }, options);

        return $.cv.ajax.call('deliveryaddress/ValidateSuburbAndPostCode', {
            parameters: opts,

            success: opts.success,

            converters: {
                'text json': $.cv.util._booleanResponseConverter
            }
        });
    };

    $.cv.css.deliveryAddress.updateBillingAddressForCurrentOrder = function (options) {
        var opts = $.extend({
            updatedBillingAddressDetails: null,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/UpdateBillingAddressForCurrentOrder', {
            parameters: {
                updatedBillingAddressDetails: opts.updatedBillingAddressDetails
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.setDeliveryAddressByName = function (options) {
        var opts = $.extend({
            deliveryAddressName: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/SetDeliveryAddressByName', {
            parameters: {
                deliveryAddressName: opts.deliveryAddressName
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.setDeliveryAddressAttentionTo = function (options) {
        var opts = $.extend({
            name: null,
            phone: null,
            setToDefault: false,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/setDeliveryAddressAttentionTo', {
            parameters: {
                name: opts.name,
                phone: opts.phone,
                setToDefault: opts.setToDefault
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.deliveryAddress.saveAddress = function (options) {
        var opts = $.extend({
            saveForUser: false,
            saveDeliveryInstructions: false,
            address: null,
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('deliveryaddress/SaveAddress', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

})(jQuery);;
/// <reference path="jquery-1.8.3.js" />

/**
 * Author: Chad Paynter
 * Date: 2013-03-06
 * Description: To get freight options and set freight
 * Dependencies
 * - jQuery
 * - cv.css.js
**/

;
(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.freightCarrier = $.cv.css.freightCarrier || {};

    $.cv.css.freightCarrier.getFreightForCurrentOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('freightcarrier/getfreightforcurrentorder', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.freightCarrier.getFreightEstimateForCurrentOrder = function (options) {
        var opts = $.extend({
            postCode: '',
            countryCode: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('freightcarrier/getFreightEstimateForCurrentOrder', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.freightCarrier.setFreightForCurrentOrder = function (options) {
        var opts = $.extend({
            freightOptionIDs: [],
            warehouseCode: null,
            ownCarrierAccount: null,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('freightcarrier/setfreightforcurrentorder', {
            parameters: { freightOptionIDs: opts.freightOptionIDs, warehouseCode: opts.warehouseCode, ownCarrierAccount: opts.ownCarrierAccount },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
                //call getCurrentOrder which will update the $.cv.css.localSetCurrentOrder object and trigger the 'orderChanged' event 
                var p1 = $.cv.css.getCurrentOrder();
                var p2 = $.cv.css.getCurrentOrderLines();
                $.when(p1, p2).always(function (order) {
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged, order[0].data[0]);
                });
            }
        });
    };

    $.cv.css.freightCarrier.validateFreightForCurrentOrder = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('freightcarrier/validatefreightforcurrentorder', {
            parameters: { },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    }
})(jQuery);;
/// <reference path="jquery-1.8.3.js" />
/* * cv.css.paymentProcessor.js * Author: Chad Paynter * Date: 05/03/2013 * Description: cv.css plugin for calls to CSS dynamic services * Dependencies: * jquery - In Script folder or http://jquery.com/ * cv.ajax -  In Script folder cv.ajax.js * cv.util - In Script folder cv.util.js */
;
(function ($, undefined) {
    // Check we have everything we need
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.paymentProcessor = $.cv.css.paymentProcessor || {};

    $.cv.css.paymentProcessor.processPayment = function (options) {
        var opts = $.extend({
            paymentType: '',
            paymentInfo: {},
            isPaymentGateway: false,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('paymentprocessor/processpayment', {
            parameters: {
                paymentType: opts.paymentType,
                paymentInfo: opts.paymentInfo,
                isPaymentGateway: opts.isPaymentGateway
            },
            success: function (msg) {
                // If we have a successful payment then execute analytics script if present.
                if (msg.data && msg.data.PaymentSuccessful && msg.data.AdditionalResultData) {
                    var func = undefined;
                    // trigger submitted event
                    $.cv.css.trigger($.cv.css.eventnames.orderSubmitted);

                    for (var i in msg.data.AdditionalResultData) {
                        if (msg.data.AdditionalResultData[i].Key == 'googleAnalyticsEcommerceScript') {
                            func = msg.data.AdditionalResultData[i].Value;
                            break;
                        }
                    }

                    if (func && func.length > 0) {
                        // Should generate global cssGoogleAnalyticsPageTracker() function
                        try {
                            eval(func);

                            // Track order if tracker present
                            if (window.cssGoogleAnalyticsPageTracker) {
                                cssGoogleAnalytics_TrackOrder();
                            }
                        } catch (e) {
                            // Eat error where func is actually just a script
                            // block instead of a function to execute.
                        }
                    }
                }
                else {
                    $.cv.css.trigger($.cv.css.eventnames.orderChanged, true);
                }

                if (opts.success) {
                    opts.success(msg);
                }
            }
        });
    };

    $.cv.css.paymentProcessor.getPaymentOptionsForCurrentOrder = function (options) {
        var opts = $.extend({
            cardsOnly: false,
            isPaymentGateway: false,
            isAccountPayment: false,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('paymentprocessor/getpaymentoptions', {
            parameters: {
                cardsOnly: opts.cardsOnly,
                isPaymentGateway: opts.isPaymentGateway,
                isAccountPayment: opts.isAccountPayment
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.paymentProcessor.getPaymentProviderDetails = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('paymentprocessor/getPaymentProviderDetails', {
            parameters: {},
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.paymentProcessor.ewayCreateAccessCode = function (options) {
        var opts = $.extend({
            clearRememberedPaymentInfo: false,
            orderNoForAccessCode: 0, // Non-Deposit Scenario
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('paymentprocessor/ewayCreateAccessCode', {
            parameters: {
                clearRememberedPaymentInfo: opts.clearRememberedPaymentInfo,
                orderNoForAccessCode: opts.orderNoForAccessCode
            },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

})(jQuery);;
/// <reference path="jquery-1.8.3.js" />

/**
 * Author: Chad Paynter
 * Date: 2013-04-04
 * Description: To get delivery options and set delivery address
 * Dependencies
 * - jQuery
 * - cv.css.js
**/

;
(function($, undefined) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.user = $.cv.css.user || {};

    $.cv.css.user.registerUserFieldData = function(options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/registeruser', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    // called for public user registration
    $.cv.css.user.registerUser = function(options) {
        var opts = $.extend({
            setCustAndRole: false,
            sendEmail: false,
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/registeruser-create-validate', {
            parameters: opts,
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            }
        });
    };

    $.cv.css.user.changeUserPassword = function (options) {
        var opts = $.extend({
            username: '',
            newPassword: '',
            newPasswordConfirm: '',
            notifyEmail: '',
            success: function (msg) { },
            error: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/checkandchangepassword', {
            parameters: { username: opts.username, newPassword: opts.newPassword, newPasswordConfirm: opts.newPasswordConfirm, notifyEmail: opts.notifyEmail },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            },
            error: function (msg) {
                if (opts.error)
                    opts.error(msg.responseText ? msg.responseText : '');
            }
        });
    };

    $.cv.css.user.retrieveUserPassword = function (options) {
        var opts = $.extend({
            username: '',
            success: function (msg) { },
            error: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/retrievepassword', {
            parameters: { username: opts.username },
            success: function (msg) {
                if (opts.success)
                    opts.success(msg);
            },
            error: function (msg) {
                if (opts.error)
                    opts.error(msg.responseText ? msg.responseText : '');
            }
        });
    };
    
    $.cv.css.user.getCurrentUser = function (options) {
        var result = $.Deferred();

        var opts = $.extend({
            success: $.noop,
            triggerEvents: true
        }, options);

        var serviceCallOptions = {
            parameters: {},
            sucess: opts.success
        
        };

        if ($.cv.css.crm) {
            serviceCallOptions.converters = {
                'text json': $.cv.css.crm._usersCurrentCrmRecordCleanupConverter
            };
        }

        var userLoaded = $.cv.ajax.call('user/currentuser', serviceCallOptions);

        userLoaded.done(function (response) {
            // Successful Load... do some special stuff.
            if (!response.errorMessage || response.errorMessage.length == 0) {
                $.cv.css.localSetUser(response.data[0]);

                // add in user settings
                if (response.data[0].RecordsPerPage) {
                    $.cv.css.localSetPageSize(response.data[0].RecordsPerPage);
                }

                if (opts.triggerEvents === false) {
                    if (opts.success) {
                        opts.success(response);
                    }

                    result.resolve(response);
                } else {
                    $.cv.css.trigger($.cv.css.eventnames.userChanged, response.data[0])
                     .done(function () {
                         if (opts.success) {
                             opts.success(response);
                         }

                         result.resolve(response);
                     });
                }
            } else {
                // No additional work to do...
                result.resolve(response);
            }
        });

        return result.promise();
    };

    // Set user details for current user
    $.cv.css.user.setCurrentUser = function (options) {
        var opts = $.extend({
            success: function (msg) { },
            data: null,
            serviceName: 'user/currentuser',
            triggerUserChanged: true
        }, options);
        var existingSuccess = opts.success;
        opts.success = function (msg) {
            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                $.cv.css.localSetUser(msg.data[0]);
                if (opts.triggerUserChanged)
                    $.cv.css.trigger($.cv.css.eventnames.userChanged, msg.data[0]);
                if (existingSuccess)
                    existingSuccess(msg);
            }
        };
        return $.cv.css.updateObject(opts);
    };

    $.cv.css.user.setCurrentUserRecordsPerPage = function (recordsPerPage) {
        return $.cv.ajax.call('user/setCurrentUserRecordsPerPage', {
            parameters: {
                recordsPerPage: recordsPerPage
            },
            success: $.noop
        });
    };

    $.cv.css.user.getUserMaintenanceData = function (options) {
        var opts = $.extend({
            jsonFieldGroupName: "",
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/currentuserdetails', {
            parameters: { jsonFieldGroupName: opts.jsonFieldGroupName },
            success: function (msg) {
                if (!msg.errorMessage || msg.errorMessage.length == 0) {
                    if (opts.success)
                        opts.success(msg);
                }
            }
        });
    };

    $.cv.css.user.setCurrentUserDetails = function (options) {
        var opts = $.extend({
            success: function (msg) { },
            updateData: null,
            jsonFieldGroup: "",
            serviceName: 'user/currentuserdetails'
        }, options);
        var data = {};
        data["jsonFieldGroup"] = opts.jsonFieldGroup;
        data["updateData"] = opts.updateData;
        data["_objectKey"] = opts.updateData._objectKey;
        opts["data"] = data;
        var existingSuccess = opts.success;
        opts.success = function(msg) {
            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                if (existingSuccess)
                    existingSuccess(msg);
            }
        };
        return $.cv.css.updateObject(opts);
    };

    $.cv.css.user.getCurrentUsersCustomersWithFinanceFilter = function (options) {
        var opts = $.extend({
            selectedCustomer: "",
            success: function (msg) { }
        }, options);
        return $.cv.ajax.call('user/getCurrentUsersCustomersWithFinanceFilter', {
            parameters: { selectedCustomer: opts.selectedCustomer },
            success: $.noop
        });
    };
    
    $.cv.css.user.maxNumberOfInvoicesAllowedAsAttachment = function () {
        return $.cv.ajax.call('user/maxNumberOfInvoicesAllowedAsAttachment', {
            parameters: {},
            success: $.noop
        });
    };
    
})(jQuery);
;

;

(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.userFavourites = $.cv.css.userFavourites || {};

    $.cv.css.userFavourites.defaults = $.cv.css.userFavourites.defaults || {};
    $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite = false;

    $.cv.css.userFavourites.getFavouriteCategories = function (options) {
		var opts = $.extend({
			categoryCharactersToCompare: 0, // Off by default
            success: function (msg) { }
        }, options);
        
        return $.cv.ajax.call('userFavourites/GetFavouriteCategories', {        
            parameters:  { 
				categoryCharactersToCompare: opts.categoryCharactersToCompare
            },
            success: opts.success
        });
    }

    $.cv.css.userFavourites.generateFavourites = function (options) {
        var opts = $.extend({
            months: 12,
            success: function (msg) { }
        }, options);
        
        return $.cv.ajax.call('userFavourites/GenerateFavourites', {        
            parameters:  { 
                months: opts.months
            },
            success: opts.success
        });
    };

     $.cv.css.userFavourites.clearFavourites = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);
        
        return $.cv.ajax.call('userFavourites/ClearFavourites', {        
            parameters:  { },
            success: opts.success
        });
     };

     $.cv.css.userFavourites.removeFavourites = function (options) {
     	var opts = $.extend({
			productCodes: [],
            success: function (msg) { }
        }, options);
        
        return $.cv.ajax.call('userFavourites/RemoveFavourites', {        
            parameters:  { productCodes: opts.productCodes.join() },
            success: opts.success
        });
     },

     $.cv.css.userFavourites.addFavourite = function (options) {
         var opts = $.extend({
             productCode: '',
             returnMessage: $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite,
            success: function (msg) { }
         }, options);

         if (opts.returnMessage === true) {
             return $.cv.ajax.call('userFavourites/AddFavouriteReturnMessage', {
                 parameters: { productCode: opts.productCode },
                 success: opts.success
             });
         } else {
             return $.cv.ajax.call('userFavourites/AddFavourite', {
                 parameters: { productCode: opts.productCode },
                 success: opts.success
             });
         }
     };

     $.cv.css.userFavourites.getFavourites = function (options) {
         var opts = $.extend({
            skip: 0,
            take: 10,
			categoryCharactersToCompare: 0, // Off by default
            success: function (msg) { }
         }, options);

         return $.cv.ajax.call('userFavourites/GetFavourites', {
         	parameters: {
         		skip: opts.skip,
         		take: opts.take,
				categoryCharactersToCompare: opts.categoryCharactersToCompare
         	},
            success: opts.success
         });
     };

})(jQuery);
;

/**
 *
 * Author: Justin Wishart
 * Date: 2013-08-05
 * Description: 
 *      Gift Card add and remove client side API
 *
**/

;

(function ($) {
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.giftCard = $.cv.css.giftCard || {};


    // Service Methods
    //

    $.cv.css.giftCard.addGiftCard = function (options) {
        var opts = $.extend({
            cardNumber: '',
            pinNumber: '',
            useAllAmount: false,
            amountToUse: 0,
            success: function (msg) { }
        }, options);
        
        return $.cv.ajax.call('giftCard/addGiftCard', {
            parameters: opts,
            success: opts.success
        });
    };

    $.cv.css.giftCard.removeGiftCard = function (options) {
        var opts = $.extend({
            cardNumber: '',
            success: function (msg) { }
        }, options);

        return $.cv.ajax.call('giftCard/removeGiftCard', {
            parameters: opts,
            success: opts.success
        });
    };

})(jQuery);;
// Closure for $.cv.css.storeLocator plugin.
;

(function ($, undefined) {
    // Check we have everything we need.
    $.cv = $.cv || {};

    // $.cv.css.orderTemplate object definition.
    $.cv.css = $.cv.css || {};
    $.cv.css.storeLocator = $.cv.css.storeLocator || {};

    //
    // Find nearest store.
    //
    $.cv.css.storeLocator.findNearestStore = function (options) {
        var opts = $.extend({
            latitude: 0,
            longitude: 0,
            filters: "",
            success: function (msg) { }
        }, options);

        var p = $.cv.ajax.call("storeLocator/findNearestStore", {
            parameters: {
                latitude: opts.latitude,
                longitude: opts.longitude,
                filters: opts.filters
            },
            success: opts.success
        });

        return p;
    };

    //
    // Get all warehouses that have pickup enabled.
    //
    $.cv.css.storeLocator.getWarehousesForPickup = function (options) {
        var opts = $.extend({
            success: function (msg) { }
        }, options);

        var p = $.cv.ajax.call("storeLocator/getWarehousesForPickup", {
            parameters: {
            },
            success: opts.success
        });

        return p;
    };

    //
    // Get selected warehouse.
    //
    $.cv.css.storeLocator.getWarehouse = function (options) {
        var opts = $.extend({
            warehouseCode: "",
            success: function (msg) { }
        }, options);

        var p = $.cv.ajax.call("storeLocator/getWarehouse", {
            parameters: {
                warehouseCode: opts.warehouseCode
            },
            success: opts.success
        });

        return p;
    };
})(jQuery);
;
// closure for $.cv.css.recentlyViewedProduct plugin 
;
(function ($, undefined) {

    // Setup base 'namespaces'
    $.cv = $.cv || {};
    $.cv.css = $.cv.css || {};
    $.cv.css.recentlyViewedProduct = $.cv.css.recentlyViewedProduct || {};


    /* local storage */

    $.cv.css.recentlyViewedProduct.localGetRecentlyViewedProducts = function () {
        return $.cv.css.getLocalStorage($.cv.css.localStorageKeys.recentlyViewedProducts);
    };

    $.cv.css.recentlyViewedProduct.localSetRecentlyViewedProducts = function (products) {
        $.cv.css.setLocalStorage($.cv.css.localStorageKeys.recentlyViewedProducts, products);
    };

    $.cv.css.recentlyViewedProduct.localRemoveRecentlyViewedProducts = function (products) {
        $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.recentlyViewedProducts);
    };

    /* local storage END */


    /* Public methods */

    /* Public methods END */

})(jQuery);;
;
(function ($, undefined) {
  $.cv = $.cv || {};
  $.cv.widgetEvents = $.cv.widgetEvents || {};
	
  // products
	
  $.cv.widgetEvents.productAddedToCart = function (data) {
	var addedMessage = "Added To Cart";
	$.cv.productCustom.updateProductModal(addedMessage);
  };
  
  $.cv.widgetEvents.productAddToCartFail = function (data) {
	var failMessage = data.errorMessage;
	$.cv.productCustom.updateProductModal(failMessage);
  };
  
  $.cv.widgetEvents.productAddedToFavourites = function (data) {
	var addedMessage = "<b>{0}</b> has been added to your wishlist.".format(data.productCode);
	$.cv.css.trigger($.cv.css.eventnames.message, { message: addedMessage, type: $.cv.css.messageTypes.success, source: 'product', clearExisting: true });
  };
  
  $.cv.widgetEvents.productAddToFavouritesFail = function (data) {
	var addedMessage = "{0}".format(data.errorMessage);
	$.cv.css.trigger($.cv.css.eventnames.message, { message: addedMessage, type: $.cv.css.messageTypes.error, source: 'product', clearExisting: true });
  };
  
  // product grid
  
  $.cv.widgetEvents.sortOptionsRendered = function() {
	$("#sortBy").selectric();
  };
  
  // checkout
  
  $.cv.widgetEvents.addressRendered = function() {
	$.cv.checkoutCustom.setDeliveryAddressFunctions();
  };
  
  $.cv.widgetEvents.billingAddressRendered = function() {
	$.cv.checkoutCustom.setBillingAddressFunctions();
	$.cv.checkoutCustom.updateReviewBillingAddress();
  };
  
  $.cv.widgetEvents.billingAddressFieldsChanged = function() {
	$.cv.checkoutCustom.updateReviewBillingAddress();
  };
  
  $.cv.widgetEvents.paymentOptionsRendered = function() {
	$.cv.checkoutCustom.setPaymentOptionEvents();
  };
  
  $.cv.widgetEvents.paymentUnsuccessful = function(data) {
	if (data.message && data.message.length > 0) {
	  $.cv.css.trigger($.cv.css.eventnames.message, { message: data.message, type: $.cv.css.messageTypes.error, source: 'paymentOptions', clearExisting: true });
	}
  }
  
  // login
  
  $.cv.widgetEvents.changingPassword = function() {
	$(".new-user, .guest-user, .track-order").hide();
  };
  
  // my account
  
  $.cv.widgetEvents.myAccountAddressInitialised = function() {
	$('.my-account-group[data-json-field-group-name="myAccountAddressBook"] select:not(.cv-dropdown-country)').selectric();
  };
  
  // store locator
	
  $.cv.widgetEvents.storeLocatorShowNearest = function() {
	if (!kendo.support.mobileOS) {
	  $('html,body').animate({scrollTop: $(".location-search-area").offset().top},'slow');
	} else {
	  $('html,body').animate({scrollTop: $(".location-list").offset().top},'slow');
	  $(".location-search-area").find("input").blur();
	}
  };
  
})(jQuery);

;
;


(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        ITEMCHANGE = "itemchange";

    var mvvmwidget = {
        name: "mvvmwidget",

        options: {
            viewTemplate: '',   // what impact this has on declarative binding - or do those properties all need to be on the main widget?
            refreshOnItemChange: false,
            allowCustomOptions: false,
            disableSourceInit: false // when declarative binding, the data source is bound using setDataSource after widget initialisation. This property will stop default initialisation being called.
        },

        // Standard Methods
        initialise: function (el, o) {
            var widget = this,
                viewIsWidgetContent = false,
                tmp = null;

            if (widget.options.allowCustomOptions)
                widget._createCustomOptions();

            var internalView = $(el).children(":first");

            // Allow data-view on the containing element instead
            if (!internalView.data("view")) {
                internalView = $(el);
            }

            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                // if itemTemplate is defined, it must be defined on the extended widget - and therefore required 
                if (widget.options.itemTemplate !== undefined) {
                    // generate an item template id and flag it to be created
                    widget.options.itemTemplateId = widget.name + "-item-template-" + kendo.guid();
                    widget._itemTemplateAppended = true;
                }
                // get template text and parse it with the options
                if (widget.options.viewTemplate) {
                    widget.viewTemplate = widget.options.viewTemplate;
                } else {
                    widget.viewTemplate = '';
                    tmp = widget._buildViewTemplate(); // this sets the viewTemplate property

                    // Bring some consistency: view, item and model methods all optionally
                    // return what they create.
                    if (tmp != null) {
                        widget.viewTemplate = tmp;
                        tmp = null; // 4 clarity
                    }
                }
                var templateFn = kendo.template(widget.viewTemplate);
                if ($.isFunction(widget.viewTemplateParsing)) {
                    widget.viewTemplateParsing(widget.options);
                }
                widget.view = templateFn(widget.options);
                if ($.isFunction(widget.viewTemplateParsed)) {
                    widget.viewTemplateParsed(widget.options);
                }
                // add the itemTemplate (not parsed)
                if (widget.options.itemTemplateId) {
                    widget.itemTemplate = '';
                    tmp = widget._buildItemTemplate();

                    // Bring some consistency: view, item and model methods all optionally
                    // return what they create
                    if (tmp != null) {
                        widget.itemTemplate = tmp;
                        tmp = null; // 4 clarity
                    }

                    widget.view += widget.itemTemplate;
                }
                if ($.isFunction(widget.viewAppending)) {
                    widget.viewAppending();
                }
                widget.element.html(widget.view);
                if ($.isFunction(widget.viewAppended)) {
                    widget.viewAppended();
                }
            }

            widget.viewModel = widget._getViewModel();

            // wrap the view if not a single element - i.e. need to bind to a single element that contains the whole view
            if (widget.element.children().length > 1) {
                var whtml = widget.element.html();
                widget.element.html("<div class='cv-widget-view-wrapper'>" + whtml + "</div>");
            }

            var target = widget.element.children(":first");
            if ($.isFunction(widget.viewModelBinding)) {
                widget.viewModelBinding();
            }
            // bind view to viewModel
            kendo.bind(target, widget.viewModel);
            if ($.isFunction(widget.viewModelBound)) {
                widget.viewModelBound();
            }
            // dataSource default should be [] if the widget uses a datasource
            if (widget.options.dataSource) {
                widget._dataSource();
                // initialise the datasource if function defined
                if ($.isFunction(widget.initDataSource) && !widget.options.disableSourceInit) {
                    // ensure datasource not passed in
                    var passedIn = (widget.options.dataSource instanceof kendo.data.DataSource) || ($.isArray(widget.options.dataSource) && widget.options.dataSource.length > 0);
                    if (!passedIn) {
                        widget.initDataSource();
                    }
                }
            }
        },

        events: [DATABINDING, DATABOUND],

        items: function () {
            // this shoudld be overridden where required in extending widgets
            return [];
        },

        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource) {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
        },

        _dataSource: function () {

            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler) {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            } else {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);
            if (widget.viewModel) {
                widget.viewModel.set("dataSource", widget.dataSource);
            }

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            if (widget.options.autoBind) {
                widget.dataSource.fetch();
            }
        },

        refresh: function (e) {
            var widget = this;
            // don't refresh recursively
            if (widget.refreshing)
                return;
            if (e.action == ITEMCHANGE && !widget.options.refreshOnItemChange)
                return;
            widget.refreshing = true;
            widget.trigger(DATABINDING);
            widget.viewModel.updateViewModelFromDataSource();
            widget.trigger(DATABOUND);
            widget.refreshing = false;
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        _createCustomOptions: function() {
            var widget = this,
                options = widget.options;
            _.each($(widget.element).data(), function (value, key, list) {
                if (typeof value != "object" && typeof value != "function" && !_.has(options, key)) {
                    widget.options[key] = value;
                }
            });
        },

        _buildItemTemplate: function () {
            this._buildItemTemplateStartScriptTag();

            // Body: method can return the string, if so we append it to the
            // itemTemplate for the caller (they shouldn't do it themselves in this case)
            var body = this._buildItemTemplateBody();

            if ($.cv.util.hasValue(body)) {
                this.itemTemplate += body;
            }

            this._buildItemTemplateEndScriptTag();
        },

        _buildItemTemplateStartScriptTag: function () {
            this.itemTemplate += '<script type="text/x-kendo-template" id="' + this.options.itemTemplateId + '">';
        },

        _buildItemTemplateBody: function () {
            // Extended widget will override this
        },

        _buildItemTemplateEndScriptTag: function () {
            this.itemTemplate += '</script>';
        }
    };

    $.cv.ui.widget(mvvmwidget);

})(jQuery);
;
/* Name: message
* Author: Aidan Thomas
* Created: 20130220 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
* Params:  
*       dataSource: 
*       fadeTime: 
*       autoBind: 
*       viewTemplate: 
*       itemViewTemplate: 
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        MESSAGESHOWN = "messageShown",
        CHANGE = "change",
        URLMESSAGESOURCE = "urlMessage";


    var messageWidget = {
        // Standard Variables

        // widget name
        name: "message",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            fadeTime: 'slow',
            queryStringMessageParameters: ["message"],
            queryStringMessageType: $.cv.css.messageTypes.warning,
            messageTypesRequiringConfirmation: [],
            // viewModel flags
            autoBind: true,
            displayUrlMessages: true,
            fadeOutMessages: false,
            // events
            // view flags
            // view text defaults
            // view Template
            viewTemplate: null, // TODO: Treat these as IDs, remove the last one.
            itemViewTemplate: null
            //itemTemplateId: "cvgrid-item-template-" + kendo.guid()
        },

        events: [DATABINDING, DATABOUND, MESSAGESHOWN],

        viewModel: null,

        view: null,

        // MVVM Support

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function(el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (widget._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            $.cv.css.bind($.cv.css.eventnames.message, $.proxy(widget.viewModel.insertMessage, widget.viewModel));
        },

        destroy: function() {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        insertMessage: function(message) {
            var widget = this;
            widget.viewModel.insertMessage(message);
        },

        // private function
        _getViewModel: function() {
            var widget = this;

            var initDataSource = function() {
                if (widget.options.dataSource.length == 0) {
                    var array = [];
                    widget.options.dataSource = array;
                }
                setDataSource();
                if (widget.options.displayUrlMessages) {
                    var urlMessage = "";
                    $.each(widget.options.queryStringMessageParameters, function (idx, item) {
                        urlMessage = $.cv.util.queryStringValue(item);
                        if (urlMessage != undefined && urlMessage.length > 0)
                            viewModel.insertMessage({ message: urlMessage, type: widget.options.queryStringMessageType, source: URLMESSAGESOURCE, clearExisting: false });
                    });
                }
            };
    
            var setDataSource = function() {
                widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

                if (widget.options.autoBind) {
                    widget.dataSource.fetch();
                }
                viewModel.updateItemList();
            };
    
            var getDataView = function() {
                // check if ds is initialised
                if (!widget.dataSource || widget.dataSource.view().length == 0)
                    return [];
                $.each(widget.dataSource.view(), function(idx, item) {
                    // add standard commands
                    item.Index = idx;
                });
                return widget.dataSource.view();
            };

            kendo.data.binders.fadeIn = kendo.data.Binder.extend({
                refresh: function() {
                    var value = this.bindings["fadeIn"].get();
                    if (value) {
                        $(this.element).css({ 'display': 'none' }).fadeIn(widget.options.fadeTime);
                        widget.trigger(MESSAGESHOWN);
                    }
                }
            });

            var viewModel = kendo.observable({
                // Properties for UI elements
                dataSource: widget.options.dataSource,

                fadeMessageIn: true,

                message: '',

                hasMessages: function () {
                    return this.get("itemList").length;
                },

                updateItemList: function() {
                    this.set("itemList", getDataView());
                },

                itemList: getDataView(),

                insertMessage: function (message) {
                    if (message.clearExisting && message.source) {
                        this.removeExistingMessagesFromSource(message.source);
                    }
                    if (message.message != "") {
                        message.execCommand_hide = function () {
                            widget.dataSource.remove(this);
                            this.parent().parent().updateItemList();
                        }
                        if (_.indexOf(widget.options.messageTypesRequiringConfirmation, message.type) != -1) {
                            message.requiresConfirmation = true;
                            message.allowFadeOut = false;
                        } else {
                            message.requiresConfirmation = false;
                            message.allowFadeOut = widget.options.fadeOutMessages;
                        }
                        widget.dataSource.add(message);
                    }
                    this.updateItemList();
                },

                removeExistingMessagesFromSource: function(source) {
                    var raw = widget.dataSource.data();
                    var length = raw.length;
                    var item, i;
                    for (i = length - 1; i >= 0; i--) {
                        item = raw[i];
                        if (item.source && item.source === source) {
                            widget.dataSource.remove(item);
                        }
                    }

                },
                
                removeAllMessagesFromSource: function () {
                    var raw = widget.dataSource.data();
                    var length = raw.length;
                    var item, i;
                    for (i = length - 1; i >= 0; i--) {
                        item = raw[i];
                        widget.dataSource.remove(item);
                    }

                }
            });

            initDataSource();

            return viewModel;
        },

        _getDefaultViewTemplate: function() {
            var widget = this;

            // modify view template based on widget.options where applicable
            var html =
            [
                '<div data-view="true">',
                '   <div>',
                '       <div class="itemList" data-bind="source: itemList" data-template="' + widget.options.itemViewTemplate + '"></div>',
                '   </div>',
                '</div>'
            ].join("\n");

            return html;
        },

        _getDefaultItemViewTemplate: function() {
            var widget = this;

            // return the template to be bound to the dataSource items
            var html =
            [
                '<script type="text/x-kendo-template" id="' + widget.options.itemViewTemplate + '">',
                '   <div data-bind="attr: { class: type }">',
                '       <span data-bind="text: message"></span>',
                '       <a href="" class="close" alt="Close">×</a>',
                '   </div>',
                '</script>'
            ].join("\n");

            return html;
        }
    };

    // register the widget

    $.cv.ui.widget(messageWidget);

})(jQuery);;
/* Name: order lines
* Author: John Farnea
* Created: 20130220 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.ajax.js
* Params:  
*       dataSource: 
*       table: 
*       recordCount: 
*       pageSize: 
*       pageSizes: 
*       autoBind: 
*       viewTemplate: 
*       includeInBrowserHistory: 
*/
/*
    Pager Widget

    Operates in 2 modes

    1. using a dataSource object passed in.

    In this case the pageSize and recordCount and currentPage is determined from the datasource
    Changing pages will set the new page on the dataSource object, also changing pageSize will set the pageSize on the dataSource

    2. Datasource is server based controlled by querystrings

    Pass in a table, pageSize and recordCount as options.
    currentPage is determined from the querystring (or = 1 if no querystring PageXXX present)
    Changing the page will redirect to the curret url with PageXXX=Y where XXX is the table and Y is the current page

 */

;

// TODO: fix default array not being correctly replcaed for pageSizes

(function ($, undefined)
{
    var CHANGE = "change";

    var pager = {

        // widget name
        name: "pager",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            table: '',
            recordCount: '',
            pageSize: 10,
            pageSizes: "5,10,25,50",
            pageNumberLinksPerPage: 10,
            includeInBrowserHistory: false,
            arrowClass: "arrow",
            arrowUnavailableClass: "unavailable",
            // viewModel flags
            autoBind: true,
            // events
            // view text defaults
            firstPageText: "<span class='cv-ui-type-font-entypo'style='font-size: 32px; line-height: 8px'>Ç</span>",
            previousPageText: "<span class='cv-ui-type-font-entypo'style='font-size: 32px; line-height: 8px'>Å</span>",
            previous10Text: "…",
            next10Text: "…",
            nextPageText: "<span class='cv-ui-type-font-entypo'style='font-size: 32px; line-height: 8px'>Ä</span>",
            lastPageText: "<span class='cv-ui-type-font-entypo'style='font-size: 32px; line-height: 8px'>É</span>",
            noItemsToDisplayText: "No items to display",
            // view Template
            viewTemplate: null,
            pageTemplate: ""
        },

        view: null,
        viewModel: null,

        initialise: function (el, o)
        {
            var widget = this;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view"))
            {
                widget.view = internalView.html();
            } else
            {
                //var viewTemplate = null;
                widget._viewAppended = true;
                if (!widget.options.pageTemplate)
                {
                    // generate an page list template name and flag it to be created
                    widget.options.pageTemplate = "pageTemplate"; //widget.name + "-page-template-" + kendo.guid();
                    //widget.view += widget._getDefaultPageTemplate();
                    widget._itemViewAppended = true;
                }
                
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                
                // add the itemView (not parsed)
                if (widget._itemViewAppended)
                {
                    widget.view += widget._getDefaultPageTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            
            // bind view to viewModel
            var target = $(widget.element).children(":first");
            kendo.bind(target, widget.viewModel);
            if (widget.options.table)
            {
                widget.refresh();
            } else if (widget.options.dataSource)
            {
                widget._dataSource();
                // refresh called by datasource Change event
            }
        },

        destroy: function ()
        {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended)
            {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        refresh: function ()
        {
            var widget = this;
            widget.viewModel.dataSource = widget.options.dataSource;
            widget.viewModel.updateProperties();
        },


        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource)
        {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
        },

        _dataSource: function ()
        {
            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler)
            {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            }
            else
            {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);
            // set the page size to the option passed in, this allows the pager to control the page size
            widget.dataSource.pageSize(parseInt(widget.options.pageSize));

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            if (widget.options.autoBind)
            {
                widget.dataSource.fetch();
            } else
            {
                //                widget.refresh();
            }
        },

        _getViewModel: function ()
        {
            var widget = this;

            var initCurrentPage = function ()
            {
                if (widget.options.table)
                {
                    var page = $.cv.util.queryStringValue("Page" + widget.options.table);
                    if (!page || page.length == 0)
                        return 1;
                    var pageInt = parseInt(page);
                    if (isNaN(pageInt))
                        return 1;
                    else
                        return pageInt;
                }
                if (widget.options.dataSource)
                {
                    if (widget.options.dataSource.page)
                    {
                        return widget.options.dataSource.page();
                    }
                }
                return 1;
            };

            var initPageSize = function ()
            {
                if (widget.options.table)
                {
                    var size = $.cv.util.queryStringValue("PageSize" + widget.options.table);
                    if (!size || size.length == 0)
                    {
                        return isNaN(widget.options.pageSize) ? 10 : widget.options.pageSize;
                    }
                    var sizeInt = parseInt(size);
                    if (isNaN(sizeInt))
                        return 10;
                    else
                        return sizeInt;
                }
                if (widget.options.dataSource)
                {
                    if (widget.options.dataSource.page)
                    {
                        return widget.options.dataSource.pageSize();
                    }
                    else
                    {
                        // ensure the page size is set to the option passed in if the data source has not yet been initialised
                        return isNaN(widget.options.pageSize) ? 10 : widget.options.pageSize;
                    }
                }
                return 10;
            };

            var initRecordCount = function ()
            {
                if (widget.options.table)
                {
                    return widget.options.recordCount;
                } else if (widget.options.dataSource && widget.options.dataSource.total)
                {
                    return widget.options.dataSource.total();
                }
                return 0;
            };

            var getUnavailable = function (currentClass, isUnavailable)
            {
                var result = currentClass;
                if (isUnavailable)
                {
                    result += currentClass.length > 0 ? " " : "";
                    result += widget.options.arrowUnavailableClass;
                }
                return result;
            };

            var viewModel = kendo.observable({

                currentPage: initCurrentPage(),

                pageSize: initPageSize(),

                recordCount: initRecordCount(),

                updateProperties: function ()
                {
                    this.set("currentPage", initCurrentPage());
                    this.set("pageSize", initPageSize());
                    this.set("recordCount", initRecordCount());
                },

                currentPageStartRecord: function ()
                {
                    var cp = this.get("currentPage");
                    var ps = this.get("pageSize");
                    var rc = this.get("recordCount");
                    if (rc == 0)
                        return 0;
                    else
                        return (cp - 1) * ps + 1;
                },

                currentPageEndRecord: function ()
                {
                    var cp = this.get("currentPage");
                    var ps = this.get("pageSize");
                    var pe = ps * cp;
                    var rc = this.get("recordCount");
                    return pe > rc ? rc : pe;
                },

                firstPage: function ()
                {
                    // select the first page
                    this.set("currentPage", 1);
                },

                previousPage: function ()
                {
                    // select the previous page
                    this.set("currentPage", this.get("currentPage") - 1);
                },

                nextPage: function ()
                {
                    // select the next page
                    this.set("currentPage", this.get("currentPage") + 1);
                },

                lastPage: function ()
                {
                    // select the last page
                    this.set("currentPage", this.pageCount());
                },

                gotoPage: function (page)
                {
                    if (!isNaN(page))
                    {
                        this.set("currentPage", page);
                    }
                },

                pageSelected: function ()
                {
                    // select "currentPage"
                    // redirect if using tablename or ds.page() if using datasource
                    if (widget.options.table)
                    {
                        var params = {};
                        params["Page" + widget.options.table] = this.get("currentPage");
                        params["PageSize" + widget.options.table] = this.get("pageSize");
                        $.cv.util.redirect(window.location.href, params, !widget.options.includeInBrowserHistory);
                    } else if (widget.options.dataSource && widget.options.dataSource.page)
                    {
                        widget.options.dataSource.page(this.get("currentPage"));
                    }
                },

                onFirstPage: function ()
                {
                    return this.get("currentPage") == 1 || this.get("recordCount") == 0;
                },

                onLastPage: function ()
                {
                    var cp = this.get("currentPage");
                    var pc = this.pageCount();
                    return cp == pc || this.get("recordCount") == 0;
                },


                pageSizes: widget.options.pageSizes,

                pageSizeDataSource: function ()
                {
                    var ps = this.get("pageSizes");
                    // issue in the declaritive declaration when trying to override the page sizes, need to pass in an array i.e. [20,40,60,80]
                    if(typeof ps === "string")
                        return new kendo.data.ObservableArray(ps.split(","));
                    else
                        return new kendo.data.ObservableArray(ps);
                },

                pageDataSource: function ()
                {
                    var pa = [];

                    if (this.get("recordCount") == 0)
                    {
                        pa.push({ cssClass: "", pageText: widget.options.noItemsToDisplayText, page: 0 });
                    }
                    else
                    {
                        var pageLinks = widget.options.pageNumberLinksPerPage;
                        var cp = this.get("currentPage");
                        var pc = this.pageCount();
                        var pageLinkCount = pageLinks > 0 ? (pc < pageLinks ? pc : pageLinks) : pc; // Number of page number links to show.

                        var startPage = 1;
                        if (cp > pageLinkCount)
                        {
                            startPage = (Math.floor((cp - 1) / pageLinkCount) * pageLinkCount) + 1;
                        }
                        var endPage = startPage + pageLinkCount - 1;
                        if (endPage > pc)
                        {
                            endPage = pc;
                        }

                        if (widget.options.firstPageText != "")
                            pa.push({ cssClass: getUnavailable(widget.options.arrowClass, cp == 1), pageText: widget.options.firstPageText, page: 1 });
                        if (widget.options.previousPageText != "")
                            pa.push({ cssClass: getUnavailable(widget.options.arrowClass, cp == 1), pageText: widget.options.previousPageText, page: cp - 1 < 1 ? 1 : cp - 1 });

                        if (cp > pageLinkCount)
                        {
                            pa.push({ cssClass: "", pageText: widget.options.previous10Text, page: startPage - 1 });
                        }

                        for (var p = startPage; p <= endPage; p++)
                        {
                            var cssClass = p == cp ? "current" : "";
                            pa.push({ cssClass: cssClass, pageText: p, page: p });
                        }

                        if (endPage < pc)
                        {
                            pa.push({ cssClass: "", pageText: widget.options.next10Text, page: startPage + pageLinkCount });
                        }

                        if (widget.options.nextPageText != "")
                            pa.push({ cssClass: getUnavailable(widget.options.arrowClass, cp == pc), pageText: widget.options.nextPageText, page: cp + 1 > pc ? pc : cp + 1 });
                        if (widget.options.lastPageText != "")
                            pa.push({ cssClass: getUnavailable(widget.options.arrowClass, cp == pc), pageText: widget.options.lastPageText, page: pc });

                        $.each(pa, function (idx, item)
                        {
                            item.selectPage = function () { widget.viewModel.gotoPage(this.page); };
                        });
                    }
                    return new kendo.data.ObservableArray(pa);
                },

                pageCount: function ()
                {
                    var sz = this.get("pageSize");
                    var pages = Math.floor(this.get("recordCount") / sz);
                    if (this.get("recordCount") % sz != 0)
                    {
                        pages += 1;
                    }
                    return pages;
                },

                hasNoPages: function() {
                    return this.pageCount() == 0;
                },

                hasMultiplePages: function () {
                    return this.pageCount() > 1;
                }
            });

            viewModel.bind("change", function (e)
            {
                if (e.field == "currentPage")
                {
                    viewModel.pageSelected();
                }
                if (e.field == "pageSize")
                {
                    if (!widget.options.table)
                    {
                        widget.options.dataSource.pageSize(parseInt(viewModel.pageSize));
                    }
                    var currentPage = viewModel.get("currentPage");
                    if (currentPage != 1)
                    {
                        viewModel.set("currentPage", 1); // which fires a pageSelected
                    } else
                    {
                        viewModel.pageSelected();
                    }
                }
            });

            return viewModel;

        },

        _getDefaultViewTemplate: function ()
        {
            var html =
			[
				'	<div data-view="true"><div>',
				'		<div class="row">',
				'			<div class="two columns">',
				'				<span class="cv-ui-elements-resultsreturned"><span data-bind="text: recordCount"></span> Records, <span data-bind="text: pageCount"></span> Pages</span>',
				'			</div>',
				'			<div class="four columns">',
				'				<select data-bind="source: pageSizeDataSource, value: pageSize" style="width: auto"></select> per page',
				//'				<form class="custom">', // This is horrible and doesn't work in Chrome.
				//'					<select style="display: none" data-bind="source: pageSizeDataSource, value: pageSize"></select>',
				//'					<div class="custom dropdown">',
				//'						<a href="\\#" class="current" data-bind="text: pageSize"></a>',
				//'						<a href="\\#" class="selector"></a>',
				//'						<ul data-bind="source: pageSizeDataSource"></ul>',
				//'					</div>',
				//'				</form>',
				'			</div>',
				'			<div class="six columns">',
				'				<ul class="pagination cv-ui-elements-pagination" data-bind="source: pageDataSource" data-template="pageTemplate"></ul>',
				'			</div>',
				'		</div>',
				'	</div></div>'
			].join("\n");

            return html;
        },

        _getDefaultPageTemplate: function ()
        {
            var html =
			[
				'<script type="text/x-kendo-template" id="pageTemplate">',
				'	<li data-bind="attr: { class: cssClass }"><a href="javascript:$.noop()" alt="" data-bind="html: pageText, click: selectPage"></a></li>',
				'</script>'
			].join("\n");

            return html;
        }
    };


    $.cv.ui.widget(pager);

})(jQuery);
;
/* Name: Sorter Widget
* Author: Aidan Thomas
* Created: 20130220
* 
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
* Params:  
*           dataSource:
*           table: 
*           sortBy: 
*           includePleaseSelect: 
*           pleaseSelectText: 
*           sortOptions: 
*           sortOptionsTextField: 
*           sortOptionsValueField: 
*           includeInBrowserHistory: 
*/
/* 
    Operates in 2 modes

    1. Using a dataSource object passed in.

    In this case the pageSize and recordCount and currentPage is determined from the datasource
    Changing the sorting will set the sort order on the dataSource object

    2. Datasource is server based controlled by querystrings

    Pass in a table sorting as options.
    currentSort is determined from the querystring (or = blank if no querystring SortXXX present)
    Changing the sort will redirect to the current url with SortXXX=Y where XXX is the table and Y is the current sort
    
    Dependencies
    scripts/cv.util.js

 */

;

// TODO: pass through select values (data-value-field) and select text (data-text-fields) so that the display of the select box can be different to the values
// TODO: test datasource sorting

(function ($, undefined) {

    var CHANGE = "change",
        SORTOPTIONSRENDERED = "sortOptionsRendered";

    var sorter = {

        name: "sorter",

        options: {
            dataSource: [],
            table: '',
            sortBy: '',
			includePleaseSelect: true,
			pleaseSelectText: 'Please Select...',
            sortOptions: 'ProductCode,Description',
			//sortOptions: [{text: 'Product Code', value: 'ProductCode'},{text: 'Description', value: 'Description'}],
			sortOptionsTextField: "text",
			sortOptionsValueField: "value",
			includeInBrowserHistory: false,
            // view Template
            viewTemplate: null,
            itemViewTemplate: null
        },

        events: [CHANGE, SORTOPTIONSRENDERED],

        view: null,
        viewModel: null,

        initialise: function (el, o) {
            var self = this;
            //var view = null;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                self.view = internalView.html();
            } else {
                //var viewTemplate = null;
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an page list template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    //widget.view += widget._getDefaultPageTemplate();
                    widget._itemViewAppended = true;
                }

                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);

                // add the itemView (not parsed)
                if (widget._itemViewAppended) {
                    widget.view += widget._getDefaultPageTemplate();
                }
                widget.element.html(widget.view);
            }
            // now MMVM bind
            self.viewModel = self._getViewModel();
            var target = $(self.element).children(":first");
            kendo.bind(target, self.viewModel);
            if (self.options.table) {
                self.options.dataSource = null;
                self.refresh();
            } else if (self.options.dataSource) {
                self._dataSource();
                // refresh called by datasource Change event
            }
        },
        
        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        refresh: function () {
            var widget = this;
            widget.viewModel.dataSource = widget.options.dataSource;
            widget.viewModel.updateProperties();
        },

        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource) {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
        },

        _dataSource: function () {
            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler) {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            }
            else {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            if (widget.options.autoBind) {
                widget.dataSource.fetch();
            }
        },
        
        _getViewModel: function() {
            var widget = this;

            var initCurrentSort = function () {
                if (widget.options.dataSource) {
                    var dssort = widget.options.dataSource.sort();
                    var sortField = '';
                    var sortDirection = "";
                    if (dssort && dssort.length > 0) {
                        sortField = dssort[0].field;
                        sortDirection = dssort[0].dir;
                    }
                    if (sortField.length > 0 && sortDirection != undefined && sortDirection.length > 0) {
                        sortField += (" " + sortDirection);
                    }
                    removePleaseSelect(sortField);
                    return sortField;
                }
                var sort = $.cv.util.queryStringValue("Sort" + widget.options.table);
                if (!sort || sort.length == 0) {
                    if (widget.options.sortBy != '') {
                        return widget.options.sortBy;
                    } else {
                        if (widget.options.includePleaseSelect) {
                            return widget.options.pleaseSelectText;
                        } else {
                            sort = '';
                        }
                    }
                }
                setIsCurrentSort(sort);
                return sort;
            };

            var setIsCurrentSort = function (sort) {
                var ds = widget.viewModel.get("sortOptionsDataSource");
                $.each(ds, function (idx, item) {
                    if (item[widget.options.sortOptionsValueField] == sort) {
                        item.set("isCurrentSort", true);
                    }
                });
            };

            var removePleaseSelect = function (sortField) {
                if (widget.viewModel && widget.viewModel != undefined && sortField.length > 0) {
                    var ds = widget.viewModel.get("sortOptionsDataSource");
                    if (!widget.options.includePleaseSelect) {
                        ds = _.filter(ds, function (item) {
                            return item[widget.options.sortOptionsValueField] != "";
                        });
                        widget.viewModel.set("sortOptionsDataSource", ds);
                    }
                }
            };
			
            var initDS = function () {
                if ($.isArray(widget.options.sortOptions)) {
                    var so = widget.options.sortOptions;
                    var pleaseSelect = {};
                    pleaseSelect[widget.options.sortOptionsTextField] = widget.options.pleaseSelectText;
                    pleaseSelect[widget.options.sortOptionsValueField] = "";
                } else {
                    var so = widget.options.sortOptions.split(",");
                    var pleaseSelect = widget.options.pleaseSelectText;
                }
                if (widget.options.includePleaseSelect || (!widget.options.includePleaseSelect && initCurrentSort() == '')) {
                    so = $.merge([pleaseSelect], so);
                }
                $.each(so, function (idx, item) {
                    item.sort = function () {
                        widget.viewModel.set("currentSort", item[widget.options.sortOptionsValueField]);
                    }
                });
                return so;
            };

            var viewModel = kendo.observable({

                currentSort: initCurrentSort(),

                direction: "",

                updateProperties: function () {
                    // Don't want to trigger "sort" on the DS from this function, so don't use this.set
                    this.set("skipSort", true);
                    this.set("currentSort", initCurrentSort());
                    this.set("skipSort", false);
                    widget.trigger(SORTOPTIONSRENDERED);
                },

                skipSort: false,
				
				sortOptions: widget.options.sortOptions,
				
				sortOptionsDataSource: initDS(),

                sortSelected: function () {
                    // select "currentSort"
                    // redirect if using tablename or ds.sort() if using datasource
                    var currentSort = this.get("currentSort"),
                        lastWord = currentSort.split(" ").pop(),
                        direction = (lastWord.toLowerCase() == "asc" || lastWord.toLowerCase() == "desc") ? lastWord.toLowerCase() : "asc";
                    if (lastWord.toLowerCase() == "asc" || lastWord.toLowerCase() == "desc" && widget.options.dataSource) {
                        currentSort = currentSort.substring(0, currentSort.lastIndexOf(" "));
                    }
                    this.set("direction", (lastWord.toLowerCase() == "asc" || lastWord.toLowerCase() == "desc") ? lastWord : "");
					if (widget.options.dataSource) {
					    if (currentSort) {
					        widget.options.dataSource.sort({ field: currentSort, dir: direction });
					    }
					} else {
					    var params = {};
						if (currentSort != widget.options.pleaseSelectText)
						    params["Sort" + widget.options.table] = currentSort;
						else
						    params["Sort" + widget.options.table] = '';
						params["Page" + widget.options.table] = 1;
						$.cv.util.redirect(window.location.href, params, !widget.options.includeInBrowserHistory);
					}
                }
            });

            // Causing a loop - this is calling sortSelected, which "sorts" the datasource, causing a ds.change event, calling refresh, calling updateProperties, sets currentSort, back to step 1...
            viewModel.bind("change", function (e) {
                if (e.field == "currentSort") {
                    if (!viewModel.get("skipSort"))
                        viewModel.sortSelected();
                }
            });

            return viewModel;

        },

        _getDefaultViewTemplate: function () {
        }

    };


    $.cv.ui.widget(sorter);

})(jQuery);;
/*
 *  
 * Author: Chad Paynter
 * Date: 08/05/2013
 * Name: selectcode
 * Description: Allow input of a trade docket with submit taking user to checkout

    Requires:
    Scripts/jquery-1.8.3.js
    Scripts/kendo.core.js
    Scripts/kendo.data.js
    Scripts/kendo.binder.js
    Scripts/cv.js
    Scripts/cv.css.js (For CSS Related Widgets)
    Scripts/cv.util.js
    Scripts/cv.widget.kendo.js

    options:
        showValue - show the value of the item rather than the default text of the item
 */
;
(function ($, undefined) {
    var kendo = window.kendo,
        ui = kendo.ui,
        keys = kendo.keys,
        ns = ".kendoComboBox",
        support = kendo.support,
        placeholderSupported = support.placeholder,
        FOCUS = "focus",
        FOCUSED = "k-state-focused",
        STATE_SELECTED = "k-state-selected",
        STATE_FILTER = "filter",
        STATE_ACCEPT = "accept",
        STATE_REBIND = "rebind",
        NULL = null;

    var selectCodeWidget = {
        // widget name
        name: "SelectCode",
        extend: "ComboBox", // extend existing combobox widget

        options: {
            showValue: false // by default this control shows the text int he input, not the value
        },

        initialise: function (element, options) {
            var that = this;


            // that.ns = ns;
            // initialize the widget by calling init on the extended class
            // kendo.ui.ComboBox.fn.init.call(that, element, options);
            that.ns = ns;

            // Added focus trigger
            that.input.on("focus" + ns, function () {
                that.trigger(FOCUS);
            });
        },

        value: function (value) {
            var that = this,
                idx;

            if (value !== undefined) {
                if (value !== null) {
                    value = value.toString();
                }

                that._valueCalled = true;

                // CHANGED: This was causing old value to appear in input when dropdown opened
                //if (value && that._valueOnFetch(value)) {
                //    return;
                //}



                idx = that._index(value);

                if (idx > -1) {
                    that.select(idx);
                    that.text(value); // CHANGED:  JDF Added this
                } else {
                    that.current(NULL);
                    that._custom(value);
                    that.text(value);
                    that._placeholder();
                }

                that._old = that._accessor();
                that._oldIndex = that.selectedIndex;
            } else {
                return that._accessor();
            }
        },

        _keydown: function (e) {
            var that = this,
                key = e.keyCode;

            that._last = key;

            clearTimeout(that._typing);

            // CHANGED: dont process the select kep up or down if the dropdown is not open
            if (!that.popup.visible() && (key == keys.UP || key == keys.DOWN)) {
                e.preventDefault();
                return;
            }

            if (key != keys.TAB && !that._move(e)) {
                if (that.dataSource && that.dataSource._data){
                    that.dataSource._data.isSearching = true;
                }
                that._search();
            }
        },

        _select: function (li) {
            var that = this,
                text,
                value,
                data = that._data(),
                idx = that._highlight(li);

            that.selectedIndex = idx;

            if (idx !== -1) {
                if (that._state === STATE_FILTER) {
                    that._state = STATE_ACCEPT;
                }

                that._current.addClass(STATE_SELECTED);

                data = data[idx];
                text = that._text(data);
                value = that._value(data);

                // CHANGED: implement which field to show
                if (that.options.showValue) {
                    that._prev = that.input[0].value = value;
                } else {
                    that._prev = that.input[0].value = text;
                }
                that._accessor(value !== undefined ? value : text, idx);
                that._placeholder();

                if (that._optionID) {
                    that._current.attr("aria-selected", true);
                }
            }
        },

        //CHANGED: function added
        _adjustListWidth: function () {
            var list = this.list,
                WIDTH = "width",
                width = list[0].style.width,
                wrapper = this.wrapper,
                computedStyle, computedWidth;

            if (!list.data(WIDTH) && width) {
                return;
            }

            //computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
            //computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();

            //if (computedStyle && (browser.mozilla || browser.msie)) { // getComputedStyle returns different box in FF and IE.
            //    computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
            //}

            width = 300; //computedWidth - (list.outerWidth() - list.width());

            list.css({
                fontFamily: wrapper.css("font-family"),
                width: width
            })
            .data(WIDTH, width);

            return true;
        },

        // JDF 27/3/14 - standard _aria() function in kendo combobox gets an error in IE 11/10 running IE7 compatibility mode
        _aria: function () {
        },

        _input: function () {
            var that = this,
                element = that.element.removeClass("k-input")[0],
                tabIndex = element.tabIndex,
                accessKey = element.accessKey,
                wrapper = that.wrapper,
                SELECTOR = "input.k-input",
                input, DOMInput,
                name = element.name || "";

            if (name) {
                name = 'name="' + name + '_input" ';
            }

            input = wrapper.find(SELECTOR);

            if (!input[0]) {
                wrapper.append('<span tabindex="-1" unselectable="on" class="k-state-default" style="background-color: transparent"><input ' + name + 'class="k-input" type="text" autocomplete="off"/></span>')
                       .append(that.element);

                input = wrapper.find(SELECTOR);
            }

            DOMInput = input[0];
            DOMInput.tabIndex = tabIndex;
            DOMInput.style.cssText = element.style.cssText;

            if (element.maxLength > -1) {
                DOMInput.maxLength = element.maxLength;
            }

            input.addClass(element.className)
                 .val(element.value)
                 .css({
                     width: "100%",
                     height: element.style.height
                 })
                 .show();

            if (placeholderSupported) {
                input.attr("placeholder", that.options.placeholder);
            }

            if (accessKey) {
                element.accessKey = "";
                input[0].accessKey = accessKey;
            }

            that._focused = that.input = input;
            that._inputWrapper = $(wrapper[0].firstChild);
            that._arrow = wrapper.find(".k-icon")
                                 .attr({
                                     "role": "button",
                                     "tabIndex": -1
                                 });

            if (element.id) {
                that._arrow.attr("aria-controls", that.ul[0].id);
            }
        }
    };

    $.cv.ui.widget(selectCodeWidget);
})(jQuery);;
// TODO: autobind template
/*
* Name: templated list
* Author: Aidan Thomas 
* Date Created: 2013/01/04
* Description: databound list with template
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js (built with jquery-1.7.1.min.js)
*          kendo.web.js (kendo.web.min.js v2012.2.710)
*
*          --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.data.kendo.js
*          /Scripts/cv.ajax.js    
* Parameters:
*          viewTemplate: 
*			types: 
*			templates:
*			dataSource: 
*			autobind: 
*			defaultType: 
*			currentType: 
*			replaceContainer: 
*/
;
(function ($, undefined) {

    var TEMPLATECHANGED = 'templatechanged';

    var templatedListWidget = {


        // Standard Variables

        // widget name
        name: "templatedList",

        // default widget options
        options: {
            // view Template
            viewTemplate: null,
            // standard view options
            types: "List,Grid,DetailList",
            templateClasses: 'list-template,grid-template,detailed-list-template',
            autobind: false,
            autoScrollToTop: false,
            scrollSpeed: "fast",
            scrollTarget: "html,body",
            saveUsersSelection: false,
            userSelectionField: "",
            userDetailsJsonFieldGroup: "",
            defaultType: 'List',
            currentType: '',
            replaceContainer: '',
            scrollContainer: ''
        },

        // private property
        _viewAppended: false,

        events: [TEMPLATECHANGED],

        // Standard Methods
        initialise: function (el, o) {
            var self = this;
            var view = null;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                view = internalView.html();
            }
            if (!view) {
                if (!self.options.viewTemplate) {
                    self.options.viewTemplate = self._getDefaultViewTemplate();
                }
                var viewTemplate = kendo.template(self.options.viewTemplate);
                view = viewTemplate(self.options);
                $(el).append(view);
                self._viewAppended = true;
            }
            // now MMVM bind
            self.viewModel = self._getViewModel();
            var target = $(el).children(":first");
            kendo.bind(target, self.viewModel);
            if (self.options.autobind)
                self.viewModel.setCurrentType();
            self.viewModel.set("isInitialLoad", false);
            $.cv.css.bind($.cv.css.eventnames.templatedListChanged, $.proxy(self.viewModel.templatedListChanged, self.viewModel));
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var initCurrentType = function () {
                return currentType;
            }

            var viewModel = $.extend(kendo.observable(widget.options), {
                // Properties for UI elements
                defaultType: widget.options.defaultType,

                currentType: '',

                isInitialLoad: true,

                typeSelected: function () {
                    var type = $.inArray(this.get("currentType"), this.typesDataSource());
                    if (type > -1) {
                        this.swapType(type);
                    }
                },

                setCurrentType: function () {
                    var currentType = widget.options.currentType != '' ? widget.options.currentType : widget.options.defaultType;
                    this.set("currentType", currentType);
                },

                types: widget.options.types,

                typesDataSource: function () {
                    var vs = this.get("types");
                    return vs.split(",");
                },

                templateClasses: widget.options.templateClasses,

                templateClassesDataSource: function () {
                    var ts = this.get("templateClasses");
                    return ts.split(",");
                },

                isType1Active: function () {
                    return (this.typesDataSource().length > 0 && this.get("currentType") == this.typesDataSource()[0])
                },

                isType2Active: function () {
                    return (this.typesDataSource().length > 1 && this.get("currentType") == this.typesDataSource()[1])
                },

                isType3Active: function () {
                    return (this.typesDataSource().length > 2 && this.get("currentType") == this.typesDataSource()[2])
                },

                showType1: function () {
                    if (this.templateClassesDataSource().length > 0 && this.typesDataSource().length > 0)
                        this.set("currentType", this.typesDataSource()[0]);
                },

                showType2: function () {
                    if (this.templateClassesDataSource().length > 1 && this.typesDataSource().length > 1)
                        this.set("currentType", this.typesDataSource()[1]);
                },

                showType3: function () {
                    if (this.templateClassesDataSource().length > 2 && this.typesDataSource().length > 2)
                        this.set("currentType", this.typesDataSource()[2]);
                },

                swapType: function (typeNum) {
                    if (widget.options.replaceContainer != '') {
                        $.each(this.templateClassesDataSource(), function (idx, item) {
                            $(widget.options.replaceContainer).removeClass(item);
                        });
                        $(widget.options.replaceContainer).addClass(this.templateClassesDataSource()[typeNum]);
                        widget.trigger(TEMPLATECHANGED, { currentType: this.get("currentType") });
                        $.cv.css.trigger($.cv.css.eventnames.templatedListChanged, this.get("currentType"));
                        if (!this.get("isInitialLoad")) {
                            this.saveUserSelection();
                            this.scrollPage();
                        }
                    }
                },

                templatedListChanged: function (currentType) {
                    if (this.get("currentType") != currentType) {
                        this.set("isInitialLoad", true);
                        this.set("currentType", currentType);
                        this.set("isInitialLoad", false);
                    }
                },

                scrollPage: function () {
                    var scrollContainer = widget.options.scrollContainer.length > 0 ? widget.options.scrollContainer : widget.options.replaceContainer;
                    if (widget.options.autoScrollToTop && scrollContainer != "") {
                        $(widget.options.scrollTarget).animate({ scrollTop: $(scrollContainer).offset().top }, widget.options.scrollSpeed);
                    }
                },

                saveUserSelection: function () {
                    if (widget.options.saveUsersSelection && widget.options.userSelectionField != "" && widget.options.userDetailsJsonFieldGroup != "" && $.cv && $.cv.css && $.cv.css.user) {
                        var _this = this, haveCurrentUser = this.getCurrentUser();
                        haveCurrentUser.done(function (user) {
                            if (user && user.data && user.data.length > 0) {
                                var userUpdateData = { _objectKey: user.data[0]._objectKey };
                                userUpdateData[widget.options.userSelectionField] = _this.get("currentType");
                                $.cv.css.user.setCurrentUserDetails({ updateData: userUpdateData, jsonFieldGroup: widget.options.userDetailsJsonFieldGroup });
                            }
                        });
                    }
                },

                getCurrentUser: function () {
                    var haveCurrentUser = $.Deferred(), currentUser = $.cv.css.localGetUser();
                    if (currentUser == null) {
                        haveCurrentUser = $.cv.css.getCurrentUser();
                    } else {
                        haveCurrentUser.resolve({ data: [currentUser] });
                    }
                    return haveCurrentUser;
                }

            });

            viewModel.bind("change", function (e) {
                if (e.field == "currentType") {
                    viewModel.typeSelected();
                }
            });

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var self = this;
            var html = "";

            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(templatedListWidget);

})(jQuery);;
/* Name: mega menu
* Author: Aidan Thomas 
* Created: 20130220
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.util.js
* Params:  
*       viewTemplate: kendo template id to show the grid in
*        openTimeout: 
*        closeTimeout: 
*        useTimout: 
*        activeItemClass: 
*        highlightCurrentMenu: 
*        currentMenuClass:
*        parentItems: 
*        showEvent: 
*        closeEvent: 
*        menuitemhover:
*/
;
(function ($, undefined) {

    var MENUITEMHOVER = 'menuitemhover',
        MENUITEMSHOW = "menuItemShow",
        MENUITEMHIDE = "menuItemHide";

    var megaMenuWidget = {


        // Standard Variables

        // widget name
        name: "megaMenu",

        // default widget options
        options: {
            // viewModel defaults
            openTimeout: 200,
            closeTimeout: 100,
            useTimout: true,
            activeItemClass: 'active',
            menuActiveClass: 'active',
            highlightCurrentMenu: true,
            currentMenuClass: 'current',
            parentItems: '#menu > li',
            showEvent: 'mouseenter',
            closeEvent: 'mouseleave',
            // viewModel flags
            // events
            menuitemhover: null,
            // view flags
            // view text defaults
            // view Template
            viewTemplate: '' // treat like its an id
        },

        events: [MENUITEMHOVER, MENUITEMSHOW, MENUITEMHIDE],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,

        _activeMenuItem: '',


        // Standard Methods
        initialise: function (el, o) {
            var widget = this;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                if (!widget.options.viewTemplate) {
                    widget.options.viewTemplate = widget._getDefaultViewTemplate();
                }
                var viewTemplate = kendo.template(widget.options.viewTemplate);
                widget.view = viewTemplate(widget.options);
                $(el).append(widget.view);
                widget._viewAppended = true;
            }
            // now MMVM bind
            widget.viewModel = widget._getViewModel();
            var target = $(widget.element).children(":first");
            kendo.bind(target, widget.viewModel);
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        showMenuItem: function (item) {
            var widget = this;
            widget.viewModel.showItem(item);
        },

        hideMenuItem: function (item) {
            var widget = this;
            widget.viewModel.hideItem(item);
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            initCurrentMenu = function () {
                var currentPath = decodeURIComponent(window.location.pathname);
                var currentItem = null;
                if (widget.options.highlightCurrentMenu) {
                    $(widget.options.parentItems).each(function () {
                        var _this = this;
                        var links = $(_this).find("a[href='" + currentPath + "']");
                        if ($(links).length > 0) {
                            currentItem = _this;
                            return false;
                        }
                    });
                    if (currentItem != null && $(currentItem).length > 0)
                        $(currentItem).addClass(widget.options.currentMenuClass);
                }
            }


            var getDataView = function () {
                var array = widget.element.find(widget.options.parentItems);
                $.each(array, function (idx, item) {
                    // add standard commands
                    $(item).bind(widget.options.showEvent, function () {                        
                        viewModel.showItem(item);
                    });
                    $(item).bind(widget.options.closeEvent, function () {
                        viewModel.hideItem(item);
                    });
                });
            }

            var viewModel = kendo.observable({


                // Properties for UI elements

                currentTimeOutID: '',

                isMenuOpen: false,

                menuActiveClass: function () {
                    return this.get("isMenuOpen") ? widget.options.menuActiveClass : "";
                },

                // UI Element state
                showItem: function (item) {
                    window.clearTimeout(this.get("currentTimeOutID"));
                    var _this = this;
                    if (widget.options.useTimout && !this.get('isMenuOpen')) {
                        var timeOutID = window.setTimeout(function () {
                            _this.hideActiveItem();
                            $(item).addClass(widget.options.activeItemClass);
                            _this.set('isMenuOpen', true);
                        }, widget.options.openTimeout);
                        this.set('currentTimeOutID', timeOutID);
                    }
                    else {
                        this.hideActiveItem();
                        $(item).addClass(widget.options.activeItemClass);
                        this.set('isMenuOpen', true);
                    }
                    widget.trigger(MENUITEMSHOW, { item: item });
                },

                hideItem: function (item) {
                    window.clearTimeout(this.get("currentTimeOutID"));
                    var _this = this;
                    if (widget.options.useTimout) {
                        var timeOutID = window.setTimeout(function () {
                            _this.hideActiveItem();
                            $(item).removeClass(widget.options.activeItemClass);
                            _this.set('isMenuOpen', false);
                        }, widget.options.closeTimeout);
                        this.set('currentTimeOutID', timeOutID);
                    }
                    else {
                        this.hideActiveItem();
                        $(e.target).removeClass(widget.options.activeItemClass);
                        this.set('isMenuOpen', false);
                    }
                    widget.trigger(MENUITEMHIDE, { item: item });
                },

                hideActiveItem: function () {
                    widget.element.find(widget.options.parentItems).removeClass(widget.options.activeItemClass);
                }

            });

            getDataView();
			initCurrentMenu();

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(megaMenuWidget);

})(jQuery);;
/*
* Name: product search
* Author: Aidan Thomas
* Date Created: 2013/01/04
* Description: product search widget 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js (built with jquery-1.7.1.min.js)
*          kendo.web.js (kendo.web.min.js v2012.2.710)
*
*          --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.data.kendo.js
*          /Scripts/cv.util.js    
* Parameters:
*          suggestionsDataSource
*          searchPageUrl: 
*          searchQueryStringKey: query string key to get search detail from
*          searchOnChange: trigger search on change
*          change: chang event
*          useSuggestions: show suggestsions from suggestions data source
*          textFieldPrompt: search field promp
*          searchButtonText: 
*          viewTemplate: kendo template id for the view
*/
;
(function ($, undefined) {

    var CHANGE = 'change',
        WIDGETINITIALISED = "widgetInitialised";

    var productsearch = {
        // Standard Variables

        // widget name
        name: "productSearch",

        // default widget options
        options: {
            // viewModel defaults
            suggestionsDataSource: [],
            searchSuggestions: true,
            productSuggestions: false,
            searchPageUrl: '/ProductDisplay.aspx',
            searchQueryStringKey: 'ProductSearch',
            productSearchFilterElement: "[data-role='productsearchfilter']",
            productSearchFilterData: "productSearchFilter",
            // viewModel flags
            searchOnChange: false,
            includeInBrowserHistory: true,
            retainCurrentSearch: true,
            // events
            change: null,
            // view flags
            useSuggestions: false,
            useSearchAndProductSuggestions: false,
            allowEmptySearch: true,
            triggerMessages: true,
            clearExistingMessages: true,
            // view text defaults
            textFieldPrompt: 'Product Search :',
            searchButtonText: 'Go',
            pleaseEnterASearchValue: "Please enter a search value",
            // view Template
            viewTemplate: '' // treat like its an id
        },

        events: [CHANGE, WIDGETINITIALISED],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,


        // Standard Methods
        initialise: function(el, o) {
            var widget = this;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                if (!widget.options.viewTemplate) {
                    widget.options.viewTemplate = widget._getDefaultViewTemplate();
                }
                var viewTemplate = kendo.template(widget.options.viewTemplate);
                widget.view = viewTemplate(widget.options);
                $(el).append(widget.view);
                widget._viewAppended = true;
            }
            // now MMVM bind
            widget.viewModel = widget._getViewModel();
            var target = $(widget.element).children(":first");
            kendo.bind(target, widget.viewModel);
            widget.trigger(WIDGETINITIALISED);
        },

        destroy: function() {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function() {
            var widget = this;

            // setup default dataSource calling service
            if (widget.options.useSuggestions && (widget.options.suggestionsDataSource === []) || widget.options.suggestionsDataSource.length == 0) {
                if (widget.options.useSearchAndProductSuggestions) {
                    widget.options.suggestionsDataSource = $.cv.data.dataSource({
                        method: 'search/predictiveSearchJson2',
                        params: function(data, type) {
                            return {
                                searchTerm: data.filter.filters[0].value,
                                searchSuggestions: widget.options.searchSuggestions,
                                productSuggestions: widget.options.productSuggestions,
                                filter: 'SearchKey'
                            };
                        }
                    });
                } else {
                    widget.options.suggestionsDataSource = $.cv.data.dataSource({
                        method: 'search/predictiveSearchJson',
                        params: function (data, type) {
                            return {
                                searchTerm: data.filter.filters[0].value,
                                filter: 'SearchKey'
                            };
                        }
                    });
                }
            }

            // pull the current search value out of the url query string
            if (widget.options.retainCurrentSearch)
                var currentSearch = $.cv.util.queryStringValue(widget.options.searchQueryStringKey);

            var viewModel = kendo.observable({

                isSearching: false,

                suggestionsDataSource: widget.options.suggestionsDataSource,

                searchText: currentSearch ? (currentSearch.length != 0 ? (widget.options.useSuggestions ? { SearchTermValue: currentSearch, SearchKey: currentSearch } : currentSearch) : null) : null,

                searchInputKeyUp: function(event) {
                    if (event.which == 13) {
                        // prevent the default methods for the enter key, helps stop the form submitting when there is a input of type submit on the page somewhere
                        event.preventDefault();
                        event.stopPropagation();
                        this.search();
                    }
                },

                getProductSearchFilter: function() {
                    var filter = "";
                    $(widget.options.productSearchFilterElement).each(function() {
                        var productSearchFilterWidget = $(this).data(widget.options.productSearchFilterData);
                        if (productSearchFilterWidget)
                            filter = productSearchFilterWidget.buildFilter();
                    });
                    return filter;
                },

                search: function () {
                    var params = {}, searchValue, filter;
                    // if using suggestions pull the search value out of the autocomplete value otherwise just get it from the input
                    if (this.get("searchText") != null) {
                        if (widget.options.useSuggestions) {
                            searchValue = (this.get("searchText").SearchTermValue != null && this.get("searchText").SearchTermValue != '') ? this.get("searchText").SearchTermValue : this.get("searchText").SearchKey;
                            if (!searchValue) {
                                searchValue = this.get("searchText");
                            }
                        } else {
                            searchValue = this.get("searchText");
                        }
                    } else {
                        searchValue = '';
                        params["AttemptedEmptySearch"] = encodeURIComponent("true");
                    }
                    if ($.trim(searchValue).length == 0 && !widget.options.allowEmptySearch) {
                        $.cv.util.notify(this, widget.options.pleaseEnterASearchValue, $.cv.css.messageTypes.error, {
                            triggerMessages: widget.options.triggerMessages,
                            clearExisting: widget.options.clearExistingMessages,
                            source: widget.name
                        });
                    } else {
                        this.set("isSearching", true);
                        params[widget.options.searchQueryStringKey] = encodeURIComponent(searchValue);
                        filter = this.getProductSearchFilter();
                        if (filter.length > 0)
                            params["FilterFeature"] = filter;
                        if (searchValue != null) {
                            $.cv.util.redirect(widget.options.searchPageUrl, params, !widget.options.includeInBrowserHistory);
                        } else {
                            this.set("isSearching", false);
                        }
                    }
                }
            });

            if (widget.options.searchOnChange) {
                viewModel.bind('change', function(e) {
                    if (e.field == "searchText") {
                        widget.trigger(CHANGE, e);
                        viewModel.search();
                    }
                });
            }

            return viewModel;
        },

        _getDefaultViewTemplate: function() {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "<div>"
                + "<label>" + widget.options.textFieldPrompt + "</label>";
            if (widget.options.useSuggestions) {
                html += "<input type='text' data-role='autocomplete' data-text-field='SearchKey' data-bind='source: suggestionsDataSource, value: searchText' data-value-update='keyup' />";
            } else {
                html += "<input type='text' data-bind='value: searchText, events: {keyup: searchInputKeyUp}' data-value-update='keyup' />";
            }
            html += "<button data-bind='click: search'>" + widget.options.searchButtonText + "</button>"
                + "</div>";
            return html;
        }
    };

    // register the widget

    $.cv.ui.widget(productsearch);

})(jQuery);
;
/*
* See: http://confluence.commercevision.com.au/x/3gL3B
*/
;

(function ($, undefined) {
    var PRODUCTADDEDTOCART = "productAddedToCart",
        PRODUCTADDEDTOFAVOURITES = "productAddedToFavourites",
        PRODUCTADDTOCARTFAIL = "productAddToCartFail",
        PRODUCTADDTOFAVOURITESFAIL = "productAddToFavouritesFail",
        PRODUCTREMOVEDFROMFAVOURITES = "productRemovedFromFavourites",
        PRODUCTREMOVEDFROMAVOURITESFAIL = "productRemovedFromFavouritesFail";

    var productWidget = {
        name: "product",
        extend: "mvvmwidget",

        extendEvents: [
            PRODUCTADDEDTOCART,
            PRODUCTADDEDTOFAVOURITES,
            PRODUCTADDTOCARTFAIL,
            PRODUCTADDTOFAVOURITESFAIL, 
            PRODUCTREMOVEDFROMFAVOURITES,
            PRODUCTREMOVEDFROMAVOURITESFAIL
        ],

        options: {
            // messages
            // view Template
            viewTemplate: null,
            // standard view options
            translationParams: "",
            productCode: "",
            itemsOnOrder: 0,
            availableQty: 0,
            priceForOne: 0.00,
            quantity: "",
            autoPopulateQty: true,
            defaultCostCentreCode: "",
            noteIsExtendedLineDescription: true,
            clearExistingMessages: false,
            triggerMessages: false,
            triggerFavouritesMessages: true,
            allowOrderEntryForProduct: true,
            allowDecimals: false,
            blurElementOnKeyup: true,
            addToCartRefreshTime: 600, // timeout when adding to cart to refresh order default to 400ms
            // others to be added to allow configuration of standard view
            // widget settings
            enableOrderLineNotes: false,
            useCostCentres: false,
            useOrderEntry: false,
            isFavourite: false,
            multiUpdateThrottleTimeout: 400,
            addSelectedAttributeCodeToOrder: false,
            forceOrderPackQty: false,
            
            //Product attributes options
            attributedProductList:null,
            pleaseSelectAttributeText: "Please select the {0}",
            attributeValueArray: "",
            packQuantity: 1,
            
            textProductAddedToCart: "Your products have been successfully added to your cart",
            textQuantityMustBeNumeric: "Please enter whole numbers greater than zero",
            pleaseOrderInPackQuantity: "Must Order in pack quantity of {0}'s",
            textProductAddedToFavourites: "Your product has been successfully added to your favourites",
            textDefaultErrorAddingToFavourites: "Error adding this product to your favourites, it may already exist",
            textDefaultRemoveFavouriteSuccess: "Your product has been successfuly removed from your favourites",
            textDefaultRemoveFavouriteFail: "Error removing this product from your favourites, it may have already been removed",
            defaultPleaseSelectAttribute: "Please ensure you have selected all product options",
            textAddToCartButtonLabel: "Add to Cart",
            textAddToFavouritesButtonLabel: "Add to Favourites"
        },

        initialise: function (el, o) {
            var widget = this,
                opts = widget.options,
                vm = widget.viewModel;

            widget.translateParameters(el);

            widget._updatePriceBreaksThrottled = _.debounce(function () {
                vm._updatePriceBreaks();
            }, opts.multiUpdateThrottleTimeout);

            // We MUST have the product code from the attribute as as a string
            // without the kendo special parsing (which would parseInt the thing thus
            // removing any zeros at the start and any letters if starts with numbers.
            // Note: .attr() as it returns string whereas data() converts it to an int potentially also.
            widget.options.productCode = el.attr('data-product-code') || "";
            widget.options.masterProductCode = el.attr('data-master-product-code') || null;
        },

        translateParameters: function (el) {
            var widget = this;

            if (widget.options.translationParams.length > 0) {
                $.each(widget.options.translationParams.split(","), function (idx, item) {
                    if (widget.options[item] != undefined && widget.options[item] != $(el).data(item)) {
                        widget.options[item] = $(el).data(item);
                    }
                });
            }
        },

        // Called after the widget view is bound to the viewModel.
        viewModelBound: function () {
            var widget = this;
        },

        _buildViewTemplate: function () {
            var widget = this;
        },

        _updatePriceBreaksThrottled: $.noop,

        _getViewModel: function () {
            var widget = this;

            var viewModel = $.extend(kendo.observable(widget.options), {
                useOrderEntry: widget.options.useOrderEntry,
                isProcessing: false,
                isAddingToCart: false,
                isAddingToFavourites: false,

                enableOrderLineNotes: function () {
                    return this.get("useOrderEntry") && widget.options.enableOrderLineNotes;
                },

                useCostCentres: function () {
                    return this.get("useOrderEntry") && widget.options.useCostCentres;
                },

                quantity: widget.options.forceOrderPackQty ? (widget.options.quantity.toString().length > 0 ? widget.options.packQuantity : widget.options.quantity) : widget.options.quantity,
                lastValidQuantity: widget.options.quantity,
                allowOrderEntryForProduct: widget.options.allowOrderEntryForProduct,
                isFavourite: widget.options.isFavourite,

                addProductKeyUp: function (event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();

                        this.addToCart();
                        if (widget.options.blurElementOnKeyup) {
                            $(event.srcElement).blur();
                        }
                    }
                },

                // This never gets changes from the original assigned product code (master product code)
                masterProductCode: $.cv.util.hasValue(widget.options.masterProductCode) ? widget.options.masterProductCode : widget.options.productCode,

                // Product Code might change to the attributed product code and the UI updated 
                // with the attributed product information...
                productCode: widget.options.productCode,
                
                codeLastUsedToGetProdData:'',
                productData: {},
                populateProductData: function(){
                    var vm = this,
                        prodcode = vm.get("productCode");
                
                    if(vm.get("codeLastUsedToGetProdData") != prodcode){
                        vm.set("codeLastUsedToGetProdData",prodcode);
                        vm.set('forceNextQtyCheck',true);
                        vm.updatePriceBreaks();
                        var productData = $.cv.css.product.getProductDetail({productCode:prodcode}).
                            success(function(data){
                                widget.viewModel.set("productData",data.data[0])
                            });
                    }
                },
                
                pricePerUnit: widget.options.priceForOne,
                extendedPrice: isNaN(widget.options.quantity) || isNaN(widget.options.priceForOne.toString().replace("$", "")) ? 0 : widget.options.quantity * widget.options.priceForOne.toString().replace("$", ""),
                lastCheckedQty: 0.00,
                forceNextQtyCheck: false,
                
                _setExtendedPrice: function () {
                    var vm = this,
                        price = vm.get("pricePerUnit").toString().replace("$", ""),
                        qty = vm.get('quantity'),
                        extendedPrice = 0;

                    if (isNaN(qty) || isNaN(price)) {
                        extendedPrice = 0;
                    } else {
                        extendedPrice = qty * parseFloat(price);
                    }
                    vm.set("extendedPrice", extendedPrice);
                    $.cv.css.trigger($.cv.css.eventnames.productExtendedPriceUpdated, {
                        productCode: vm.get("productCode"),
                        extendedPrice: extendedPrice
                    });
                },

                _updatePriceBreaks: function () {
                    var vm = this,
                        qty = vm.get('quantity');

                    if (qty == vm.get('lastCheckedQty') && vm.get('forceNextQtyCheck') == false)
                        return;

                    vm.set('lastCheckedQty', qty);

                    $.cv.css.product.getPriceForQty({
                        productCode: vm.get('productCode'),
                        quantity: qty
                    }).
                            success(function (data) {
                                widget.viewModel.set("pricePerUnit", parseFloat(data.data));
                                vm._setExtendedPrice();
                            });
                },

                updatePriceBreaks:function(){
                    /*
                        Call a throttled version of the method
                        Prevents multiple calls when a user clicks on plus or minus on a kendo numeric textbox
                        or makes multiple quantity chaanges in quick succession
                    */
                    widget._updatePriceBreaksThrottled();
                },
                
                getQuantity:function () {
                    var vm = this;
                    return vm.get('quantity');
                },
                
                setQuantity:function (qty) {
                    var vm = this;
                    vm.set('quantity', qty);
                },
                
                isMasterProduct:function(){
                    var vm = this;
                    return vm.get("attributedProductList") != null && vm.get("attributedProductList").length > 0
                },
               
                // This will return the current attributes selected for this product.
                // If the product is not a master product but has an attributed product it can be changed.
                productAttributesHasBeenInit: false,
                currentProductAttributesLocal: [],
                currentProductAttributes: function(){
                    var vm = this,
                        allPossibleCombinations = vm.get("attributedProductList");
                        
                    if (vm.get("productAttributesHasBeenInit") == false){
                        vm.set("productAttributesHasBeenInit",true);
                        var temp=[],
                            i=0,
                            isFirst=true;
                        
                        //  if we are a master product we should have a list of options.
                        if (vm.isMasterProduct() && allPossibleCombinations.length > 0)
                        {
                            for(var attr in allPossibleCombinations[0][2])
                            // We only need one row as any product will always have all of the 
                            // Attr Types. ie color and size
                            {
                                var isSelect = attr.split('_')[1] == 'D';
                                temp.push({
                                    nameForDisplay:attr.split('_')[0],
                                    name:attr,
                                    val:"",
                                    index:i,
                                    isSelect: isSelect,
                                    showRadio: isFirst && !isSelect,
                                    showSelect: isFirst && isSelect,
                                    showPleaseSelect: !isFirst,
                                    updateShowSettings: function(){
                                        var observer = this,
                                        checkIDX = widget.viewModel.get("currentAttributeIDXToSelectNext");
                                        
                                        val = (checkIDX >= observer.get("index") || checkIDX == -1);
                                        
                                        observer.set("showPleaseSelect",!val);
                                        if(observer.isSelect == true){
                                            observer.set("showSelect",val);
                                        }else{
                                            observer.set("showRadio",val);
                                        }
                                    },

                                    // This will change depending on what the current previous attributes are.
                                    attributeOptionsSource: [],
                                    
                                    updateAttributeOptionsSource:function(){
                                        var observer = this,
                                        productViewModel = observer.parent().parent(),
                                        allPossibleCombinations = productViewModel.get("attributedProductList"),
                                        currentIndex = observer.index,
                                        local = productViewModel.currentProductAttributesLocal,
                                        tempObject = [],
                                        dataObject = observer.isSelect == true ? [
                                            kendo.observable({id:"",text:"Please Select",attrName:observer.name})
                                        ] : [];
                                        
                                        // Get all distinct options.
                                        if (allPossibleCombinations){
                                            for(var i = 0; i < allPossibleCombinations.length; i++){
                                                var invalid = false;
                                                // Check the current selected items
                                                for(var g = 0; g < local.length; g++)
                                                {
                                                    // Up until the current index
                                                    if(local[g].index < currentIndex && 
                                                    // If the combination we are checking has a value = current then its all good.
                                                    local[g].val != allPossibleCombinations[i][2][local[g].name])
                                                    {
                                                        invalid = true;
                                                    }
                                                }
                                                var data = allPossibleCombinations[i][2][observer.name];
                                                if(!_.contains(tempObject,data) && !invalid){
                                                    tempObject.push(data);
                                                }
                                            }
                                            // Now to return an observable obj.
                                            for(var q = 0; q < tempObject.length; q++)
                                            {
                                                dataObject.push(kendo.observable({
                                                    id:tempObject[q],
                                                    text:tempObject[q],
                                                    attrName:observer.name
                                                }));
                                            }
                                        }
                                        observer.set("attributeOptionsSource",dataObject);
                                    },
                                    
                                    // In a perfect world(/widget) this shouldn't be needed.
                                    // I should be able to just put a get into options of the.
                                    // productViewModel.get("currentAttributeIDXToSelectNext");
                                    // But it doesn't seem to be correctly applying the listener hence the following.
                                    updateWidget:function(){
                                        var observer = this,
                                        productViewModel = widget.viewModel;
                                        
                                        var local = productViewModel.currentProductAttributesLocal;
                                        
                                        for (var q = 0 ; q < local.length ; q++)
                                        {
                                            if(observer.index < local[q].index){
                                                local[q].set("val","");
                                                local[q].updateAttributeOptionsSource();
                                            }
                                            productViewModel.updateCurrentAttributeIDXToSelectNext();
                                            local[q].updateShowSettings();
                                        }
                                        
                                    }
                                });
                                isFirst = false;
                                i++;
                            }
                        }
                        
                        vm.set("currentProductAttributesLocal",temp);
                        var list = vm.get("currentProductAttributesLocal");
                        for (var i = 0; i < list.length; i ++){
                            list[i].updateAttributeOptionsSource();
                        }
                        
                        // At this point we need to check if and of our products are the default and if they are select them.
                        for(var g = 0; g < allPossibleCombinations.length; g++){
                            if(allPossibleCombinations[g][1] == true){
                                for (var i = 0; i < list.length; i++){
                                    list[i].set("val",allPossibleCombinations[g][2][list[i].name]);
                                    list[i].updateWidget();
                                }
                                break;
                            }
                        }
                        
                    // I'm not attributed or master product if I am here and nothing was done.
                    }
                    return vm.get("currentProductAttributesLocal");
                },

                currentAttributeIDXToSelectNext:0,
                
                updateCurrentAttributeIDXToSelectNext: function(){
                    var vm = this,
                    // All of the attributes for the product with and without vals
                    attrList = vm.currentProductAttributes();
                    if(attrList && attrList.length !=0){
                        for(var a = 0; a <attrList.length ; a++)
                        {
                            if(attrList[a].val == ""){
                                // The first one we find without a value is the index we want.
                                vm.set("currentAttributeIDXToSelectNext", a);
                                vm.setProductCodeToAttributedProductCode();
                                return;    
                            }        
                        }
                    }
                    // Everything is selected. So return -1
                    vm.set("currentAttributeIDXToSelectNext",-1);
                    vm.setProductCodeToAttributedProductCode();
                },
                
                pleaseSelectAttributeText: function(){
                    var vm = this,
                    attrList = vm.currentProductAttributes();
                    attrIndex = vm.get("currentAttributeIDXToSelectNext");
                    
                    if(attrList && attrList.length != 0 && attrIndex != -1){
                        return widget.options.pleaseSelectAttributeText.replace("{0}", attrList[attrIndex].nameForDisplay);
                    }
                    return "";
                },
                
                isAllAttributesSelected: function(){
                    var vm = this,
                    attrIndex = vm.get("currentAttributeIDXToSelectNext");

                    return attrIndex == -1;
                },
                
                setProductCodeToAttributedProductCode: function () {
                    /*
                     * The ProductCode is updated to the Attributed Product ProductCode so that that visuals can change... i.e.
                     * we would load the attributed product details to get the image, description, prices etc which might be
                     * different to the master product.
                     */
                    var vm = this;
                    
                    if(vm.isAllAttributesSelected() == true){
                        allPossibleCombinations = vm.get("attributedProductList"),
                        local = vm.currentProductAttributesLocal;
                        
                        // Get all distinct options.
                        if (allPossibleCombinations){
                            for(var i = 0; i < allPossibleCombinations.length; i++){
                                var valid = true;
                                for(var g = 0; g < local.length; g++)
                                {
                                    // Up until the current index
                                    if(local[g].val != allPossibleCombinations[i][2][local[g].name])
                                    {
                                        valid = false;
                                        break;
                                    }
                                }
                                if(valid == true){
                                    vm.set("productCode",allPossibleCombinations[i][0]);
                                    vm.populateProductData();
                                    return;
                                }
                            }
                        }
                    }
                    // If not then revert
                    vm.set("productCode",widget.options.productCode);
                },

                itemsOnOrder: widget.options.itemsOnOrder.toString().length == 0 ? 0 : (isNaN(widget.options.itemsOnOrder) ? 0 : widget.options.itemsOnOrder),

                availableQty: widget.options.availableQty,

                orderedMoreThanAvailable: function () {
                    return this.get("itemsOnOrder") > this.get("availableQty");
                },

                costCentreCode: widget.options.defaultCostCentreCode,

                productNotes: '',
                message: '',

                clearExistingMessages: widget.options.clearExistingMessages,
                triggerMessages: widget.options.triggerMessages,

                setMessage: function (message, type) {
                    var vm = this;
                    
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                getProductAddParams: function () {
                    var vm = this,
                        params = {},
                        addAttributedProductCode = widget.options.addSelectedAttributeCodeToOrder || (widget.options.masterProductCode != null && widget.options.masterProductCode != widget.options.productCode);

                    // WARNING(jwwishart): 
                    // When you are dealing with attributed products you need to
                    //  pass to the add line to current service, the MASTER product code and the 
                    //  attribute values of the product that we want to add (i.e. what is in AttributeTitleValueSeq fiel
                    //  ProductAttributeProduct...
                    // This widget will adjust the productCode value based on the product that matches the 
                    //  selected attributes so that the product information will be updated (different prices, different description,
                    //  different product image etc...
                    //  Do to all the above, we need t o use to use the masterProductCode as it never changes from what is bound!
                    // ADDITION(athomas):
                    // The caveat to this is when the widget setting addSelectedAttributeCodeToOrder is set to true or the widget.options.masterProductCode != widget.options.productCode
                    params.productCode = addAttributedProductCode ? vm.get("productCode") : vm.get("masterProductCode");
                    params.quantity = vm.get("quantity");
                    params.costCentre = vm.get("costCentreCode");
                    params.notes = vm.get("productNotes");
                    params.noteIsExtendedLineDescription = widget.options.noteIsExtendedLineDescription;
                    params.refreshOrderTimeout = widget.options.addToCartRefreshTime;

                    var attributedProductList = addAttributedProductCode ? [] : vm.get('attributedProductList');

                    if (!addAttributedProductCode && vm.get('attributesForProduct') && vm.get('attributesForProduct').length > 0) {
                        // SCENARIO: Product Attribute List widget - it will set the attributesForProduct 
                        // option on this sub widget instance ... we just need to split it.
                        params.attributes = vm.get('attributesForProduct').split(';');
                    } else if (attributedProductList && attributedProductList.length > 0) {
                        // SCENARIO: Product widget rendering a single Product
                        // Get the attribute values for the currently selected product
                        var attributedProductInfo =_.find(attributedProductList, function(item) {
                            var attributedProductCode = item[0];

                            return attributedProductCode === vm.get('productCode');
                        });

                        if ($.cv.util.hasValue(attributedProductInfo)) {
                            var attributes = [];
                            var selectedAttributesValues = attributedProductInfo[2];

                            // We have to go through this and extract the value in the correct ORDER
                            // otherwise we might send things in the WRONG order! :o( that would be bad!
                            _.each(vm.currentProductAttributes(), function(attributeInformation, index) {
                                var key = attributeInformation.name; // This is just "name" minus display type: i.e. _D or _R on the end...
                                var value = selectedAttributesValues[key];

                                // nameForDisplay doesn't have the _D or _R suffix (which we don't want in the
                                // data we send...
                                attributes.push(attributeInformation.nameForDisplay + ':' + value);
                            });

                            params.attributes = attributes;
                        }
                    }

                    return params;
                },

                resetDefaults: function (params) {
                    var vm = this;
                
                    vm.set('itemsOnOrder', parseInt(vm.get("itemsOnOrder")) + parseInt(params.quantity));
                    vm.set("quantity", widget.options.quantity);
                    vm.set("lastValidQuantity", '');
                    vm.set("productNotes", '');
                    vm.set("costCentreCode", widget.options.defaultCostCentreCode);
                },

                triggerAddedToCart: function (msg) {
                    var vm = this;

                    widget.trigger(PRODUCTADDEDTOCART, {
                        productCode: vm.get("productCode"),
                        quantity: vm.get("quantity") == "" ? 1 : vm.get("quantity"),
                        msg: msg
                    });

                    if (widget.options.triggerMessages) {
                        if (msg.length == 0) {
                            vm.setMessage(widget.options.textProductAddedToCart, $.cv.css.messageTypes.success);
                        } else {
                            vm.setMessage(msg, $.cv.css.messageTypes.success);
                        }
                    }
                },

                triggerAddToCartFail: function (msg) {
                    var vm = this;
                    
                    widget.trigger(PRODUCTADDTOCARTFAIL,{ 
                        productCode: vm.get("productCode"),
                        errorMessage: msg 
                    });
                    
                    vm.setMessage(msg, $.cv.css.messageTypes.error);
                },

                triggerAddedToFavourites: function (msg) {
                    var vm = this;
                    
                    msg = typeof msg !== 'undefined' ? msg : '';
                    widget.trigger(PRODUCTADDEDTOFAVOURITES,{ 
                        productCode: vm.get("productCode"),
                        msg: msg 
                    });
                    
                    vm.setMessage(msg, $.cv.css.messageTypes.success);
                },

                triggerAddToFavouritesFail: function (msg) {
                    var vm = this;
                    
                    widget.trigger(PRODUCTADDTOFAVOURITESFAIL,{ 
                        productCode: vm.get("productCode"),
                        errorMessage: msg 
                    });
                    
                    vm.setMessage(msg, $.cv.css.messageTypes.error);
                },

                triggerRemoveFavourite: function (msg) {
                    var vm = this;
                    
                    widget.trigger(PRODUCTREMOVEDFROMFAVOURITES,{ 
                        productCode: vm.get("productCode"),
                        msg: msg 
                    });
                    
                    vm.setMessage(msg, $.cv.css.messageTypes.success);
                },

                triggerRemoveFavouriteFail: function (msg) {
                    var vm = this;
                    
                    widget.trigger(PRODUCTREMOVEDFROMAVOURITESFAIL,{ 
                        productCode: vm.get("productCode"),
                        errorMessage: msg 
                    });
                    
                    vm.setMessage(msg, $.cv.css.messageTypes.error);
                },

                isQuantityValid: function () {
                    var vm = this,
                        valid = true, 
                        quantity = vm.getQuantity();
                    
                    if (isNaN(quantity) || (!widget.options.allowDecimals && quantity % 1 != 0) || quantity <= 0) {
                        valid = false;
                    }
                    if (!valid) {
                        vm.setQuantity(vm.get("lastValidQuantity"));
                    } else {
                        vm.set("lastValidQuantity", quantity);
                    }
                    return valid;
                },

                addToCart: function () {
                    var vm = this,
                        params = vm.getProductAddParams();

                    // Check if master product and all options are selected
                    if(vm.isMasterProduct() && !vm.isAllAttributesSelected()) {
                        vm.triggerAddToCartFail(vm.pleaseSelectAttributeText().length > 0 ? vm.pleaseSelectAttributeText() : widget.options.defaultPleaseSelectAttribute);
                        return;
                    }

                    // Validate
                    if (params.quantity.length == 0 && vm.get("autoPopulateQty") == true) {
                        if (widget.options.forceOrderPackQty) {
                            params.quantity = widget.options.packQuantity;
                            vm.set('quantity', widget.options.packQuantity);
                        } else {
                            params.quantity = 1;
                            vm.set('quantity', 1);
                        }
                    }
                    else if(params.quantity.length == 0){
                        // If we are doing an add all with no qty just bail out here
                        return null;
                    }
                    else if (widget.options.forceOrderPackQty && params.quantity % widget.options.packQuantity != 0) {
                        vm.triggerAddToCartFail(widget.options.pleaseOrderInPackQuantity.format(widget.options.packQuantity));
                        return;
                    }
                    
                    vm.clearErrorMessage();

                    if (!vm.isQuantityValid()) {
                        vm.triggerAddToCartFail(widget.options.textQuantityMustBeNumeric);
                        return null;
                    }

                    vm.set("isProcessing", true);
                    vm.set("isAddingToCart", true);

                    return $.cv.css.addToCurrentOrder(params).done(function (response) {
                        vm.set("isProcessing", false);
                        vm.set("isAddingToCart", false);

                        if (response.data && response.data.editOrderOk === true) {
                            vm.triggerAddedToCart(response.data.message);
                        } else {
                            vm.triggerAddToCartFail(response.data.message);
                            params.quantity = 0;
                        }

                        vm.resetDefaults(params);
                    });
                },

                addToFavourites: function () {
                    var vm = this,
                        pCode = vm.get("productCode").toString(),
                        addSuccess = false,
                        message = "";
                        
                    //var d = $.cv.ajax.call('userFavourites/AddFavourite', {parameters: { productCode: pCode }});
                    vm.set("isProcessing", true);
                    vm.set("isAddingToFavourites", true);
                    var d = $.cv.css.userFavourites.addFavourite({ productCode: pCode });
                    $.when(d).done(function (msg) {
                        vm.set("isProcessing", false);
                        vm.set("isAddingToFavourites", false);
                        vm.set("triggerMessages", widget.options.triggerFavouritesMessages);
                        vm.set("clearExistingMessages", true);
                        if ($.cv.css.userFavourites.defaults && $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite) {
                            message = msg.data.Messages[0];
                            if (msg.data.Success)
                                addSuccess = true;
                        } else {
                            if (msg.data == 'True') {
                                addSuccess = true;
                                message = widget.options.textProductAddedToFavourites;
                            } else {
                                if (msg.errorMessage != null)
                                    message = msg.errorMessage;
                                else
                                    message = widget.options.textDefaultErrorAddingToFavourites;
                            }
                        }
                        if (addSuccess) {
                            vm.triggerAddedToFavourites(message);
                            vm.set("isFavourite", true);
                        } else
                            vm.triggerAddToFavouritesFail(message);
                        vm.set("triggerMessages", widget.options.triggerMessages);
                        vm.set("clearExistingMessages", widget.options.clearExistingMessages);
                    });
                },

                removeFromFavourites: function () {
                    var vm = this,
                        pCode = vm.get("productCode").toString(),
                        removeSuccess = false,
                        message = "";
                        
                    //var d = $.cv.ajax.call('userFavourites/AddFavourite', {parameters: { productCode: pCode }});
                    vm.set("isProcessing", true);
                    vm.set("isAddingToFavourites", true);
                    $.cv.css.userFavourites.removeFavourites({ productCodes: [pCode] }).done(function (msg) {
                        vm.set("isProcessing", false);
                        vm.set("isAddingToFavourites", false);
                        vm.set("triggerMessages", widget.options.triggerFavouritesMessages);
                        vm.set("clearExistingMessages", true);

                        if (msg.data.toString().toLowerCase() === "true") {
                            removeSuccess = true;
                            message = widget.options.textDefaultRemoveFavouriteSuccess;
                            vm.set("isFavourite", false);
                        } else {
                            message = widget.options.textDefaultRemoveFavouriteFail;
                        }

                        if (removeSuccess) {
                            vm.triggerRemoveFavourite(message);
                            vm.set("isFavourite", false);
                        } else {
                            vm.triggerRemoveFavouriteFail(message);
                        }
                        vm.set("triggerMessages", widget.options.triggerMessages);
                        vm.set("clearExistingMessages", widget.options.clearExistingMessages);
                    });
                },

                clearErrorMessage: function () {
                    var vm = this;
                    
                    vm.set("message", "");
                    var clearExistingMessages = vm.get("clearExistingMessages");
                    vm.set("clearExistingMessages", true);
                    vm.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message,{
                            message: "",
                            type: '',
                            source: 'product',
                            clearExisting: vm.get("clearExistingMessages")
                        });
                    vm.set("clearExistingMessages", clearExistingMessages);
                }

            });

            if ($.cv.css.userFavourites.defaults) {
                $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite = true;
            }

            return viewModel;
        },

        getProductAddParams: function (isBulkAdd) {
            var widget = this;
            isBulkAdd = typeof isBulkAdd !== 'undefined' ? isBulkAdd : true;
            if ((isBulkAdd && widget.viewModel.quantity != '' && widget.viewModel.quantity > 0) || !isBulkAdd) {
                var params = widget.viewModel.getProductAddParams();
                return params;
            } else {
                return null;
            }
        },

        triggerAddedToCart: function (msg) {
            var widget = this;
            widget.viewModel.triggerAddedToCart(msg);
        },

        triggerAddToCartFail: function (msg) {
            var widget = this;
            widget.viewModel.triggerAddToCartFail(msg);
        },

        triggerAddedToFavourites: function (msg) {
            var widget = this;
            widget.viewModel.triggerAddedToFavourites(msg);
        },

        triggerAddToFavouritesFail: function (msg) {
            var widget = this;
            widget.viewModel.triggerAddToFavouritesFail(msg);
        },

        triggerRemoveFavourite: function (msg) {
            var widget = this;
            widget.viewModel.triggerRemoveFavourite(msg);
        },

        triggerRemoveFavouriteFail: function (msg) {
            var widget = this;
            widget.viewModel.triggerRemoveFavourite(msg);
        },

        resetDefaults: function (params) {
            var widget = this;
            widget.viewModel.resetDefaults(params);
        }
    };

    // register the widget
    $.cv.ui.widget(productWidget);
})(jQuery);
;
(function (e, t) { var n = "onBeforeSubmit"; var r = "onAfterSubmit"; var i = "onCancel"; var s = "onPopupClosed"; var o = { name: "productReviewWidget", options: { dynamicService: "productReview", viewTemplate: null, productCode: null, productDescription: null, reviewerName: "", reviewerEmail: "", submitButtonText: "Submit Review", cancelButtonText: "Cancel", popupId: null, fancyboxOptions: {}, ratings: [] }, events: [n, r, i, s], _viewModel: null, _view: null, _viewAppended: false, _errorClass: "error", _defaultPopupId: "productReviewPopup", initialise: function (t, n) { var r = this; var i = e(t).children(":first"); if (i.data("view")) { r.view = i.html() } else { if (!r.options.viewTemplate) { r.options.viewTemplate = r._getDefaultViewTemplate() } var s = kendo.template(r.options.viewTemplate); r.view = s(r.options); i.append(r.view); r._viewAppended = true } r.viewModel = r._getViewModel(); var o = e(r.element).children(":first"); kendo.bind(o, r.viewModel); e(".star").rating() }, destroy: function () { var e = this; e.element.removeData(e.options.prefix + e.options.name); if (e._viewAppended) { e.element.empty() } }, _getViewModel: function () { var t = this; var o = kendo.observable({ productDescription: "[PRODUCT]", name: "", email: "", location: "", title: "", summary: "", rating: "", submitButtonText: null, cancelButtonText: null, isSaving: false, isNotSaving: function () { return !this.get("isSaving") }, submit: function (i) { i.preventDefault(); this.set("showValidationErrors", true); if (this.isFormInvalid() || this.get("isSaving")) return; this.set("isSaving", true); e("input.star").rating("readOnly", true); var s = { productCode: t.options.productCode, name: this.name, email: this.email, location: this.location, title: this.title, text: this.summary, ratingCode: this.rating }; t.trigger(n, s); e.cv.ajax.call(t.options.dynamicService + "/SaveProductReview", { parameters: s, success: function () { t.trigger(r, s); if (t.options.popupId) { e.fancybox.close() } t.viewModel.resetForm() } }) }, cancel: function (n) { n.preventDefault(); t.trigger(i); if (t.options.popupId) { e.fancybox.close() } }, resetForm: function () { t.viewModel.set("showValidationErrors", false); t.viewModel.set("name", t.options.reviewerName); t.viewModel.set("email", t.options.reviewerEmail); t.viewModel.set("location", ""); t.viewModel.set("title", ""); t.viewModel.set("summary", ""); t.viewModel.set("rating", ""); t.viewModel.set("isSaving", false); e("input.star").rating("readOnly", false).rating("select", false) }, show: function (n) { n.preventDefault(); if (!this.isPopup()) return; var r = t.options.popupId ? t.options.popupId : t._defaultPopupId; var i = { href: "#" + r, afterClose: function () { t.viewModel.set("showValidationErrors", false); t.trigger(s) } }; i = e.extend(i, t.options.fancyboxOptions); e.fancybox(i) }, isPopup: function () { return t.options.popupId != null }, showValidationErrors: false, isFormInvalid: function () { return this.isFormValid() == false }, isFormValid: function () { return this.nameIsEmpty() == false && this.emailIsEmpty() == false && this.emailIsInvalid() == false && this.locationIsEmpty() == false && this.titleIsEmpty() == false && this.summaryIsEmpty() == false && this.ratingIsEmpty() == false }, nameIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("name")).length == 0 }, nameInputClass: function () { return this.nameIsEmpty() ? t._errorClass : "" }, emailIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("email")).length == 0 }, emailIsInvalid: function () { if (this.get("showValidationErrors")) { if (this.emailIsEmpty() == false) { var e = /\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/; return !e.test(this.get("email")) } return true } return false }, emailInputClass: function () { return this.emailIsEmpty() || this.emailIsInvalid() ? t._errorClass : "" }, emailErrorText: function () { if (this.emailIsEmpty()) { return "Required" } if (this.emailIsInvalid()) { return "Invalid" } return "" }, locationIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("location")).length == 0 }, locationInputClass: function () { return this.locationIsEmpty() ? t._errorClass : "" }, titleIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("title")).length == 0 }, titleInputClass: function () { return this.titleIsEmpty() ? t._errorClass : "" }, summaryIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("summary")).length == 0 }, summaryInputClass: function () { return this.summaryIsEmpty() ? t._errorClass : "" }, ratingIsEmpty: function () { return !!this.get("showValidationErrors") && e.trim(this.get("rating")).length == 0 }, ratingInputClass: function () { return this.ratingIsEmpty() ? t._errorClass : "" } }); if (t.options.productDescription == null) { e.cv.ajax.call(t.options.dynamicService + "/GetProductInfo", { parameters: { productCode: t.options.productCode }, success: function (e) { if (e.data.length == 1) { o.set("productDescription", e.data[0].Description) } } }) } else { o.set("productDescription", t.options.productDescription) } o.set("name", t.options.reviewerName); o.set("email", t.options.reviewerEmail); o.set("submitButtonText", t.options.submitButtonText); o.set("cancelButtonText", t.options.cancelButtonText); return o }, _getDefaultViewTemplate: function () { var t = this; if (t.options.ratings.length == 0) { e.cv.ajax.call(t.options.dynamicService + "/LoadRatings", { parameters: {}, async: false, success: function (e) { t.options.ratings = e.data } }) } var n = ["<section class='cv-ui-layout-pagecontent'>", "	<div class='row'>", "		<div class='twelve columns'>", "			<h1 class='page-heading'>Write A Product Review</h1>", "", "			<div class='cv-ui-widget-review'>", "				<div class='row'>", "					<div class='twelve columns'>", "						<p>If you have a <span style='font-weight: bold' data-bind='text: productDescription'></span>, let us know what you think. Write an opinion and share your opinions with others. All reviews will be independantly reviewed before being posted. Review must be on this particular product and not on the service or warranty issues.</p>", "					</div>", "				</div>", "", "				<div class='row'>", "					<div class='six columns'>", "						<div data-bind='attr: { class: nameInputClass }'>", "							<label style='display: inline'>Name:</label> <label class='error' style='display: inline-block'>*</label>", "							<input type='text' data-bind='attr: { class: nameInputClass }, value: name, enabled: isNotSaving' data-value-update='keyup' maxlength='100'>", "							<small data-bind='visible: nameIsEmpty'>Required</small>", "						</div>", "", "						<div data-bind='attr: { class: emailInputClass }'>", "							<label style='display: inline'>Email:</label> <label class='error' style='display: inline-block'>*</label>", "							<input type='email' data-bind='attr: { class: emailInputClass }, value: email, enabled: isNotSaving' data-value-update='keyup' maxlength='100'>", "							<small data-bind='visible: emailIsInvalid, text: emailErrorText'></small>", "						</div>", "", "						<div data-bind='attr: { class: locationInputClass }'>", "							<label style='display: inline'>Location:</label> <label class='error' style='display: inline-block'>*</label>", "							<input type='text' data-bind='attr: { class: locationInputClass }, value: location, enabled: isNotSaving' data-value-update='keyup' maxlength='100'>", "							<small data-bind='visible: locationIsEmpty'>Required</small>", "						</div>", "", "						<div data-bind='attr: { class: titleInputClass }'>", "							<label style='display: inline'>Review Title:</label> <label class='error' style='display: inline-block'>*</label>", "							<input type='text' data-bind='attr: { class: titleInputClass }, value: title, enabled: isNotSaving' data-value-update='keyup' maxlength='100'>", "							<small data-bind='visible: titleIsEmpty'>Required</small>", "						</div>", "", "						<div data-bind='attr: { class: summaryInputClass }'>", "							<label style='display: inline'>Your Review:</label> <label class='error' style='display: inline-block'>*</label>", "							<textarea data-bind='attr: { class: summaryInputClass }, value: summary, enabled: isNotSaving' data-value-update='keyup'></textarea>", "							<small data-bind='visible: summaryIsEmpty'>Required</small>", "						</div>", "					</div>", "", "					<div class='five columns'>", "						<div class='cv-ui-rating'>", "							<div class='row'>", "								<div class='five columns'>", "									<label class='overall' style='display: inline'>Overall Rating</label> <label class='error' style='display: inline-block'>*</label>", "								</div>", "								<div class='seven columns' data-bind='attr: { class: ratingInputClass }'>", "									" + t.getStarsForPlugin(), "									<div style='height: 2.5em'></div>", "									<small data-bind='visible: ratingIsEmpty'>Required</small>", "								</div>", "							</div>", "						</div>", "					</div>", "				</div>", "				", "				<hr />", "", "				<div class='row row-buttons'>", "					<div class='twelve columns' data-bind='visible: isNotSaving'>", "						<a href='javascript:$.noop()' class='cv-ui-button-green' data-reveal-id='modal-publish' data-bind='click: submit, text: submitButtonText'></a>", "						<a href='javascript:$.noop()' class='cv-ui-button-red' data-bind='click: cancel, text: cancelButtonText'></a>", "					</div>", "					<div class='twelve columns' data-bind='visible: isSaving'>", "						Review is being submitted. Please wait...<br /> ", "					</div>", "				</div>", "			</div>", "		</div>", "	</div>   ", "</section>"].join("\n"); if (t.options.popupId) { n = ["<div id='productReviewPopup' style='display: none; margin-left: auto; margin-right: auto'>", n, "</div>"].join("\n") } return n }, getStarsForPlugin: function () { var e = this; var t = ""; for (var n = 0; n < e.options.ratings.length; n++) { var r = e.options.ratings[n]; var i = n == 0 ? " required" : ""; t += "<input name='stars' type='radio' class='star" + i + "' value='" + r.Code + "' title=\"" + r.Description + "\" data-bind='checked: rating' />" } return t } }; e.cv.ui.widget(o) })(jQuery);
/* Name: current user account
* Author: Aidan Thomas 
* Created: 20130220
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.ajax.js
*          /Scripts/cv.util.js
* Params:  
*       productElement: 
*       productData: 
*/
;
(function ($, undefined) {

    var addAllToCartWidget = {


        // Standard Variables

        // widget name
        name: "addAllToCart",

        // default widget options
        options: {
			productElement: "[data-role='product']",
			productData: "product",
            // standard view options
            triggerMessages: true,
            // view flags
			sessionTimeOutRedirectUrl: 'login.aspx',
            textNoProductsAdded: "No items have been added to your cart, please enter a quantity"
        },

        // private property

        // Standard Methods
        initialise: function (el, o) {
            var widget = this, products = [], product = {}, productWidgets = [], errorMessages = "";
            $(widget.element).click(function () {
                $(el).addClass($.cv.css.isProcessingClass);
				$(widget.options.productElement).each(function() {
					var productWidget = $(this).data(widget.options.productData);
					if (productWidget) {
					    product = productWidget.getProductAddParams();
					    if (product && product.quantity.length != 0 && !isNaN(product.quantity)) {
					        productWidgets.push(productWidget);
					        products.push(product);
					    }
					}
				});
				if (products.length > 0) {
				    widget.clearMessage();

				    $.cv.css.orders.addToCurrentOrderBulk({
				        batchData: products
				    }).done(function (response) {
				        $.each(products, function (idx, item) {
				            var responseData = response.data[idx];

                            if (response.errorMessage[idx] == null &&
                                responseData != null &&
                                responseData.editOrderOk === true)
                            {
                                productWidgets[idx].triggerAddedToCart(responseData.message);
                            } else {
                                if (response.errorMessage[idx] != null) {
                                    productWidgets[idx].triggerAddToCartFail(response.errorMessage[idx]);
                                    errorMessages = errorMessages.length == 0 ? response.errorMessage[idx] : "," + response.errorMessage[idx];
                                }
                                if (responseData != null && responseData.message != '') {
                                    productWidgets[idx].triggerAddToCartFail(responseData.message);
                                }
                            }

                            productWidgets[idx].resetDefaults(products[idx]);

                            if (errorMessages.length > 0) {
                                widget.setMessage(errorMessages, $.cv.css.messageTypes.error);
                            }
                        });
				        $(el).removeClass($.cv.css.isProcessingClass);
                        products = [];
                        product = {};
                        productWidgets = [];
                        errorMessages = "";
                    }).fail(function (msg) {
                        var msg = JSON.parse(msg);
                        if (msg.sessionHasTimedOut)
                            widget.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, {}, true);
                        $(el).removeClass($.cv.css.isProcessingClass);
                        products = [];
                        product = {};
                        productWidgets = [];
                        errorMessages = "";
                    });
				} else {
				    widget.setMessage(widget.options.textNoProductsAdded, $.cv.css.messageTypes.warning);
				    $(el).removeClass($.cv.css.isProcessingClass);
				}
			});
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        redirectToUrl: function (fallbackUrl, params, includeInBrowserHistory) {
            if ($.cv.ajax.settings.timeoutRedirectUrl == "")
                $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
            else
                $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
        },

        setMessage: function (message, type) {
            var widget = this, vm = widget.viewModel;
            $.cv.util.notify(vm, message, type, {
                triggerMessages: widget.options.triggerMessages,
                source: widget.name,
                clearExisting: true
            });
        },

        clearMessage: function () {
            var widget = this;
            if (widget.options.triggerMessages)
                $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'addAllToCart', clearExisting: true });
        }

    }

    // register the widget

    $.cv.ui.widget(addAllToCartWidget);

})(jQuery);;
/*
* Name: Order summary
* Author: Aidan Thomas
* Date Created: 2013/01/04 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js (built with jquery-1.7.1.min.js)
*          kendo.web.js (kendo.web.min.js v2012.2.710)
*
*          --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.util.js
*          /Scripts/cv.ajax.js
*          /Scripts/cv.css.orders.js
*          /Scripts/cv.css.orderTemplate.js
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        WIDGETINITIALISED = "widgetInitialised",
		TEMPLATESAVED = "templateSaved",
        QUOTECANCELLED = "quoteCancelled",
        ORDERMODEORDER = "order",
		ORDERMODEQUOTE = "quote";


    var orderSummaryWidget = {

        // Standard Variables

        // widget name
        name: "orderSummary",

        // default widget options
        options: {
            // viewModel defaults
            linesElement: "[data-role='orderlines']",
            linesData: "orderLines",
            holdOrderRedirectUrl: "OrdersOnHold.aspx?Order={0}",
            saveAsTemplateRedirectUrl: "OrderTemplates.aspx?action=save&TemplateType=order",
            //requestQuoteUrl: "OrderComplete.aspx?type=quote",
            designStampUrl: "StampDesign.aspx",
            displayLineErrorsAsMessages: true,
            displayLineMessagesOnLines: true,
            displayErrorMessagesOnLoad: true,
            displayLineErrorSummaryMessage: true,
            hideHeaderMessageTypes: ["ForwardOrderDateInvalid", "OrderRequiresCustomerReference"],
            formatFieldsAsCurrency: [],
            headerCostCentreError: "InvalidCostCentresError",
            lineCostCentreError: "CostCentreError",
            // viewModel flags
            autoBind: true,
			triggerMessages: true,
			includeInBrowserHistory: true,
			reloadAfterOrderHeld: true,
			getLatestOrderOnLoad: false,
			displayingOrderInformation: true,
			displayingOrderMessages: false,
			saveAsTemplateUseModal: false,
			usingCostCentres: false,
            // events
			templateSaved: null,
            // view flags
			sessionTimeOutRedirectUrl: 'login.aspx',
			validateOnLoad: false,
			validateChanges: false,
            // view text defaults
			textCheckoutErrorDefaultMessage: 'There was an error preventing your checkout, please try again later',
			textCheckoutQuoteErrorDefaultMessage: 'There was an error preventing your quote, please try again later',
			textSaveAsTemplateSuccessful: 'Successfully saved your template',
			textRequestQuoteEmptyCartMessage: "Your cart is empty, please add products before you request a quote",
			textQuoteCancelSuccessful: "Successfully cancelled the quote",
			textSubmitOrderNoItems: "Please add a product to your order before submitting your order",
            textErrorLinesHaveErrors: "There are lines with errors on your order",
            // widget settings,
			enableOrderComplete: true,
			enableUpdateOrder: true,
			enableOrderHolding: false,
			enableSaveAsTemplate: false,
			enableRequestQuote: false,
			enableEmptyCart: false,
			enableDesignStamps: false,
			changeOrderAccount: false,
            hasMultipleAccounts: false,
			hidePricingInOrderGrid: false,
			isRep: false,
            isAdvancedQuoteOrder: false,
			textOrderCompleteButtonLabel: "Complete Order",
			textUpdateOrderButtonLabel: "Update Order",
			textOrderHoldingButtonLabel: "Hold Order",
			textSaveAsTemplateButtonLabel: "Save As Template",
			textRequestQuoteButtonLabel: "Request Quote",
			textEmptyCartButtonLabel: "Empty Cart",
			textDesignStampsButtonLabel: "Design Stamps",
            textCancelQuoteButtonLabel: "Cancel Quote",
            // view Template
            viewTemplate: null // TODO: Treat these as IDs, remove the last one.
        },

        events: [DATABINDING, DATABOUND, TEMPLATESAVED, QUOTECANCELLED, WIDGETINITIALISED],

        viewModel: null,

        view: null,

        // MVVM Support

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);

            $.cv.css.bind($.cv.css.eventnames.orderChanged,      $.proxy(widget.viewModel.cartUpdated, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.orderSubmitted,    $.proxy(widget.viewModel.cartUpdated, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.localOrderChanged, $.proxy(widget.viewModel.reloadCart,  widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.checkoutMessages,  $.proxy(widget.viewModel.checkoutOptionsUpdated, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.loggingOut, $.proxy(widget.viewModel.loggingOut, widget.viewModel));

            // detect url message and trigger message if order account set
            if (widget.options.displayingOrderMessages) {
                var messages = $.cv.css.localGetOrderSummaryMessages();
                if (messages != null && messages.length > 0) {
                    $.cv.css.localSetOrderSummaryMessages(null);
                    $.each(messages, function(index, item) {
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: item, type: $.cv.css.messageTypes.info, source: 'orderSummaryOrderMessage', clearExisting: false });
                    });
                }
            }
            widget.trigger(WIDGETINITIALISED);
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;
			
            var initDataSource = function () {
                if (widget.options.displayingOrderInformation) {
                    if (widget.options.getLatestOrderOnLoad) {
                        var d1 = $.cv.css.getCurrentOrder();
                        var _this = this
                        $.when(d1).done(function () {
                            $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                        });
                    } else {
                        if (viewModel.get("order") == null)
                            viewModel.reloadCart();
                        else {
                            if (widget.options.validateOnLoad && viewModel.items() > 0)
                                viewModel.validateOrder();
                        }
                    }
                }
            };

            var formatFieldsAsCurrency = function (order) {
                if (widget.options.formatFieldsAsCurrency.length > 0 && order != null) {
                    _.each(widget.options.formatFieldsAsCurrency, function (element, index, list) {
                        if (_.has(order, element)) {
                            order[element] = kendo.toString(order[element], 'c');
                        }
                    });
                }
                return order;
            };
            
            var viewModel = kendo.observable({

                // Properties for UI elements
                order: formatFieldsAsCurrency($.cv.css.localGetCurrentOrder()),

                enableOrderComplete: widget.options.enableOrderComplete,

                enableUpdateOrder: widget.options.enableUpdateOrder,

                enableOrderHolding: widget.options.enableOrderHolding,

                enableSaveAsTemplate: widget.options.enableSaveAsTemplate,

                enableRequestQuote: widget.options.enableRequestQuote,

                enableEmptyCart: widget.options.enableEmptyCart,

                enableCancelQuote: widget.options.isAdvancedQuoteOrder,

                enableDesignStamps: function () {
                    var order = this.get("order");
                    return order == null ? false : (order.HasStamps != "undefined" ? order.HasStamps : false);
                },

                enableAccountSelect: function () {
                    return this.get("isRep") || this.get("hasMultipleAccounts");
                },

                hasMultipleAccounts: widget.options.hasMultipleAccounts,

                changeOrderAccount: widget.options.changeOrderAccount,

                hidePricingInOrderGrid: widget.options.hidePricingInOrderGrid,

                isRep: widget.options.isRep,

                textOrderCompleteButtonLabel: widget.options.textOrderCompleteButtonLabel,

                textUpdateOrderButtonLabel: widget.options.textUpdateOrderButtonLabel,

                textOrderHoldingButtonLabel: widget.options.textOrderHoldingButtonLabel,

                textSaveAsTemplateButtonLabel: widget.options.textSaveAsTemplateButtonLabel,

                textRequestQuoteButtonLabel: widget.options.textRequestQuoteButtonLabel,

                textEmptyCartButtonLabel: widget.options.textEmptyCartButtonLabel,

                textDesignStampsButtonLabel: widget.options.textDesignStampsButtonLabel,

                textCancelQuoteButtonLabel: widget.options.textCancelQuoteButtonLabel,
										
                orderTotal: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmount') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmount'), 'c')) : (kendo.toString(0, 'c'));
                },

				orderTotalBeforeDiscount: function () {
				    return (this.get('order') != null && this.get('order.SoOrderTotalAmount') != undefined) ? (kendo.toString(this.get('order.SoOrderTotalAmount'), 'c')) : (kendo.toString(0, 'c'));
				},
                
				orderTotalAmountLessCharges: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountLessCharges') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountLessCharges'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountLessChargesCustomerDefined: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountLessChargesCustomerDefined') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountLessChargesCustomerDefined'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountLessChargesExTax: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountLessChargesExTax') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountLessChargesExTax'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountLessChargesIncTax: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountLessChargesIncTax') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountLessChargesIncTax'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountBeforeDiscountLessChargesCustomerDefined: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountBeforeDiscountLessChargesCustomerDefined') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountBeforeDiscountLessChargesCustomerDefined'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountBeforeDiscountLessChargesExTax: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountBeforeDiscountLessChargesExTax') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountBeforeDiscountLessChargesExTax'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalAmountBeforeDiscountLessChargesIncTax: function () {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountBeforeDiscountLessChargesIncTax') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountBeforeDiscountLessChargesIncTax'), 'c')) : (kendo.toString(0, 'c'));
				},
				
				orderTotalExTax: function() {
				    return (this.get('order') != null && this.get('order.OrderTotalAmountExGst') != undefined) ? (kendo.toString(this.get('order.OrderTotalAmountExGst'), 'c')) : (kendo.toString(0, 'c'));
				},

				taxRate: function () {
				    return (this.get('order') != null && this.get('order.TaxRate') != undefined) ? this.get('order.TaxRate') : 0;
				},
				
				orderTotalTax: function() {
				    return (this.get('order') != null && this.get('order.OrderTotalTax') != undefined) ? (kendo.toString(this.get('order.OrderTotalTax'), 'c')) : (kendo.toString(0, 'c'));
				},

				orderTotalCharges: function () {
				    return (this.get('order') != null && this.get('order.SoOrderTotalCharges') != undefined) ? (kendo.toString(this.get('order.SoOrderTotalCharges'), 'c')) : (kendo.toString(0, 'c'));
				},

				promotionalDiscountAmount: function () {
				    return (this.get('order') != null && this.get('order.SoPromotionalDiscountAmount') != undefined) ? (kendo.toString(this.get('order.SoPromotionalDiscountAmount'), 'c')) : (kendo.toString(0, 'c'));
				},

				promotionalDiscountAmountIncGst: function () {
				    return (this.get('order') != null && this.get('order.SoPromotionalDiscountAmountIncGST') != undefined) ? (kendo.toString(this.get('order.SoPromotionalDiscountAmountIncGST'), 'c')) : (kendo.toString(0, 'c'));
				},

				promotionalDiscountAmountExGst: function () {
				    return (this.get('order') != null && this.get('order.SoPromotionalDiscountAmountExGST') != undefined) ? (kendo.toString(this.get('order.SoPromotionalDiscountAmountExGST'), 'c')) : (kendo.toString(0, 'c'));
				},

				promotionalDiscountTax: function () {
				    return (this.get('order') != null && this.get('order.SoPromotionalTaxAmount') != undefined) ? (kendo.toString(this.get('order.SoPromotionalTaxAmount'), 'c')) : (kendo.toString(0, 'c'));
				},

				hasPromotionDiscount: function () {
				    return (this.get('order') != null && this.get('order.SoPromotionalDiscountAmount') != undefined) ? this.get('order.SoPromotionalDiscountAmount') > 0 : false;
				},

				hasValidPromotionCodes: function () {
				    var count = 0;
				    if (this.get('order') != null && this.get('order.PromotionalCodes') != undefined) {
				        $.each(this.get('order.PromotionalCodes'), function (index, element) {
				            if (element.IsValidOnCurrentOrder) {
				                count++;
				            }
				        });
				    }
				    return count > 0;
				},

				hasPromotionCodes: function () {
				    return (this.get('order') != null && this.get('order.PromotionalCodes') != undefined) ? this.get('order.PromotionalCodes').length > 0 : false;
				},

				hasOrderCharges: function () {
				    return (this.get('order') != null && this.get('order.SoOrderTotalCharges') != undefined) ? this.get('order.SoOrderTotalCharges') > 0 : false;
				},

				orderTotalChargesLessFreightIncGst: function () {
				    var orderTotalCharge = (this.get('order') != null && this.get('order.SoOrderTotalCharges') != undefined) ? this.get('order.SoOrderTotalCharges') : 0;
				    var freightChargeAmount = (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? this.get('order.FreightChargeAmount') : 0;
				    return (kendo.toString((orderTotalCharge - freightChargeAmount), 'c'));
				},

				orderTotalChargesLessFreightExGst: function () {
				    var orderTotalCharge = (this.get('order') != null && this.get('order.SoOrderTotalCharges') != undefined) ? this.get('order.SoOrderTotalCharges') : 0;
				    var freightChargeAmount = (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? this.get('order.FreightChargeAmount') : 0;
				    var tax = (orderTotalCharge - freightChargeAmount) / (1 + 1 / (this.taxRate() / 100));
				    return (kendo.toString((orderTotalCharge - freightChargeAmount - tax), 'c'));
				},

				hasOrderChargesLessFreight: function () {
				    var orderTotalCharge = (this.get('order') != null && this.get('order.SoOrderTotalCharges') != undefined) ? this.get('order.SoOrderTotalCharges') : 0;
				    var freightChargeAmount = (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? this.get('order.FreightChargeAmount') : 0;
				    return (orderTotalCharge - freightChargeAmount) > 0;
				},

				freightChargeAmount: function () {
				    return (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? (kendo.toString(this.get('order.FreightChargeAmount'), 'c')) : (kendo.toString(0, 'c'));
				},

				freightChargeAmountExTax: function () {
				    return (this.get('order') != null && this.get('order.FreightChargeAmountExTax') != undefined) ? (kendo.toString(this.get('order.FreightChargeAmountExTax'), 'c')) : (kendo.toString(0, 'c'));
				},

				hasFreightCharge: function () {
				    var freightChargeAmount = (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? this.get('order.FreightChargeAmount') : 0;
				    return freightChargeAmount > 0 || this.freightChargeLines().length > 0;
				},

				hasZerDollarFreightCharge: function () {
				    var freightChargeAmount = (this.get('order') != null && this.get('order.FreightChargeAmount') != undefined) ? this.get('order.FreightChargeAmount') : 0;
				    return freightChargeAmount == 0 && this.freightChargeLines().length > 0;
				},

				freightChargeLines: function () {
				    return (this.get('order') != null && this.get('order.FreightChargeLine') != undefined && this.get('order.FreightChargeLine') != null) ? this.get('order.FreightChargeLine') : [];
				},
				
				lineCount: function () {
				    var data = this.get('order');
				    return (data != null && data.ItemLineCount != undefined) ? data.ItemLineCount : 0;
				},

				isNotSingleLine: function () {
				    return this.lineCount() != 1;
				},

				items: function () {
				    var data = this.get('order');
				    return (data != null && data.SoOrderPackages != undefined) ? data.SoOrderPackages : 0;
				},

				isNotSingleItem: function () {
				    return this.items() != 1;
				},
				
				templateName: '',
				
				holdReference: '',
				
				orderSubmitting: false,

                processing: false,
                isLoggingOut: false,

                isProcessing: function () {
                    return this.get("processing");
                },

                isSavingAsTemplate: false,
                isHoldingOrder: false,
                isUpdatingOrder: false,
                isEmptyingCart: false,
                isRequestingQuote: false,

                validationPreventingCheckout: false,

                preventCheckout: false,

                cartUpdated: function (opts) {
                    var _this = this,
                        d1 = $.Deferred(),
                        localOrder = null,
                        order = $.cv.css.localGetCurrentOrder(widget.options.displayingOrderInformation),
                        preventReValidation = opts ? opts.preventValidation : false;

                    if (order != null && $.isFunction(order.promise)) {
                        d1 = order;
                    } else {
                        d1.resolve();
                    }

                    d1.done(function () {
                        localOrder = $.cv.css.localGetCurrentOrder(false);

                        _this.set('order', formatFieldsAsCurrency(localOrder));
                        _this.clearMessage();

                        if (!preventReValidation && // prevents validation when validation caused the update!
                            (widget.options.validateOnLoad || widget.options.validateChanges) &&
                            !_this.get("isLoggingOut") &&
                            !_this.get("orderSubmitting") && _this.items() > 0) {
                            _this.validateOrder();
                        }

                        if (_this.items() == 0) {
                            $.cv.css.localRemoveUpdatedCurrentOrderLines();
                        }
                    });
                },

                reloadCart: function (opts) {
                    // local data already updated, just trigger event
                    var _this = this
                    _this.cartUpdated(opts);
                },

				checkoutOptionsUpdated: function(msg) {
					if(msg.allCheckoutMessagesConfirmed != undefined) {
						this.set("preventCheckout", !msg.allCheckoutMessagesConfirmed);
					}
				},

				loggingOut: function () {
				    this.set("isLoggingOut", true);
				},

				redirectToUrl: function (fallbackUrl, params, includeInBrowserHistory) {
				    if ($.cv.ajax.settings.timeoutRedirectUrl == "")
				        $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
				    else
				        $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
				},

				message: '',
				
				clearExistingMessages: true,

				displayErrorMessagesOnLoad: widget.options.displayErrorMessagesOnLoad,
									
				setMessage: function(message,type) {
				    $.cv.util.notify(this, message, type, {
				        triggerMessages: widget.options.triggerMessages,
				        source: widget.name
				    });
				},
				
				clearMessage: function() {
					this.set("clearExistingMessages",true);
					this.set("message","");
					if(widget.options.triggerMessages)
						$.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'orderSummary', clearExisting: this.get("clearExistingMessages") });
				},

				displayLineMessages: function (msgs, linesToValidateString) {
				    $(widget.options.linesElement).each(function () {
				        var linesWidget = $(this).data(widget.options.linesData);
				        if (linesWidget)
				            linesWidget.displayLineMessages(msgs, linesToValidateString);
				    });
				},
				
                updateAllLines: function() {
					$(widget.options.linesElement).each(function() {
						var linesWidget = $(this).data(widget.options.linesData);
						if(linesWidget)
							linesWidget.updateAllLines();
					});
                },

                updateLinesFlaggedForUpdate: function (displayMessages, triggerOrderRefresh) {
                    var vm = this, updates = {}, allUpdates = new $.Deferred(), deletes = new $.Deferred();
                    deletes = this.deleteLinesFlaggedForDelete(false, false);
                    vm.set("isUpdatingOrder", true);
                    $(widget.options.linesElement).each(function () {
                        var linesWidget = $(this).data(widget.options.linesData);
                        if (linesWidget)
                            updates = linesWidget.updateLinesFlaggedForUpdate(displayMessages, triggerOrderRefresh);
                    });
                    $.when(updates, deletes).done(function () {
                        allUpdates.resolve();
                        vm.set("isUpdatingOrder", false);
                    });
                    return allUpdates;
                },
                
                deleteAllLines: function() {
                    var vm = this, d = {};
                    vm.set("isEmptyingCart", true);
                    $(widget.options.linesElement).each(function () {
						var linesWidget = $(this).data(widget.options.linesData);
						if(linesWidget)
						    d = linesWidget.deleteAllLines();
                    });
                    $.when(d).done(function () {
                        vm.set("isEmptyingCart", false);
                    });
                },

                deleteLinesFlaggedForDelete: function (displayMessages, triggerOrderRefresh) {
                    var d = new $.Deferred();
                    $(widget.options.linesElement).each(function () {
                        var linesWidget = $(this).data(widget.options.linesData);
                        if (linesWidget)
                            d = linesWidget.deleteLinesFlaggedForDelete(displayMessages, triggerOrderRefresh);
                        else
                            d.resolve();
                    });
                    return d;
                },

                getLinesToValidate: function () {
                    var _this = this, updatedLines = [], addedLines = [], linesToValidate = [], linesToValidateString = '', localUpdatedCurrentOrderLines = {};
                    localUpdatedCurrentOrderLines = $.cv.css.localGetUpdatedCurrentOrderLines();
                    if (localUpdatedCurrentOrderLines == null)
                        localUpdatedCurrentOrderLines = { addedLines: [], updatedLines: [], linesDeleted: false };
                    // if there is a list of updated lines in local storage on validate those particular lines
                    updatedLines = localUpdatedCurrentOrderLines["updatedLines"];
                    // if there is a list of new lines in local storage on validate those particular lines
                    addedLines = localUpdatedCurrentOrderLines["addedLines"];
                    if (updatedLines != null && addedLines != null)
                        linesToValidate = _.union(updatedLines, addedLines);
                    else {
                        if (updatedLines != null)
                            linesToValidate = updatedLines;
                        else if(addedLines != null)
                            linesToValidate = addedLines;
                    }
                    if (linesToValidate != null && linesToValidate.length > 0) {
                        linesToValidateString = _.reduce(linesToValidate, function (memo, num) { return memo + "," + num; });
                    } else {
                        // if there are no updated or new lines check if any have been deleted
                        if (localUpdatedCurrentOrderLines["linesDeleted"] != undefined && localUpdatedCurrentOrderLines["linesDeleted"])
                            linesToValidateString = "0"; // if only lines deleted only need to validate order header so pass in non existent line number
                    }
                    return linesToValidateString.toString();
                },

                validateOrder: function (orderMode) {
                    var vm = this,
                        linesToValidateString = '',
                        order = vm.get("order");

                    if (order && order._objectKey) {
                        var validationMode = typeof orderMode !== 'undefined' ? "checkout" : "cart";

                        linesToValidateString = vm.getLinesToValidate();

                        vm.set("validationPreventingCheckout", false);

                        $.cv.css.trigger($.cv.css.eventnames.validateOrderStarted);

                        $.cv.css.orders.validateForCheckout({
                            _objectKey: order._objectKey,
                            validationMode: validationMode,
                            linesToValidate: linesToValidateString
                        }).done(function (response) {
                            var data = response.data,
                                params = {};

                            $.cv.css.localRemoveUpdatedCurrentOrderLines();

                            // Lines Changed During Validation Require Merging into Local Storage
                            // Modification due to promo codes for example
                            if (data.modifiedLines && data.modifiedLines.length > 0) {
                                $.cv.css.localUpdateCurrentOrderLines(data.modifiedLines);

                                $.cv.css.trigger($.cv.css.eventnames.localOrderChanged, { preventValidation: true });
                            }

                            $.cv.css.trigger($.cv.css.eventnames.validateOrderComplete);

                            if (!data.sessionHasTimedOut) {
                                vm.displayValidationMessages(data, linesToValidateString);

                                if (!vm.get("validationPreventingCheckout") && vm.get("orderSubmitting")) {
                                    if (vm.getLinesWithErrorsCount() == 0) {
                                        if (orderMode == ORDERMODEORDER) {
                                            $.cv.css.bind($.cv.css.eventnames.preOrderSubmitComplete, function () {
                                                $.cv.util.redirect(data.orderCompletionRedirectUrl, params, !widget.options.includeInBrowserHistory);
                                            });
                                        } else if (orderMode == ORDERMODEQUOTE) {
                                            $.cv.css.bind($.cv.css.eventnames.preOrderSubmitComplete, function () {
                                                vm.placeQuote();
                                            });
                                        }

                                        $.cv.css.trigger($.cv.css.eventnames.preOrderSubmit);
                                    } else {
                                        vm.setMessage(widget.options.textErrorLinesHaveErrors, $.cv.css.messageTypes.error);
                                    }
                                } else if (vm.get("validationPreventingCheckout") && vm.get("orderSubmitting")) {
                                    vm.set("orderSubmitting", false);
                                }

                                vm.set("processing", false);
                            } else {
                                vm.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                            }
                        }).fail(function (msg) {
                            vm.set("processing", false);

                            if (msg.errorMessage != null) {
                                vm.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                            } else {
                                vm.setMessage(widget.options.textCheckoutErrorDefaultMessage, $.cv.css.messageTypes.error);
                            }
                        });
                    }
                },

				getLinesWithErrorsCount: function () {
				    var count = 0;
				    $(widget.options.linesElement).each(function () {
				        var linesWidget = $(this).data(widget.options.linesData);
				        if (linesWidget)
				            count = linesWidget.getLineErrorCount();
				    });
				    return count;
				},

                // builds array from the object returned
				buildLineValidationMessageArray: function (obj) {
				    var lineValidationErrors = new Array();
				    lineValidationErrors = [];
				    for (key in obj) {
				        if (obj.hasOwnProperty(key)) {
				            var el = {};
				            el["lineMessages"] = obj[key]
				            if (!widget.options.usingCostCentres && obj[key][0].errorType == widget.options.lineCostCentreError) {
				                continue;
				            }
				            lineValidationErrors.push(el);
				        }
				    }
				    return lineValidationErrors;
				},
				
				displayValidationMessages: function (data, linesToValidateString) {
					var _this = this, linesHaveErrors = false;
					_this.clearMessage();

					var lineValidationErrors = _this.buildLineValidationMessageArray(data.lineValidationErrors);

				    if(data.headerValidationErrors.length > 0 || lineValidationErrors.length > 0) {
						_this.set("clearExistingMessages",false);
						$.each(data.headerValidationErrors, function (idx, item) {
                            // hides particular error messages that are not relevant on the orders page and only applicable on the checkout page
						    if ($.inArray(item.errorType, widget.options.hideHeaderMessageTypes) < 0) {
						        if (item.preventsCheckOut) {
						            _this.set("validationPreventingCheckout", true);
						            if ((!_this.get("orderSubmitting") && _this.get("displayErrorMessagesOnLoad")) || _this.get("orderSubmitting") || (!widget.options.usingCostCentres && item.errorType == widget.options.headerCostCentreError))
						                _this.setMessage(item.errorMessage, $.cv.css.messageTypes.error);
						        } else {
						                _this.setMessage(item.errorMessage, $.cv.css.messageTypes.warning);
						        }
						    }
						});

                        // calls the orderLines widget (if it can be found) to display the error messages on the individual lines
						if (widget.options.displayLineMessagesOnLines) {
						    if (lineValidationErrors.length > 0) {
						        _this.displayLineMessages(lineValidationErrors, linesToValidateString);
						    } else if (linesToValidateString.length > 0) {
						        _this.clearLineMessages(linesToValidateString);
						    }
						}
						
						$.each(lineValidationErrors, function (idx, item) {
						    $.each(item["lineMessages"], function (idx, lineItem) {
						        if (lineItem.errorMessage != "") {
						            linesHaveErrors = true;
						        }
						        if (widget.options.displayLineErrorsAsMessages) {
						            if (lineItem.errorMessage != "") {
						                if (lineItem.preventsCheckOut) {
						                    if ((!_this.get("orderSubmitting") && _this.get("displayErrorMessagesOnLoad")) || _this.get("orderSubmitting"))
						                        _this.setMessage(lineItem.lineSeq + ', ' + lineItem.productCode + ': ' + lineItem.errorMessage, $.cv.css.messageTypes.error);
						                } else {
						                    _this.setMessage(lineItem.lineSeq + ', ' + lineItem.productCode + ': ' + lineItem.errorMessage, $.cv.css.messageTypes.warning);
						                }
						            }
						        }

						        if (lineItem.preventsCheckOut)
						            _this.set("validationPreventingCheckout", true);
						    });
						});

						if (widget.options.displayLineErrorSummaryMessage && !widget.options.displayLineErrorsAsMessages && linesHaveErrors) {
						    _this.setMessage(widget.options.textErrorLinesHaveErrors, $.cv.css.messageTypes.error);
						}

						_this.set("clearExistingMessages",true);
				    } else if (linesToValidateString.length > 0) {
				        _this.clearLineMessages(linesToValidateString);
				    }
				},

				clearLineMessages: function (lines) {
				    $(widget.options.linesElement).each(function () {
				        var linesWidget = $(this).data(widget.options.linesData);
				        if (linesWidget)
				            count = linesWidget.clearLineMessages(lines);
				    });
				},

				processLineChanges: function () {
				    var _this = this, d1 = new $.Deferred(), d2 = new $.Deferred(), d3 = new $.Deferred(), d4 = new $.Deferred(), d5 = new $.Deferred();
				    d2 = this.deleteLinesFlaggedForDelete(false,false);
				    d3 = this.updateLinesFlaggedForUpdate(false,false);
				    $.when(d2, d3).done(function () {
				        d4 = $.cv.css.getCurrentOrder();
				        $.when(d4).done(function () {
				            var order = $.cv.css.localGetCurrentOrder();
				            _this.set('order', formatFieldsAsCurrency(order));
				            d1.resolve();
				        });
				    });
				    return d1;
				},

				placeQuote: function() {
				    var _this = this;
				    var d1 = $.cv.css.orders.requestQuote();
				    $.when(d1).done(function (msg) {
				        var data = msg.data
				        var params = {};
				        if (!data.sessionHasTimedOut) {
				            if (data.Success)
				                $.cv.util.redirect(data.RedirectUrl, params, !widget.options.includeInBrowserHistory);
				            else {
				                _this.setMessage(widget.options.textCheckoutQuoteErrorDefaultMessage, $.cv.css.messageTypes.error);
				                _this.set("isRequestingQuote", false);
				            }
				            _this.set("processing", false);
				        } else {
				            _this.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
				        }
				    }).fail(function (msg) {
				        _this.set("processing", false);
				        _this.set("isRequestingQuote", false);
				        if (msg.errorMessage != null)
				            _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error)
				        else
				            _this.setMessage(widget.options.textCheckoutQuoteErrorDefaultMessage, $.cv.css.messageTypes.error);
				    });
				},
				
				submitOrder: function () {
				    var vm = this, d1 = new $.Deferred();
				    vm.set("orderSubmitting", true);
				    vm.set("processing", true);
				    d1 = vm.processLineChanges();
				    $.when(d1).done(function () {
				        if (vm.items() > 0)
				            vm.validateOrder(ORDERMODEORDER);
				        else {
				            vm.setMessage(widget.options.textSubmitOrderNoItems, $.cv.css.messageTypes.error);
				            vm.set("processing", false);
				            vm.set("orderSubmitting", false);
				        }
				    });
				},
				
				placeOrderOnHold: function() {
					var _this = this;
					var order = _this.get("order");
					if (order._objectKey) {
					    _this.set("isHoldingOrder", true);
					    var d1 = $.cv.css.orders.holdCurrentOrder({ _objectKey: order._objectKey, triggerOrderChanged: !widget.options.reloadAfterOrderHeld, triggerOrderSubmitted: !widget.options.reloadAfterOrderHeld });
					    _this.set("orderSubmitting", true);
					    _this.set("processing", true);
						$.when(d1).done(function(msg){
							var data = msg.data
							if (!data.sessionHasTimedOut) {
							    $.cv.css.localSetCurrentOrder(null);
							    $.cv.css.localSetCurrentOrderLines(null);
							    if (widget.options.reloadAfterOrderHeld) {
							        $.cv.util.redirect(widget.options.holdOrderRedirectUrl.format(order.SoOrderNo), {}, !widget.options.includeInBrowserHistory);
							    } else {
							        $.cv.css.trigger($.cv.css.eventnames.orderChanged);
							        _this.set("processing", false);
							        _this.set("isHoldingOrder", false);
							    }
							} else {
							    _this.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
							}
						}).fail(function (msg) {
						    _this.set("processing", false);
						    _this.set("isHoldingOrder", false);
							if(msg.errorMessage != null)
								_this.setMessage(msg.errorMessage,$.cv.css.messageTypes.error)
							else
								_this.setMessage(widget.options.textCheckoutErrorDefaultMessage,$.cv.css.messageTypes.error)
						});
					}
				},
                
                saveAsTemplate: function() {
                    var _this = this;
                    _this.set("isSavingAsTemplate", true);
                    if (widget.options.saveAsTemplateUseModal) {
                        opts = {};
                        opts.templateName = _this.get("templateName");
                        var d1 = $.cv.css.orderTemplate.createTemplateFromCurrentOrder(opts);
                        _this.set("processing", true);
                        $.when(d1).done(function (msg) {
                            var data = msg.data
                            if (!data.sessionHasTimedOut) {
                                widget.trigger(TEMPLATESAVED);
                                if (widget.options.saveAsTemplateRedirectUrl != "")
                                    $.cv.util.redirect(widget.options.saveAsTemplateRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                                else {
                                    _this.setMessage(widget.options.textSaveAsTemplateSuccessful, $.cv.css.messageTypes.success);
                                    _this.set("processing", false);
                                    _this.set("isSavingAsTemplate", false);
                                }
                                _this.set("templateName", "");
                            } else {
                                var params = {};
                                _this.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                            }
                        }).fail(function () {
                            if (msg.errorMessage != null)
                                _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                            _this.set("isSavingAsTemplate", false);
                        });
                    } else {
                        _this.set("processing", false);
                        if (widget.options.saveAsTemplateRedirectUrl != "")
                            $.cv.util.redirect(widget.options.saveAsTemplateRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        else
                            _this.set("isSavingAsTemplate", false);
                    }
                },

                requestQuote: function () {
                    var _this = this, d1 = new $.Deferred()
                    if (this.items() > 0) {
                        this.set("processing", true);
                        this.set("orderSubmitting", true);
                        this.set("isRequestingQuote", true);
                        d1 = _this.processLineChanges();
                        $.when(d1).done(function () {
                            _this.validateOrder(ORDERMODEQUOTE);
                        });
                    }
                    else
                        this.setMessage(widget.options.textRequestQuoteEmptyCartMessage, $.cv.css.messageTypes.error)
                },

                designStamps: function () {
                    $.cv.util.redirect(widget.options.designStampUrl, {}, widget.options.includeInBrowserHistory);
                },

                cancelQuote: function () {
                    var _this = this;
                    var d1 = $.cv.css.orders.cancelQuoteOrder();
                    _this.set("processing", true);
                    $.when(d1).done(function (msg) {
                        var data = msg.data
                        if (!msg.sessionHasTimedOut) {
                            $.cv.css.bind($.cv.css.eventnames.localOrderChanged, function () {
                                widget.trigger(QUOTECANCELLED);
                                if (data.RedirectUrl != "")
                                    $.cv.util.redirect(data.RedirectUrl, {}, !widget.options.includeInBrowserHistory);
                                else
                                    _this.setMessage(widget.options.textQuoteCancelSuccessful, $.cv.css.messageTypes.success);
                                _this.set("processing", false);
                            });
                        } else {
                            var params = {};
                            _this.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        _this.set("processing", false);
                        if (msg.errorMessage != null)
                            _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error)
                    });
                }

            });

			initDataSource();
			
            return viewModel;
        },


        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = ""
            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(orderSummaryWidget);

})(jQuery);;
/* Name: Sorter Widget
* Author: Aidan Thomas 
* Created: 20130220
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.util.js
*          /cv.css.js
*          /cv.css.user.js
* 
* Params:  
*           mailTo: 
*           emailFrom: 
*           redirectTemplate: 
*           redirectRoute: 
*           subject: 
*           emailTemplateName: 
*           questionnaireCode: 
*           formFieldTypes: 
*           formFieldPrefix: 
*           formHiddenFields: 
*           fieldsStored: 
*           postmode:
*           viewTemplate: kendo template id for the main view
*/
// TODO: Create default view?
;
(function ($, undefined) {

    var BEFORESUCCESSFULFORMSUBMIT = 'beforesuccessfulformsubmit',
        FORMSUBMITTED = 'formsubmitted',
        FORMSUBMITFAIL = 'formsubmitfail';

    var formSubmitWidget = {


        // Standard Variables

        // widget name
        name: "formSubmit",

        // default widget options
        options: {
            // viewModel defaults
            mailTo: '',
            emailFrom: 'info@commercevision.com.au',
            redirectTemplate: '',
            redirectRoute: '',
            subject: '',
            emailTemplateName: '',
            questionnaireCode: '',
            formFieldTypes: 'input,select,textarea',
            formFieldPrefix: 'CVFormField_',
            formHiddenFields: 'mailto,EmailFrom,redirectroute,Subject,EmailTemplate,QuestionnaireCode,postmode',
            fieldsStored: '',
            postmode: 'emailform',
            // viewModel flags

            // events

            // view flags
            validateCaptcha: false,
            getCurrentUserEmail: true,

            // view text defaults

            // view Template
            viewTemplate: '', // treat like its an id

            validationOptions: {} // kendoValidate options if you need to override anything (rules, messages etc)
        },

        events: [BEFORESUCCESSFULFORMSUBMIT, FORMSUBMITTED, FORMSUBMITFAIL],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,


        // Standard Methods
        initialise: function (el, o) {
            var widget = this;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                if (!widget.options.viewTemplate) {
                    widget.options.viewTemplate = widget._getDefaultViewTemplate();
                }
                var viewTemplate = kendo.template(widget.options.viewTemplate);
                widget.view = viewTemplate(widget.options);
                $(el).append(widget.view);
                widget._viewAppended = true;
            }
            // now MMVM bind
            var viewModel = widget._getViewModel();
            var target = $(widget.element).children(":first");
            kendo.bind(target, viewModel);
            viewModel.setFormFields();
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var initFormFields = function () {
                var fieldsStored = widget.options.fieldsStored.length > 0 ? widget.options.fieldsStored.split(',') : "";

                $(widget.element).find(widget.options.formFieldTypes).each(function (i, el) {
                    var $el = $(el);

                    // Ignore the captcha field as it MUST remain exactly with this name
                    if ($el.attr('name') === '_cvTemplateCaptcha') {
                        return true; // continue;
                    }

                    if ($el.attr('name') && $el.attr('name').indexOf(widget.options.formFieldPrefix) < 0) {
                        if (fieldsStored.length == 0) {
                            $el.attr('name', widget.options.formFieldPrefix + $el.attr('name'));
                        }
                        else {
                            if ($.inArray($el.attr('name'), fieldsStored) > -1)
                                $el.attr('name', widget.options.formFieldPrefix + $el.attr('name'));
                        }
                    }
                });
            };

            var viewModel = kendo.observable({
                // Properties for UI elements
                postmode: widget.options.postmode,

                mailTo: widget.options.mailTo,

                emailFrom: widget.options.emailFrom,

                redirectTemplate: widget.options.redirectTemplate,

                redirectRoute: widget.options.redirectRoute,

                subject: widget.options.subject,

                emailTemplateName: widget.options.emailTemplateName,

                questionnaireCode: widget.options.questionnaireCode,

                formHiddenFields: widget.options.formHiddenFields,

                captchaText: '',

                loggedInUserEmail: '',

                captchaImageUrl: '/ValidationImage.aspx?rand=' + $.now(),

                isProcessing: false,

                formHiddenFieldsDataSource: function () {
                    var vs = this.get("formHiddenFields");
                    return vs.split(",");
                },

                setFormFields: function () {
                    initFormFields();
                },

                resetCaptcha: function () {
                    if (widget.options.validateCaptcha === true) {
                        viewModel.set('captchaText', '');

                        $.cv.css.generateCaptcha().done(function () {
                            viewModel.set('captchaImageUrl', '/ValidationImage.aspx?rand=' + $.now());
                        });
                    }
                },

                inputKeyUp: function (event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();
                        this.submitForm(event);
                    }
                },

                // UI Element state

                // functions for UI events
                submitForm: function (e) {
                    e.preventDefault();
                    e.stopPropagation();

                    var _this = this;
                    _this.set("isProcessing", true);

                    // Validation: We need it now so set it up.
                    var $widgetElement = $(widget.element);
                    var validator = $widgetElement.data("kendoValidator");

                    if (!validator && $widgetElement.kendoValidator) {
                        $widgetElement.kendoValidator(widget.options.validationOptions);
                        validator = $widgetElement.data("kendoValidator");
                    }

                    // Validate: Return if not valid so user forced to enter valid values.
                    if (validator && !validator.validate()) {
                        _this.set("isProcessing", false);
                        return;
                    }

                    var prom = $.Deferred();

                    // Pre-validate captcha before submitting
                    if (widget.options.validateCaptcha === true) {
                        var text = this.get('captchaText');

                        if (text && text.length > 0) {
                            var validated = $.cv.css.validateCaptcha(text);

                            validated.done(function (response) {
                                if (response.data.Success) {
                                    prom.resolve();
                                } else {
                                    _this.set("isProcessing", false);
                                    prom.reject();
                                    widget.trigger(FORMSUBMITFAIL, { missingFields: 'cvTemplateCaptcha' });
                                    viewModel.resetCaptcha();
                                }
                            });
                        } else {
                            _this.set("isProcessing", false);
                            prom.reject();
                            widget.trigger(FORMSUBMITFAIL, { missingFields: 'cvTemplateCaptcha' });
                            viewModel.resetCaptcha();
                        }
                    } else {
                        prom.resolve();
                    }

                    prom.done(function () {
                        // Submit
                        viewModel.removeFormHiddenFields();
                        if (viewModel.get("postmode") != '' && viewModel.get("mailTo") != '' && viewModel.get("emailFrom") != '' && (viewModel.get("redirectTemplate") != '' || viewModel.get("redirectRoute") != '') && viewModel.get("subject") != '' && viewModel.get("emailTemplateName") != '') {
                            viewModel.insertFormHiddenFields();

                            $('form:first').attr('action', "/post.aspx?mailto=" + viewModel.get("mailTo") + "&RedirectTemplate=" + (viewModel.get("redirectTemplate") == '' ? viewModel.get("redirectRoute") : viewModel.get("redirectTemplate")) + "&Subject=" + viewModel.get("subject") + "&EmailTemplate=" + viewModel.get("emailTemplateName"));

                            widget.trigger(BEFORESUCCESSFULFORMSUBMIT);
                            $('form:first').submit();
                            widget.trigger(FORMSUBMITTED);
                        }
                        else {
                            var missingFields = '';
                            missingFields += viewModel.get("mailTo") == '' ? 'mailto,' : '';
                            missingFields += viewModel.get("emailFrom") == '' ? 'emailFrom,' : '';
                            missingFields += (viewModel.get("redirectTemplate") == '' && viewModel.get("redirectRoute") == '') ? 'redirectTemplate or redirectRoute,' : '';
                            missingFields += viewModel.get("subject") == '' ? 'subject,' : '';
                            missingFields += viewModel.get("emailTemplateName") == '' ? 'emailTemplateName,' : '';
                            missingFields = missingFields.substring(0, missingFields.length - 1);

                            widget.trigger(FORMSUBMITFAIL, { missingFields: missingFields });
                        }
                        _this.set("isProcessing", false);
                    });
                },

                removeFormHiddenFields: function () {
                    for (var i = 0; i < this.formHiddenFieldsDataSource().length; i++) {
                        $('input[name="' + this.formHiddenFieldsDataSource()[i] + '"]').remove();
                    }
                },

                insertFormHiddenFields: function () {
                    var form = $("form:first");
                    $(form).append("<input name='postmode' value='" + this.get("postmode") + "' type='hidden'>");
                    $(form).append("<input name='mailto' value='" + this.get("mailTo") + "' type='hidden'>");
                    $(form).append("<input name='EmailFrom' value='" + this.get("emailFrom") + "' type='hidden'>");
                    this.get("redirectTemplate") == '' ? $(form).append("<input name='redirectroute' value='" + this.get("redirectRoute") + "' type='hidden'>") : $(form).append("<input name='RedirectTemplate' value='" + this.get("redirectTemplate") + "' type='hidden'>");

                    $(form).append("<input name='Subject' value='" + this.get("subject") + "' type='hidden'>");
                    $(form).append("<input name='EmailTemplate' value='" + this.get("emailTemplateName") + "' type='hidden'>");
                    if (this.get("questionnaireCode") != '')
                        $(form).append("<input name='QuestionnaireCode' value='" + this.get("questionnaireCode") + "' type='hidden'>");
                }
            });

            // Try and Load the current user's email address if not present
            // This will load async and bind when done so should just update when it is done.
            //
            // WARNING: Used in situations where $.cv namespace might not exist so we are not 
            // depending on it existing here on purpose!
            if (widget.options.getCurrentUserEmail && $.cv && $.cv.css) {
                var currentUser = $.cv.css.localGetUser();
                if (!currentUser) {
                    if ($.cv.css.user && $.cv.css.user.getCurrentUser) {
                        $.cv.css.user.getCurrentUser()
                         .done(function (response) {
                             if (response.data && response.data.length > 0 && response.data[0].ValidEmailAddress) {
                                 viewModel.set('loggedInUserEmail', response.data[0].ValidEmailAddress);
                             }
                         });
                    }
                } else {
                    if(currentUser.ValidEmailAddress)
                        viewModel.set('loggedInUserEmail', currentUser.ValidEmailAddress);
                }
            }

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(formSubmitWidget);

})(jQuery);;
/* Name: order lines
* Author: Aidan Thomas
* Created: 20130220 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.ajax.js
*          /Scripts/cv.css.orders.js
* Params:  
*       dataSource: 
*       autoBind: 
*       triggerMessages: 
*       messagesUseLineSeq: 
*       linesRendered: 
*       noteIsExtendedLineDescription: 
*       textLineUpdatedSuccessfully: 
*       textLinesUpdatedSuccessfully: d
*       textLinesUpdatedUnSuccessfully: 
*       textLinesUpdatedErrored: 
*       textEnterNumeric: 
*       viewTemplate: 
*       itemViewTemplate: null
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        LINESRENDERED = "linesRendered",
        CHARGELINESRENDERED = "chargeLinesRendered",
        CHARGELINESWITHOUTFREIGHTRENDERED = "chargeLinesWithoutFreightRendered",
        PRODUCTADDEDTOFAVOURITES = "productAddedToFavourites",
        PRODUCTADDTOFAVOURITESFAIL = "productAddToFavouritesFail",
        PRODUCTREMOVEDFROMFAVOURITES = "productRemovedFromFavourites",
        PRODUCTREMOVEDFROMFAVOURITESFAIL = "productRemovedFromFavouritesFail",
        STOCKLINE = "SN",
        NOTELINE = "DN",
        KITLINE = "KN",
        CHARGELINE = "SC",
        ITEMLISTVIEW = "itemListView",
        CHARGELINELISTVIEW = "chargeLineListView",
        NONCHANGEDEVENTS = ["HasErrors", "ErrorMessages", "ErrorTypes", "lastValidQuantity", "quantityReset", "requiresUpdate", "requiresDelete", "isDeleting", "removingFromFavourites", "addingToFavourites", "isFavourite"],
        LINESUPDATED = "linesUpdated",
        OPTIONENABLEDSUFFIX = "-enabled",
        OPTIONDISABLEDSUFFIX = "-disabled";


    var orderLinesWidget = {

        // Standard Variables

        // widget name
        name: "orderLines",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            designStampUrl: "StampDesign.aspx?id={0}",
            clearAdditionalWidgetMessages: "quickAdd", // comma separated list of widget names
            pricingOnOrderGridClass: "ol-pricing-on-order-grid",
            orderLineNotesClass: "ol-order-line-notes",
            useOrderEntryClass: "ol-use-order-entry",
            notesRequiredForNonContractClass: "ol-notes-required-for-non-contract",
            // viewModel flags
            autoBind: true,
            triggerMessages: true,
            clearExistingMessages: false,
            messagesUseLineSeq: false,
            getLatestOrderOnLoad: false,
            lineErrorSeparator: ", ",
            freightChargeType: 0,
            // events
            linesRendered: null,
            // view flags
            noteIsExtendedLineDescription: true,
            includeInBrowserHistory: true,
            isAddToFavouritesAMove: false,
            allowDecimals: false,
            updateLinesDuringDelete: false,
            // view text defaults
            textProductAddedToFavourites: 'Your product has been successfully added to your favourites',
            textDefaultErrorAddingToFavourites: 'Error adding this product to your favourites, it may already exist',
            textProductRemovedFromFavourites: "Product {0} has been successfully removed from your favourites",
            textDefaultErrorRemovedFromFavourites: "Error removing {0} from your favourites, it may have already been removed",
            // widget settings
            hidePricingInOrderGrid: false,
            enableOrderLineNotes: false,
            useCostCentres: false,
            userOrderEntry: true,
            notesRequiredForNonContractPurchasing: false,
            isAdvancedQuoteOrder: false,
            defaultLinesSortField: "",
            defaultLinesSortDirection: "",
            textLineUpdatedSuccessfully: 'Line updated successfully',
            textLinesUpdatedSuccessfully: 'updated successfully',
            linesUpdatedSuccessfullyGeneric: '',
            textLinesUpdatedUnSuccessfully: 'updated unsuccessfully',
            linesUpdatedUnSuccessfullyGeneric: '',
            textLinesUpdatedErrored: 'There was an error trying to update the lines',
            textEnterNumeric: 'Please enter a numeric quantity',
            textLineDeletedSuccessfully: 'Product Code {0} has been removed',
            textLinesDeletedSuccessfully: 'Products {0} have been removed',
            // view Template
            stampsTemplate: "#stampsTemplate",
            stampsWidgetContainer: ".stampsWidgetContainer",
            viewTemplate: null, // TODO: Treat these as IDs, remove the last one.
            itemViewTemplate: null,
            chargeLineViewTemplate: null
            //itemTemplateId: "cvgrid-item-template-" + kendo.guid()
        },

        events: [DATABINDING, DATABOUND, LINESRENDERED, CHARGELINESRENDERED, CHARGELINESWITHOUTFREIGHTRENDERED, LINESUPDATED, PRODUCTADDEDTOFAVOURITES, PRODUCTADDTOFAVOURITESFAIL, PRODUCTREMOVEDFROMFAVOURITES, PRODUCTREMOVEDFROMFAVOURITESFAIL],

        viewModel: null,

        view: null,

        // MVVM Support

        // private property
        _viewAppended: false,
        _itemViewAppended: false,
        _chargeLineViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                if (!widget.options.chargeLineViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.chargeLineViewTemplate = widget.name + "-charge-line-template-" + kendo.guid();
                    widget._chargeLineViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (!widget._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                // add the chargeLineView (not parsed)
                if (!widget._chargeLineViewAppended) {
                    widget.view += widget._getDefaultChargeLineViewTemplate();
                }
                widget.element.html("<div class='cv-widget-wrapper'>" + widget.view + "</div>");
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);

            // Subscribe to Events
            $.cv.css.bind($.cv.css.eventnames.orderChanged, $.proxy(widget.viewModel.cartUpdated, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.localOrderChanged, $.proxy(widget.initDataSource, widget));
            $.cv.css.bind($.cv.css.eventnames.validateOrderStarted, $.proxy(function () { widget.viewModel.set("isValidating", true); }, widget));
            $.cv.css.bind($.cv.css.eventnames.validateOrderComplete, $.proxy(function () { widget.viewModel.set("isValidating", false); }, widget));

            if (widget.options.getLatestOrderOnLoad) {
                widget.viewModel.set("isProcessing", true);
                var currOrderLines = $.cv.css.getCurrentOrderLines();

                currOrderLines.done(function () {
                    // trigger local orderchanged - this will notify widgets
                    $.cv.css.trigger($.cv.css.eventnames.localOrderChanged);
                    widget.viewModel.set("isProcessing", false);
                });
            } else {
                widget.initDataSource();
            }
        },

        initDataSource: function () {
            var widget = this;
            // get local storage
            // if no local storage make dynamic service call
            var ds = $.cv.css.localGetCurrentOrderLines();
            if (ds) {
                widget.retainLineMessages(ds);
                widget.setDataSource(ds);
            }

        },

        retainLineMessages: function (ds) {
            var widget = this, updatedLines = [], addedLines = [], linesToIgnore = [], localUpdatedCurrentOrderLines = $.cv.css.localGetUpdatedCurrentOrderLines();
            if (widget.dataSource) {
                if (localUpdatedCurrentOrderLines == null)
                    localUpdatedCurrentOrderLines = { addedLines: [], updatedLines: [], linesDeleted: false };
                updatedLines = localUpdatedCurrentOrderLines["updatedLines"];
                addedLines = localUpdatedCurrentOrderLines["addedLines"];
                if (updatedLines != null && addedLines != null)
                    linesToIgnore = _.union(updatedLines, addedLines);
                else {
                    if (updatedLines != null)
                        linesToIgnore = updatedLines;
                    else if (addedLines != null)
                        linesToIgnore = addedLines;
                }
                if (linesToIgnore.length == 0) {
                    if (localUpdatedCurrentOrderLines["linesDeleted"] != undefined && localUpdatedCurrentOrderLines["linesDeleted"])
                        linesToIgnore = ["0"];
                }
                // if the updated lines array is not empty retain the error message status on all the non updated lines as they would not have been revalidated
                if (linesToIgnore.length > 0) {
                    rList = _.reject(widget.dataSource.data(), function (item) { return _.contains(linesToIgnore, item.LineSeq.toString());});
                } else {
                    rList = widget.dataSource.data();
                }
                $.each(ds, function (idx, item) {
                    data = _.find(rList, function (lItem) { return lItem.LineSeq == item.LineSeq; });
                    if (data) {
                        ds[idx].HasErrors = data.HasErrors;
                        ds[idx].ErrorMessages = data.ErrorMessages;
                        ds[idx].ErrorTypes = data.ErrorTypes;
                    }
                });
            }
        },

        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource) {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
        },

        _dataSource: function () {
            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler) {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            }
            else {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);
            if (widget.viewModel) {
                widget.viewModel.set("dataSource", widget.dataSource);
            }

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            this._sortDataSource();

            if (widget.options.autoBind) {
                widget.dataSource.fetch();
            }
        },

        _sortDataSource: function () {
            var widget = this;
            if (widget.options.defaultLinesSortField != undefined && widget.options.defaultLinesSortField.length > 0) {
                var dssort = widget.dataSource.sort();
                if (dssort == undefined || dssort.length == 0) {
                    var direction = widget.options.defaultLinesSortDirection.length > 0 ? widget.options.defaultLinesSortDirection : "asc";
                    widget.dataSource.sort({ field: widget.options.defaultLinesSortField, dir: direction });
                }
            }
        },

        refresh: function (e) {
            if (e.action == "itemchange")
                return;
            var widget = this;
            widget.trigger(DATABINDING);
            widget.viewModel.updateViewModelFromDataSource();
            widget.trigger(DATABOUND);
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        displayLineMessages: function (msgs, lines) {
            var widget = this;
            widget.viewModel.displayLineMessages(msgs, lines);
        },

        clearLineMessages: function (lines) {
            var widget = this;
            widget.viewModel.clearLineMessages(lines);
        },

        updateAllLines: function () {
            var widget = this;
            widget.viewModel.updateAllLines();
        },

        updateLinesFlaggedForUpdate: function (displayMessages, triggerOrderRefresh) {
            var widget = this;
            var d = widget.viewModel.updateLinesFlaggedForUpdate(displayMessages, triggerOrderRefresh);
            return d;
        },

        deleteAllLines: function () {
            var widget = this;
            var d = widget.viewModel.deleteAllLines();
            return d;
        },

        deleteLinesFlaggedForDelete: function (displayMessages, triggerOrderRefresh) {
            var widget = this;
            var d = widget.viewModel.deleteLinesFlaggedForDelete(displayMessages, triggerOrderRefresh);
            return d;
        },

        refreshLines: function () {
            var widget = this;
            widget.viewModel.refreshLines();
        },

        getLineErrorCount: function () {
            var widget = this;
            return widget.viewModel.getLineErrorCount();
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            initSettings = function () {
                if ($.cv.css.userFavourites.defaults)
                    $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite = true;
            };

            var getChargeLines = function (data) {
                var array = [];
                if (data != null) {
                    $.each(data, function (idx, item) {
                        if (item.LineType === CHARGELINE) {
                            array.push(item);
                        }
                    });
                }
                return array;
            };

            var appendLineCharge = function (array, item) {
                var lineSeqForCharge = Math.floor(item.LineSeq);
                var lineItem = $.grep(array, function (e) { return e.LineSeq == lineSeqForCharge; });
                if (lineItem.length == 1) {
                    if (lineItem[0].lineCharges == undefined)
                        lineItem[0].lineCharges = [];
                    lineItem[0].lineCharges.push(item);
                    lineItem[0].hasLineCharges = true;
                }
            };

            var appendNoteLine = function (array, item) {
                var lineSeqForNote = Math.floor(item.LineSeq);
                var lineItem = $.grep(array, function (e) { return e.LineSeq == lineSeqForNote; });
                if (lineItem.length == 1) {
                    if (widget.options.noteIsExtendedLineDescription) {
                        lineItem[0].ExtendedLineDescription = item.Description + " ";
                        lineItem[0].requiresUpdate = true;
                        item.requiresDelete = true;
                    }
                    if (lineItem[0].lineNotes == undefined)
                        lineItem[0].lineNotes = [];
                    lineItem[0].lineNotes.push(item);
                    lineItem[0].hasLineNotes = true;
                }
            };

            var getStockLines = function (data) {
                var array = [];
                if (data != null) {
                    $.each(data, function (idx, item) {
                        if (item.LineType === STOCKLINE || item.LineType === KITLINE) {
                            item.lineNotes = [];
                            item.hasLineNotes = false;
                            item.lineCharges = [];
                            item.hasLineCharges = false;
                            if (item.LineSeq % 1 == 0) {
                                array.push(item);
                            } else {
                                appendLineCharge(array, item);
                            }
                        } else if (item.LineType === NOTELINE) {
                            appendNoteLine(array, item);
                        }
                    });
                }
                return array;
            };

            var getDataView = function (listView) {
                // check if ds is initialised
                var array = [];
                if (!widget.dataSource)
                    return array;
                if (listView == ITEMLISTVIEW) {
                    array = getItemListView();
                } else if (listView == CHARGELINELISTVIEW) {
                    array = getChargeLineListView();
                }
                return array;
            };

            var getItemListView = function () {
                var array = [], stockLineIndex = 0, noteLineIndex = 0, batchLineUpdates = [], batchLineDeletes = [];
                $.each(getStockLines(widget.dataSource.view()), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    item.lastValidQuantity = item.OrderedQty;
                    item.quantityReset = false;
                    if (item.requiresUpdate === undefined)
                        item.requiresUpdate = false;
                    if (item.requiresDelete === undefined)
                        item.requiresDelete = false;
                    if (item.isProcessing === undefined)
                        item.isProcessing = false;
                    if (item.isUpdating === undefined)
                        item.isUpdating = false;
                    if (item.isDeleting === undefined)
                        item.isDeleting = false;
                    if (item.addingToFavourites === undefined)
                        item.addingToFavourites = false;
                    if (item.removingFromFavourites === undefined)
                        item.removingFromFavourites = false;
                    if (item.isLineEditable === undefined)
                        item.isLineEditable = !widget.options.isAdvancedQuoteOrder;

                    item.execCommand_destroy = function () {
                        item.isProcessing = true;
                        viewModel.setLocalLinesDeleted(true);

                        // set this line as requires delete
                        item.set("requiresDelete", true);
                        // ensure no updates are attempted on this line as it will be deleted
                        item.set("requiresUpdate", false);
                        item.set("isDeleting", true);

                        var currentLineDelete = new $.Deferred(), updates = new $.Deferred(), deletes = new $.Deferred();
                        // if update lines during delete is turned on we want to process any other updates and all deletes including the delete of this line, only trigger messages for updates
                        if (widget.options.updateLinesDuringDelete && _.filter(viewModel.get("itemList"), function (i) { return i.requiresUpdate && !i.requiresDelete; }).length > 0) {
                            // We don't wan't to trigger refresh of the order (orderChanged event) as this
                            // will cause a reload of the order from localStorage and re-binding of the
                            // dataSource, which results in wrong lines...
                            deletes = viewModel.deleteLinesFlaggedForDelete(false, false);
                            updates = viewModel.updateLinesFlaggedForUpdate(true, false);

                            currentLineDelete.resolve();
                        } else {
                            currentLineDelete = $.cv.css.deleteCurrentOrderLine({ seq: item.LineSeq.toString(), triggerOrderRefresh: false });
                            updates.resolve();
                            deletes.resolve();
                        }

                        $.when(currentLineDelete, updates, deletes).done(function (data) {
                            // Reload order and lines and trigger orderChanged so that dataSource
                            // is re-set and bound etc... deleted lines will not be in the 
                            // data returned and set into localStorage, so no need in this case to
                            // remove it from the dataSource
                            $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines()).always(function () {
                                $.cv.css.trigger($.cv.css.eventnames.orderChanged);

                                item.set("isDeleting", false);
                                viewModel.clearMessage();

                                if (item.StockCode != null) {
                                    viewModel.setMessage(widget.options.textLineDeletedSuccessfully.format(item.StockCode), $.cv.css.messageTypes.success);
                                }
                            });
                        });
                    };

                    // stock lines and note lines can be individually deleted
                    if (item.lineNotes != undefined && item.lineNotes.length > 0) {
                        $.each(item.lineNotes, function (idx2, lineNote) {
                            lineNote.execCommand_destroy = function () {
                                lineNote.isProcessing = true;
                                var d1 = $.cv.css.deleteCurrentOrderLine({ seq: lineNote.LineSeq.toString() });
                                $.when(d1).done(function (data) {
                                    //item.lineNotes[idx2].pop();
                                    item.lineNotes = item.lineNotes.splice(idx2, 1);
                                });
                            };
                        });
                    }

                    // these only apply to stock lines
                    item.stockLineIndex = stockLineIndex++;
                    item.hidePricingInOrderGrid = viewModel.get("hidePricingInOrderGrid");
                    item.notesRequiredForNonContractPurchasing = widget.options.notesRequiredForNonContractPurchasing;
                    item.enableOrderLineNotes = viewModel.get("enableOrderLineNotes");
                    item.useCostCentres = viewModel.get("useCostCentres");
                    item.userOrderEntry = viewModel.get("userOrderEntry");
                    if (item.HasErrors == undefined)
                        item.HasErrors = false;
                    if (item.ErrorMessages == undefined)
                        item.ErrorMessages = "";
                    if (item.ErrorTypes == undefined)
                        item.ErrorTypes = "";
                    // to make it correct casing and inline with the casing of all other view model attributes (this is also the casing as used in the product widget - these need to be consistent)
                    if (item.CostCentreCode != undefined) {
                        item.costCentreCode = item.CostCentreCode;
                        // this ensure that if you are displaying the product cost centre box that the line cost centre value is displayed, it also stops the change event triggering when the product cost centre resource is set to use data-bind="valueWithDefault: costCentreCode"
                        if (item.CostCentreCodeControl != undefined && $(item.CostCentreCodeControl).attr("value") != undefined) {
                            item.CostCentreCodeControl = $(item.CostCentreCodeControl).attr("value", item.CostCentreCode).wrap("<div />").parent().html();
                        }
                        item.set("costCentreCode", item.CostCentreCode);
                    }
                    if (widget.options.notesRequiredForNonContractPurchasing) {
                        // this ensure that if you are displaying the non contract reason it stops the change event triggering when the non contract reason resource is set to use data-bind="valueWithDefault: nonContractReason"
                        if (item.NonContractReasonInputField != undefined && $(item.NonContractReasonInputField).attr("value") != undefined) {
                            item.set("nonContractReason", $(item.NonContractReasonInputField).attr("value"));
                        }

                    }
                    item.execCommand_updateErrorMessage = function (hasErrors, errorMessages, errorTypes) {
                        this.set("HasErrors", hasErrors);
                        this.set("ErrorMessages", errorMessages);
                        this.set("ErrorTypes", errorTypes);
                    };
                    item.inputEventKeyUp = function (event) {
                        if (event.which == 13) {
                            // stops the form from submitting when using the widget on a page that has form submit buttons
                            event.preventDefault();
                            event.stopPropagation();
                            item.execCommand_update();
                        }
                    };
                    item.execCommand_update = function () {
                        updateLineItem(item);
                    };
                    item.execCommand_designStamp = function () {
                        if (item.IsStampProduct != undefined && item.StampID != undefined) {
                            if (item.IsStampProduct)
                                $.cv.util.redirect(widget.options.designStampUrl.format(item.StampID), {}, widget.options.includeInBrowserHistory);
                        }
                    };
                    item.isFavourite = false;
                    if (item.BuildDescriptionTextParts != undefined && item.BuildDescriptionTextParts.UserFavouriteIcon !== undefined && item.BuildDescriptionTextParts.UserFavouriteIcon.length > 0) {
                        item.isFavourite = true;
                    };
                    item.execCommand_addToFavourites = function () {
                        addItemToFavourites(item);
                    };
                    item.execCommand_removeFromFavourites = function () {
                        removeItemFromFavourites(item);
                    };
                    item.execCommand_toggleFavourite = function () {
                        if (item.get("isFavourite")) {
                            removeItemFromFavourites(item);
                        } else {
                            addItemToFavourites(item);
                        }
                    };
                    item.bind("change", function (e) {
                        // TODO: handle the change from the valueWithDefault binding to not flagged changed when loading the default value from the input
                        var validChange = true;
                        if (e.field == "OrderedQty" && (!isQuantityValid(item) || item.get("quantityReset"))) {
                            validChange = false;
                        }
                        if (!_.contains(NONCHANGEDEVENTS, e.field) && validChange)
                            item.set("requiresUpdate", true);
                        if (e.field == "OrderedQty" && !validChange && item.get("quantityReset")) {
                            item.set("quantityReset", false);
                        }
                    });
                    array.push(item);
                });
                return array;
            };

            var getChargeLineListView = function () {
                var array = [], chargeLineIndex = 0;
                $.each(getChargeLines(widget.dataSource.view()), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    item.chargeLineIndex = chargeLineIndex++;
                    item.hidePricingInOrderGrid = viewModel.get("hidePricingInOrderGrid");
                    array.push(item);
                });
                return array;
            };

            var isQuantityValid = function (item) {
                var valid = true, quantity = item.OrderedQty;
                if (isNaN(quantity) || (!widget.options.allowDecimals && quantity % 1 != 0) || quantity < 0) {
                    valid = false;
                }
                if (!valid) {
                    item.set("quantityReset", true);
                    item.set("OrderedQty", item.get("lastValidQuantity"));
                } else {
                    item.set("lastValidQuantity", quantity);
                }
                return valid;
            };

            var updateLineItem = function (item, displayMessages) {
                var opts = {};
                displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : true;
                if (isNaN(item.OrderedQty.toString()) || item.OrderedQty.toString() == '') {
                    viewModel.setMessage(widget.options.textEnterNumeric, $.cv.css.messageTypes.error);
                } else if (item.OrderedQty == 0) {
                    item.execCommand_destroy();
                } else {
                    opts = buildLineItemJSON(item);
                    viewModel.setLocalLinesUpdated([opts]);
                    item.set("isProcessing", true);
                    item.set("isUpdating", true);
                    var d1 = $.cv.css.updateCurrentOrderLine(opts);
                    $.when(d1).done(function (data) {
                        if (data.data.toString().toLowerCase() == 'true') {
                            viewModel.updateItemList();
                            item.set("isProcessing", false);
                            item.set("isUpdating", false);
                            if (displayMessages) {
                                viewModel.clearMessage();
                                viewModel.setMessage(widget.options.textLineUpdatedSuccessfully, $.cv.css.messageTypes.success);
                            }
                        }
                    }).fail(function (msg) {
                        item.set("isProcessing", false);
                        item.set("isUpdating", false);
                        if (displayMessages)
                            viewModel.setMessage(widget.options.textLinesUpdatedUnSuccessfully, $.cv.css.messageTypes.error);
                    });
                }
            };

            var buildLineItemJSON = function (item) {
                var opts = {
                    sequence: item.LineSeq.toString(),
                    quantity: item.OrderedQty.toString(),

                    price:    item.Price != null    ? item.Price    : "-1.0",
                    discount: item.Discount != null ? item.Discount : "-1.0"
                };

                if (widget.options.noteIsExtendedLineDescription) {
                    if (typeof item.ExtendedLineDescription != 'undefined') {
                        opts.note = item.ExtendedLineDescription;
                    } else {
                        opts.note = "";
                    }
                }

                if (typeof item.costCentreCode != 'undefined') {
                    opts.costCentreCode = item.costCentreCode.toString();
                } else {
                    opts.costCentreCode = "";
                }

                if (widget.options.notesRequiredForNonContractPurchasing) {
                    if (typeof item.nonContractReason != 'undefined') {
                        opts.nonContractReason = item.nonContractReason.toString();
                    } else {
                        opts.nonContractReason = "";
                    }
                }

                return opts;
            };

            var removeItemFromFavourites = function (item) {
                var removeSuccess = false, message = "";
                if (item.Product != null && item.StockCode != null && item.StockCode != "") {
                    var pCode = item.StockCode;
                    item.isProcessing = true;
                    item.set("removingFromFavourites", true);
                    var d = $.cv.css.userFavourites.removeFavourites({ productCodes: [pCode] });
                    $.when(d).done(function (msg) {
                        viewModel.set("triggerMessages", true);
                        viewModel.set("clearExistingMessages", true);
                        item.set("removingFromFavourites", false);
                        if (msg.data.toLowerCase() == 'true') {
                            removeSuccess = true;
                            message = widget.options.textProductRemovedFromFavourites.format(pCode);
                        } else {
                            if (msg.errorMessage != null)
                                message = msg.errorMessage;
                            else
                                message = widget.options.textDefaultErrorRemovedFromFavourites.format(pCode);
                        }
                        if (removeSuccess)
                            widget.trigger(PRODUCTREMOVEDFROMFAVOURITES, { productCode: pCode });
                        else
                            widget.trigger(PRODUCTREMOVEDFROMFAVOURITESFAIL, { productCode: pCode, errorMessage: message });
                        item.isProcessing = false;
                        viewModel.clearMessage();
                        viewModel.setMessage(message, removeSuccess ? $.cv.css.messageTypes.success : $.cv.css.messageTypes.error);
                        if (removeSuccess) {
                            item.set("isFavourite", false);
                        }
                        viewModel.set("triggerMessages", widget.options.triggerMessages);
                        viewModel.set("clearExistingMessages", widget.options.clearExistingMessages);
                    });
                }
            };

            var addItemToFavourites = function (item) {
                var addSuccess = false, message = "";
                if (item.Product != null && item.StockCode != null && item.StockCode != "") {
                    var pCode = item.StockCode;
                    item.isProcessing = true;
                    item.set("addingToFavourites", true);
                    var d = $.cv.css.userFavourites.addFavourite({ productCode: pCode });
                    $.when(d).done(function (msg) {
                        viewModel.set("triggerMessages", true);
                        viewModel.set("clearExistingMessages", true);
                        item.set("addingToFavourites", false);
                        if ($.cv.css.userFavourites.defaults && $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite) {
                            message = msg.data.Messages[0];
                            if (msg.data.Success)
                                addSuccess = true;
                        } else {
                            if (msg.data == 'True') {
                                addSuccess = true;
                                message = widget.options.textProductAddedToFavourites;
                            }
                            else {
                                if (msg.errorMessage != null)
                                    message = msg.errorMessage;
                                else
                                    message = widget.options.textDefaultErrorAddingToFavourites;
                            }
                        }
                        if (addSuccess)
                            widget.trigger(PRODUCTADDEDTOFAVOURITES, { productCode: widget.options.productCode });
                        else
                            widget.trigger(PRODUCTADDTOFAVOURITESFAIL, { productCode: widget.options.productCode, errorMessage: msg });
                        item.isProcessing = false;
                        viewModel.clearMessage();
                        viewModel.setMessage(msg.data.Messages[0], addSuccess ? $.cv.css.messageTypes.success : $.cv.css.messageTypes.error);
                        if (addSuccess) {
                            if (widget.options.isAddToFavouritesAMove)
                                item.execCommand_destroy();
                            if (addSuccess) {
                                item.set("isFavourite", true);
                            }
                        }
                        viewModel.set("triggerMessages", widget.options.triggerMessages);
                        viewModel.set("clearExistingMessages", widget.options.clearExistingMessages);
                    });
                }
            };

            var viewModel = kendo.observable({

                // Properties for UI elements
                dataSource: widget.options.dataSource,

                updateViewModelFromDataSource: function () {
                    this.updateItemList();
                    this.updateChargeLineList();
                    this.updateChargeLineWithoutFreightList();
                },

                hidePricingInOrderGrid: widget.options.hidePricingInOrderGrid,

                hidePricingInOrderGridClass: function () {
                    var c = widget.options.pricingOnOrderGridClass;
                    return this.get("hidePricingInOrderGrid") ? c + OPTIONDISABLEDSUFFIX : c + OPTIONENABLEDSUFFIX;
                },

                enableOrderLineNotes: widget.options.enableOrderLineNotes,

                enableOrderLineNotesClass: function () {
                    var c = widget.options.orderLineNotesClass;
                    return this.get("enableOrderLineNotes") ? c + OPTIONENABLEDSUFFIX : c + OPTIONDISABLEDSUFFIX;
                },

                useCostCentres: widget.options.useCostCentres,

                userOrderEntry: widget.options.userOrderEntry,

                useOrderEntryClass: function () {
                    var c = widget.options.useOrderEntryClass;
                    return this.get("userOrderEntry") ? c + OPTIONENABLEDSUFFIX : c + OPTIONDISABLEDSUFFIX;
                },

                notesRequiredForNonContractPurchasingClass: function () {
                    var c = widget.options.notesRequiredForNonContractClass;
                    return widget.options.notesRequiredForNonContractPurchasing ? c + OPTIONENABLEDSUFFIX : c + OPTIONDISABLEDSUFFIX;
                },

                linesCssClasses: function () {
                    var classes = "";
                    classes += this.hidePricingInOrderGridClass();
                    classes = classes.length > 0 ? classes + " " + this.enableOrderLineNotesClass() : this.enableOrderLineNotesClass();
                    classes = classes.length > 0 ? classes + " " + this.useOrderEntryClass() : this.userOrderEntryClass();
                    classes = classes.length > 0 ? classes + " " + this.notesRequiredForNonContractPurchasingClass() : this.notesRequiredForNonContractPurchasingClass();
                    return classes;
                },

                linesCssClassesAdd: function () {
                    return { add: true, cssClass: this.linesCssClasses() };
                },

                message: '',

                hasLines: false,

                hasChargeLines: false,

                hasChargeLinesWithoutFreight: false,

                isProcessing: false,
                isValidating: false,

                // Encompasses whole process: from when we are about to send the update call
                // to when we have finished validation
                isUpdating: function () {
                    return this.get('isProcessing') || this.get('isValidating');
                },

                startUpdating: function () {
                    // isUpdating() returns true when at least 1 of the following conditions is true,
                    // isUpdating() encompases the whole process of updating.
                    this.set('isProcessing', true);
                    this.set('isValidating', true);
                },

                clearExistingMessages: widget.options.clearExistingMessages,

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                clearMessage: function () {
                    this.set("message", "");
                    if (widget.options.triggerMessages) {
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'orderLines', clearExisting: true });
                        if (widget.options.clearAdditionalWidgetMessages.length > 0) {
                            var widgetNames = widget.options.clearAdditionalWidgetMessages.split(",");
                            for (var i = 0; i < widgetNames.length; i++) {
                                $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: widgetNames[i], clearExisting: true });
                            }
                        }
                    }
                },

                cartUpdated: function () {
                    widget.initDataSource();
                },

                updateItemList: function () {
                    var dataView = getDataView(ITEMLISTVIEW);
                    this.set("itemList", dataView);
                    this.set("hasLines", dataView.length > 0);
                    widget.trigger(LINESRENDERED, { count: dataView.length });
                    $.cv.css.trigger($.cv.css.eventnames.cartLinesRendered, { stampsWidgetContainer: widget.options.stampsWidgetContainer, lines: this.get("itemList") });
                },

                updateChargeLineList: function () {
                    var dataView = getDataView(CHARGELINELISTVIEW);
                    this.set("chargeLineList", dataView);
                    this.set("hasChargeLines", dataView.length > 0);
                    widget.trigger(CHARGELINESRENDERED, { count: dataView.length });
                },

                updateChargeLineWithoutFreightList: function () {
                    var dataView = _.filter(getDataView(CHARGELINELISTVIEW), function (line) { return line.SolChgType != widget.options.freightChargeType; });
                    this.set("chargeLineWithoutFreightList", dataView);
                    this.set("hasChargeLinesWithoutFreight", dataView.length > 0);
                    widget.trigger(CHARGELINESWITHOUTFREIGHTRENDERED, { count: dataView.length });
                },

                itemList: [],

                chargeLineList: [],

                chargeLineWithoutFreightList: [],

                // function called externally from the widget, allow individual line errors to be regsitered against the line iteself
                displayLineMessages: function (lineMessages, lines) {
                    var itemList = this.get("itemList"), linesWithoutErrors = [];
                    itemList = $.grep(itemList, function (e) { return e.StockCode != null; });
                    if (lineMessages.length > 0) {
                        if (lines.length == 0)
                            linesWithoutErrors = itemList;
                        else {
                            linesWithoutErrors = _.filter(itemList, function (item) { return _.contains(lines.toString().split(","), item.LineSeq.toString()); });
                        }
                        $.each(lineMessages, function (idx, item) {
                            var hasErrors = false, messages = "", errorTypes = "";
                            $.each(item["lineMessages"], function (idx, lineMessage) {
                                var line = $.grep(itemList, function (e) { return e.LineSeq == lineMessage.lineSeq; });
                                linesWithoutErrors = $.grep(linesWithoutErrors, function (e) { return e.LineSeq != lineMessage.lineSeq; });
                                if (line.length > 0) {
                                    if (lineMessage.errorMessage != "") {
                                        hasErrors = true;
                                        messages = messages == "" ? lineMessage.errorMessage : messages + widget.options.lineErrorSeparator + lineMessage.errorMessage;
                                        errorTypes = errorTypes == "" ? lineMessage.errorType : errorTypes + " " + lineMessage.errorType;
                                        if (hasErrors)
                                            line[0].execCommand_updateErrorMessage(hasErrors, messages, errorTypes);
                                    }
                                }
                            });
                        });
                        $.each(linesWithoutErrors, function (idx, item) {
                            item.execCommand_updateErrorMessage(false, "", "");
                        });
                    }
                },

                clearLineMessages: function (lines) {
                    var itemList = this.get("itemList"), clearLines = [];
                    itemList = $.grep(itemList, function (e) { return e.StockCode != null; });
                    clearLines = _.filter(itemList, function (item) { return _.contains(lines.toString().split(","), item.LineSeq.toString()); });
                    $.each(clearLines, function (idx, item) {
                        item.execCommand_updateErrorMessage(false, "", "");
                    });
                },

                setLocalLinesDeleted: function (deleted) {
                    localUpdatedCurrentOrderLines = $.cv.css.localGetUpdatedCurrentOrderLines();
                    if (localUpdatedCurrentOrderLines == null)
                        localUpdatedCurrentOrderLines = { linesDeleted: deleted };
                    $.cv.css.localSetUpdatedCurrentOrderLines(localUpdatedCurrentOrderLines);
                },

                setLocalLinesUpdated: function (updateList) {
                    var updatedLines = [];
                    localUpdatedCurrentOrderLines = $.cv.css.localGetUpdatedCurrentOrderLines();
                    if (localUpdatedCurrentOrderLines == null)
                        localUpdatedCurrentOrderLines = { updatedLines: [] };
                    if (localUpdatedCurrentOrderLines["updatedLines"] != undefined)
                        updatedLines = localUpdatedCurrentOrderLines["updatedLines"];
                    // if updated lines exists merge them with the current list of lines being updated else set the list of updated list to the current list of lines
                    if (updatedLines != null && updatedLines.length > 0) {
                        updatedLines = _.union(updatedLines, _.pluck(updateList, 'sequence'));
                    } else {
                        updatedLines = _.pluck(updateList, 'sequence');
                    }
                    localUpdatedCurrentOrderLines["updatedLines"] = updatedLines;
                    // store the list of updated lines in local storage
                    $.cv.css.localSetUpdatedCurrentOrderLines(localUpdatedCurrentOrderLines);
                },

                updateLinesFlaggedForUpdate: function (displayMessages, triggerOrderRefresh) {
                    var _this = this, batchData = [], def = new $.Deferred();
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : true;
                    triggerOrderRefresh = typeof triggerOrderRefresh !== 'undefined' ? triggerOrderRefresh : true;
                    $.each(this.get("itemList"), function (idx, item) {
                        // don't need to include deleted lines in an update
                        if (item.requiresUpdate && !item.requiresDelete) {
                            batchData.push(buildLineItemJSON(item));
                        }
                    });
                    if (batchData.length > 0) {
                        def = _this.updateAllLines(batchData, displayMessages, triggerOrderRefresh);
                    } else {
                        def.resolve();
                    }
                    return def;
                },

                updateAllLines: function (batchData, displayMessages, triggerOrderRefresh) {
                    var _this = this, d1 = new $.Deferred(), d2 = new $.Deferred(), d3 = new $.Deferred(), itemList = [], deleteList = [], updateList = [], localUpdatedCurrentOrderLines = {};
                    batchData = typeof batchData !== 'undefined' ? batchData : [];
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : true;
                    triggerOrderRefresh = typeof triggerOrderRefresh !== 'undefined' ? triggerOrderRefresh : true;
                    if (batchData.length == 0) {
                        $.each(widget.dataSource.view(), function (idx, item) {
                            batchData.push(buildLineItemJSON(item));
                        });
                    }
                    if (batchData.length > 0) {
                        deleteList = _.filter(batchData, function (item) { return item.quantity == 0; });
                        updateList = _.filter(batchData, function (item) { return item.quantity != 0; });
                        if (displayMessages) {
                            _this.clearMessage();
                        }
                        if (deleteList.length > 0) {
                            _this.setLocalLinesDeleted(true);
                            for (var i = 0; i < deleteList.length; i++) {
                                deleteList[i]["seq"] = deleteList[i]["sequence"];
                            }
                            d3 = _this.deleteAllLines(deleteList, displayMessages, triggerOrderRefresh && updateList.length == 0);
                        } else {
                            _this.setLocalLinesDeleted(false);
                            d3.resolve();
                        }
                        if (updateList.length > 0) {
                            _this.setLocalLinesUpdated(updateList);

                            _this.startUpdating(); // isProcessing & isValidating set to true

                            // Update Lines
                            d2 = $.cv.css.orders.updateCurrentOrderLineBulk({ batchData: updateList, triggerOrderRefresh: triggerOrderRefresh });
                            $.when(d2).done(function (data) {
                                _this.set("isProcessing", false);
                                var linesSuccessful = '';
                                var linesUnSuccessful = '';
                                if (!widget.options.messagesUseLineSeq)
                                    var itemList = _this.get("itemList");
                                for (var i = 0; i < data.data.length; i++) {
                                    switch (data.data[i].toLowerCase()) {
                                        case "true":
                                            linesSuccessful += (widget.options.messagesUseLineSeq ? batchData[i].sequence : $.grep(itemList, function (e) { return e.LineSeq == updateList[i].sequence; })[0].Index + 1) + ",";
                                            break;
                                        default:
                                            linesUnSuccessful += (widget.options.messagesUseLineSeq ? batchData[i].sequence : $.grep(itemList, function (e) { return e.LineSeq == updateList[i].sequence; })[0].Index + 1) + ",";
                                    }
                                }
                                if (linesSuccessful != "") {
                                    if (displayMessages) {
                                        if (widget.options.linesUpdatedSuccessfullyGeneric.length > 0) {
                                            _this.setMessage(widget.options.linesUpdatedSuccessfullyGeneric, $.cv.css.messageTypes.success);
                                        } else {
                                            _this.setMessage("Lines " + linesSuccessful + " " + widget.options.textLinesUpdatedSuccessfully, $.cv.css.messageTypes.success);
                                        }
                                    }
                                    if (triggerOrderRefresh)
                                        _this.updateItemList();
                                }
                                if (linesUnSuccessful != "") {
                                    if (displayMessages) {
                                        if (widget.options.linesUpdatedUnSuccessfullyGeneric.length > 0) {
                                            _this.setMessage(widget.options.linesUpdatedUnSuccessfullyGeneric, $.cv.css.messageTypes.success);
                                        } else {
                                            _this.setMessage("Lines " + linesUnSuccessful + " " + widget.options.textLinesUpdatedUnSuccessfully, $.cv.css.messageTypes.error);
                                        }
                                    }
                                }
                                widget.trigger(LINESUPDATED, { linesSuccessful: linesSuccessful, linesUnSuccessful: linesUnSuccessful, displayMessages: displayMessages });
                            }).fail(function (msg) {
                                _this.set("isProcessing", false);
                                if (displayMessages)
                                    _this.setMessage(widget.options.textLinesUpdatedErrored, $.cv.css.messageTypes.error);
                            });
                        } else {
                            d2.resolve();
                        }
                        $.when(d2, d3).always(function (data) {
                            d1.resolve();
                        });
                    } else {
                        d1.resolve();
                    }
                    return d1;
                },

                deleteLinesFlaggedForDelete: function (displayMessages, triggerOrderRefresh) {
                    var _this = this, batchData = [], def = new $.Deferred();
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : true;
                    triggerOrderRefresh = typeof triggerOrderRefresh !== 'undefined' ? triggerOrderRefresh : true;
                    $.each(this.get("itemList"), function (idx, item) {
                        if (item.requiresDelete) {
                            batchData.push({ seq: item.LineSeq.toString() });
                        } else if (item.hasLineNotes) {
                            $.each(item.lineNotes, function (idx2, lineNote) {
                                if (lineNote.requiresDelete)
                                    batchData.push({ seq: lineNote.LineSeq.toString() });
                            });
                        }
                    });
                    if (batchData.length > 0) {
                        def = _this.deleteAllLines(batchData, displayMessages, triggerOrderRefresh);
                    } else {
                        def.resolve();
                    }
                    return def;
                },

                deleteAllLines: function (batchData, displayMessages, triggerOrderRefresh) {
                    var _this = this, d1 = new $.Deferred(), productsRemoved = "";
                    batchData = typeof batchData !== 'undefined' ? batchData : [];
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : true;
                    triggerOrderRefresh = typeof triggerOrderRefresh !== 'undefined' ? triggerOrderRefresh : true;
                    if (batchData.length == 0) {
                        $.each(this.get("itemList"), function (idx, item) {
                            batchData.push({ seq: item.LineSeq.toString() });
                        });
                    }
                    if (batchData.length > 0) {
                        _this.setLocalLinesDeleted(true);
                        var d1 = $.cv.css.orders.deleteCurrentOrderLineBulk({ batchData: batchData, triggerOrderRefresh: triggerOrderRefresh });
                        $.when(d1).done(function (data) {
                            $.each(batchData, function (idx, item) {
                                lineItem = $.grep(widget.dataSource.view(), function (e) { return e.LineSeq == item.seq; });
                                if (lineItem.length == 1) {
                                    if (lineItem[0].StockCode)
                                        productsRemoved = productsRemoved.length == 0 ? lineItem[0].StockCode : productsRemoved + ", " + lineItem[0].StockCode;
                                    try {
                                        widget.dataSource.remove(lineItem[0]);
                                    }
                                    catch (err) {}
                                } else {
                                    lineItem = $.grep(widget.dataSource.view(), function(e) {return e.LineSeq == Math.floor(item.seq);});
                                    if(lineItem.length == 1) {
                                        var removeIndexes = [ ];
                                        $.each(lineItem [0].lineNotes, function(idx2, lineNote) {
                                            if(lineNote.LineSeq == item.seq)
                                                removeIndexes.push(idx2);
                                        });
                                        if(removeIndexes.length > 0) {
                                            removeIndexes = removeIndexes.sort(function(a, b) {return b - a;});
                                            for(var i = 0;i < removeIndexes.length;i++)
                                                lineItem [0].lineNotes = lineItem [0].lineNotes.splice(removeIndexes [i], 1);
                                        }
                                    }
                                }
                            });
                            if (displayMessages) {
                                _this.clearMessage();
                                if (batchData.length == 1)
                                    _this.setMessage(widget.options.textLineDeletedSuccessfully.format(productsRemoved), $.cv.css.messageTypes.success);
                                else
                                    _this.setMessage(widget.options.textLinesDeletedSuccessfully.format(productsRemoved), $.cv.css.messageTypes.success);
                            }
                        });
                    } else {
                        d1.resolve();
                    }
                    return d1;
                },

                getLineErrorCount: function () {
                    var count = 0;
                    $.each(this.get("itemList"), function (idx, item) {
                        if (item.HasErrors) {
                            count++;
                        }
                    });
                    return count;
                },

                refreshLines: function () {
                    widget.initDataSource();
                },

                retainSortSelected: function () {
                    var dssort = widget.dataSource.sort();
                    var sortField = '';
                    var sortDirection = "";
                    if (dssort && dssort.length > 0) {
                        sortField = dssort[0].field;
                        sortDirection = dssort[0].dir != undefined ? dssort[0].dir : "asc";
                        widget.options.defaultLinesSortField = sortField;
                        widget.options.defaultLinesSortDirection = sortDirection;
                    }
                }

            });

            initSettings();

            viewModel.bind("change", function (e) {
                if (e.field == "dataSource") {
                    viewModel.retainSortSelected();     
                }
            });

            return viewModel;
        },

        _getDefaultSorterTemplate: function () {
            var widget = this;
            if (widget.options.searchFields && (widget.options.searchPosition == POSITIONBOTH || widget.options.searchPosition == POSITIONTOP)) {
                return "<div data-role='sorter' data-sort-options='#= sortOptions #' data-bind='source: dataSource'></div>";
            }
            return "";
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = ""
                    + "<div>"
                    + "<div class='itemList' data-bind='source: itemList' data-template='" + widget.options.itemViewTemplate + "'></div>"
                    + "<div class='chargeLineList' data-bind='source: chargeLineList' data-template='" + widget.options.chargeLineViewTemplate + "'></div>";
            html += "</div>";
            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemViewTemplate + "'>";
            html += "<div>";
            html += "Product";
            html += "</div></script>";
            return html;
        },

        _getDefaultChargeLineViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.chargeLineViewTemplate + "'>";
            html += "<div>";
            html += "Charge Line";
            html += "</div></script>";
            return html;
        }

    }; // register the widget

    $.cv.ui.widget(orderLinesWidget);

})(jQuery);;
/* Name: delivery address
* Author: Aidan Thomas
* Created: 20130220 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
*          /Scripts/cv.css.deliveryAddress.js
* Params:  
*           orderComments: 
*           deliveryInstructions: 
*           orderReference: 
*           copyOrderConfirmation: 
*           soDelCountry: 
*           soDelPhone: 
*           autoBind: 
*           triggerMessages: 
*           enterOrderComments: 
*           disableDeliveryAddress: 
*           showDeliveryInstructions: 
*           forceOrderReference: 
*           allowCopyOrderConfirmation: 
*           textOrderReferenceMandatory: 
*           textCustomerReferenceUpdatedSuccess: 
*           textDeliveryAddressUpdatedSuccess: 
*           textOrderCommentsUpdatedSuccess: 
*           textDeliveryInstructionsUpdatedSuccess: 
*           textCopyOrderConfirmationUpdatedSuccess: 
*           textCopyOrderConfirmationInvalidEmails: 
*           settings: 
*           resources: 
*           useSettings: 
*           viewTemplate: kendo template id for the main view
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        ADDRESSRENDERED = "addressRendered",
        MULTIADDRESSRENDERED = "multiAddressRendered",
        ADDRESSVALIDATIONRENDERED = "addressValidationRendered",
        ADDRESSVALIDATIONOPTIONSELECTED = "addressValidationOptionSelected",
        COPYADDRESSTOBILLING = "addressToBilling",
        COPYBILLINGTOADDRESS = "billingToAddress";

    var deliveryAddressWidget = {


        // Standard Variables

        // widget name
        name: "deliveryAddress",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            multipleDeliveryAddressDataSource: [],
            deliveryAddressValidationDataSource: [],
            orderComments: '',
            deliveryInstructions: '',
            orderReference: '',
            copyOrderConfirmation: '',
            soDelCountry: '',
            soDelPhone: '',
            sessionTimeOutRedirectUrl: "login.aspx",
            multipleAddressTextField: "Name",
            multipleAddressValueField: "Name",
            addressValidationTextField: "label",
            addressValidationValueField: "value",
            defaultMultipleAddressValue: '',
            defaultAddressValidationValue: '',
            inputErrorClass: "input-error",
            deliveryAddressMode: "Delivery",
            // viewModel flags
            autoBind: true,
            triggerMessages: false,
            triggerErrorMessages: false,
            triggerExternalValidationMessages: false,
            includeInBrowserHistory: false,
            showCustomerReferenceUpdatedMessage: false,
            updateAddressOnChanged: false,
            autoSelectAddressValidationValue: false,
            saveAddressForUser: true,
            saveDeliveryInstructions: false,
            showPickupDeliveryControls: false,
            pickupContactFieldData: [],
            defaultLinkDeliveryAndBilling: false,
            useNegationLogicForLinkingAddress: false,
            showBillingAddressOnPickup: false,
            clearOnUncheckedLink: true,
            saveAddressDownToUser: true,
            saveBillAddressDownToUser: false,
            addressMapMode: COPYBILLINGTOADDRESS,
            loadBlankBillingFromDelivery: false,
            preserveLoadedDeliveryAddress: false,
            callValidateServiceOnCheckout: false,
            fieldMapFrom: "0,1,2,3,4,5,6,7,8",
            userAddressFields: "DeliveryContactName,DeliveryAddress1,DeliveryAddress2,DeliverySuburb,DeliveryState,DeliveryCountry,DeliveryPostcode,PhoneNumber",
            saveAddressFieldGroup: "saveAddressFieldGroup",
            // events
            // view flags
            // view text defaults
            textErrorGettingDeliveryAddress: "There was an error getting the delivery address for the order",
            textErrorUpdatingDeliveryAddress: "There was an error updating the delivery address for the order",
            textErrorValidatingSuburbPostcode: "There was an error validating the delivery address' suburb and postcode",
            textCustomerReferenceUpdatedSuccess: 'Customer reference updated successfully',
            textDeliveryAddressUpdatedSuccess: 'Delivery address updated successfully',
            textBillingAddressUpdatedSuccess: 'Billing address updated successfully',
            textCopyOrderConfirmationInvalidEmails: 'One of the copy confirmation email addresses is invalid',
            textErrorGettingMutipleDeliveryAddress: "There was an error retrieving your list of delivery addresses",
            textUpdatingAttentionToSuccess: "Attention to and phone number updated successfully",
            textErrorUpdatingAttentionTo: "There was an error updating the attention to or phone number",
            textErrorSavingDeliveryAddress: "There was an error saving your delivery address",
            textErrorMissingDeliveryAddressFields: "Not all the mandatory address fields have been populated",
            textErrorCannotEditAddressOnPickup: "The delivery address cannot be changed when using pickup",
            defaultMultipleAddressText: 'Please Select...',
            defaultAddressValidationText: 'Please Select...',
            textMandatoryFieldNotComplete: "",
            // widget settings
            enterOrderComments: true,
            disableDeliveryAddress: false,
            disableBillingAddress: false,
            showDeliveryInstructions: true,
            forceOrderReference: true,
            allowCopyOrderConfirmation: true,
            copyOrderConfirmationMandatory: false,
            hasMultipleDeliveryAddresses: false,
            showAttentionToUserDetails: false,
            isViewOnly: false,
            textOrderReferenceMandatory: 'Please enter an order reference',
            textCopyOrderConfirmationMandatory: 'Please enter an order confirmation copy email address',
            textPickupContactDetailsIncomplete: "Pickup contact details are incomplete",
            textOrderCommentsUpdatedSuccess: 'Order comments updated successfully',
            textDeliveryInstructionsUpdatedSuccess: 'Delivery instructions updated successfully',
            textCopyOrderConfirmationUpdatedSuccess: 'Copy order confirmation updated successfully',
            attentionUserName: "",
            attentionPhoneNumber: "",
            multiUpdateThrottleTimeout: 0,
            // view Template
            viewTemplate: '', // treat like its an id
            addressViewTemplate: null,
            multipleAddressViewTemplate: null,
            addressValidationViewTemplate: null,

            // Pickup view only field mapping
            pickupSuburbFieldName: 'SoDelAddr3',
            pickupStateFieldName: 'SoDelAddr4',

            // Suburb and Postcode Validation
            enableSuburbAndPostcodeValidation: false,
            suburbFieldName: 'SoDelSuburb',
            postcodeFieldName: 'SoDelPostcode',
            billingSuburbFieldName: 'SoBillSuburb',
            billingPostcodeFieldName: 'SoBillPostcode',
            suburbAndPostcodeInvalidMessage: 'The suburb and postcode don\'t correspond.'
        },

        events: [DATABINDING, DATABOUND, MULTIADDRESSRENDERED, ADDRESSRENDERED, ADDRESSVALIDATIONRENDERED, ADDRESSVALIDATIONOPTIONSELECTED],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,
        _addressViewAppended: false,
        _multipleAddressViewAppended: false,
        _addressValidationViewAppended: false,

        // Standard Methods
        initialise: function (el, o) {

            var widget = this;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                widget._viewAppended = true;
                if (!widget.options.addressViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.addressViewTemplate = widget.name + "-address-template-" + kendo.guid();
                    widget._addressViewAppended = true;
                }
                if (!widget.options.multipleAddressViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.multipleAddressViewTemplate = widget.name + "-multi-address-template-" + kendo.guid();
                    widget._multipleAddressViewAppended = true;
                }
                if (!widget.options.addressValidationViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.addressValidationViewTemplate = widget.name + "-address-validation-template-" + kendo.guid();
                    widget._addressValidationViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the addressViewTemplate (not parsed)
                if (widget._addressViewAppended) {
                    widget.view += widget._getDefaultAddressViewTemplate();
                }
                // add the multipleAddressViewTemplate (not parsed)
                if (widget._multipleAddressViewAppended) {
                    widget.view += widget._getDefaultMultipleAddressViewTemplate();
                }
                // add the addressValidationViewTemplate (not parsed)
                if (widget._addressValidationViewAppended) {
                    widget.view += widget._getDefaultAddressValidationViewTemplate();
                }
                widget.element.html(widget.view);
            }
            // now MMVM bind
            widget.viewModel = widget._getViewModel();
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            widget.trigger(DATABOUND);
            if (widget.options.isViewOnly) {
                $.cv.css.bind($.cv.css.eventnames.localdeliveryAddressChanged, $.proxy(widget.viewModel.localdeliveryAddressChanged, widget.viewModel));
                $.cv.css.bind($.cv.css.eventnames.deliveryAddressModeChanged, function (msg) {
                    widget.viewModel.set("deliveryAddressMode", msg.message)
                });
            }

            widget._setThrottles();
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        validateInputFields: function (showMessages) {
            var widget = this,
                triggerMessages = widget.options.triggerMessages,
                validateDeferred = $.Deferred();
            if (!widget.options.isViewOnly) {
                if (widget.options.triggerExternalValidationMessages) {
                    widget.options.triggerMessages = true;
                }
                validateDeferred = widget.viewModel.validateInputFields(showMessages, widget.options.callValidateServiceOnCheckout);
                widget.options.triggerMessages = triggerMessages;
            }
            return validateDeferred;
        },

        _setThrottles: function () {
            var widget = this;
            widget._updateDeliveryAddressThrottled = _.debounce(function () {
                if (widget.options.multiUpdateThrottleTimeout > 0) {
                    var triggerUpdate = !widget.viewModel.get("isAddressBeingEdited");
                    if (widget.viewModel.get("deliveryAddressMode") != "Pickup" && widget.options.addressMapMode == COPYBILLINGTOADDRESS && widget.viewModel.shouldAddressBeCopied()) {
                        triggerUpdate = !widget.viewModel.get("isBillingAddressBeingEdited");
                    }
                    if (triggerUpdate) {
                        return widget.viewModel._updateDeliveryAddress.apply(widget.viewModel, arguments);
                    }
                } else {
                    return widget.viewModel._updateDeliveryAddress.apply(widget.viewModel, arguments);
                }
            }, widget.options.multiUpdateThrottleTimeout);
            widget._updateBillingAddressThrottled = _.debounce(function () {
                if (widget.options.multiUpdateThrottleTimeout > 0) {
                    var triggerUpdate = !widget.viewModel.get("isBillingAddressBeingEdited");
                    if (widget.viewModel.get("deliveryAddressMode") != "Pickup" && widget.options.addressMapMode == COPYADDRESSTOBILLING && widget.viewModel.shouldAddressBeCopied()) {
                        triggerUpdate = !widget.viewModel.get("isAddressBeingEdited");
                    }
                    if (triggerUpdate) {
                        return widget.viewModel._updateBillingAddress.apply(widget.viewModel, arguments);
                    }
                } else {
                    return widget.viewModel._updateBillingAddress.apply(widget.viewModel, arguments);
                }
            }, widget.options.multiUpdateThrottleTimeout);
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var init = function () {
                if (!widget.options.isViewOnly) {
                    loadSoDelAddress().done(function (response) {
                        // Skip suburb-postcode validation if disabled.
                        if (widget.options.enableSuburbAndPostcodeValidation !== true) {
                            return;
                        }

                        // So We SHOULD have bill to and delivery address assigned
                        // Validate Post Code and Suburbs for both
                        if (response && response.data && response.data.result && response.data.result.length > 0) {
                            // Data has already been assigned to the ViewModel fields below...
                            var addrList = viewModel.get('addressItemList');
                            var billAddrList = viewModel.get('billingAddressItemList');

                            // .. therefore extract 3 required fields for suburb-postcode validation
                            // for both delivery and billing...
                            var addr = {
                                suburb: '',
                                postcode: '',
                                country: ''
                            },
                                bill = {
                                    suburb: '',
                                    postcode: '',
                                    country: ''
                                };

                            _.each(addrList, function (item) {
                                if (item.fieldItem.FieldName === "SoDelCountry") {
                                    addr.country = item.value;
                                    return;
                                }

                                if (item.fieldItem.FieldName === widget.options.suburbFieldName) {
                                    addr.suburb = item.value;
                                    return;
                                }

                                if (item.fieldItem.FieldName === widget.options.postcodeFieldName) {
                                    addr.postcode = item.value;
                                    return;
                                }
                            });

                            _.each(billAddrList, function (item) {
                                if (item.fieldItem.FieldName === "SoDelCountry") {
                                    bill.country = item.value;
                                    return;
                                }

                                if (item.fieldItem.FieldName === widget.options.billingSuburbFieldName) {
                                    bill.suburb = item.value;
                                    return;
                                }

                                if (item.fieldItem.FieldName === widget.options.billingPostcodeFieldName) {
                                    bill.postcode = item.value;
                                    return;
                                }
                            });

                            // ... and validate them ...
                            var d1 = viewModel.validateSuburbAndPostCodeInternal(addr.suburb, addr.postcode, addr.country, false /* delivery */);
                            var d2 = viewModel.validateSuburbAndPostCodeInternal(bill.suburb, bill.postcode, bill.country, true  /* billing */);
                            $.when(d1, d2).done(function () {
                                viewModel.validateInputFields(false);
                            });
                        }
                    });

                    // Multiple delivery addresses
                    if (widget.options.hasMultipleDeliveryAddresses && widget.options.multipleDeliveryAddressDataSource.length == 0) {
                        getDeliveryAddresses();
                    } else {
                        setMultipleDeliveryAddressDataSource();
                    }
                    if (widget.options.showAttentionToUserDetails) {
                        viewModel.setAttentionToUserName(false);
                        viewModel.setAttentionToPhoneNumber(false);
                    }
                } else {
                    viewModel.localdeliveryAddressChanged();
                }
            };

            var loadSoDelAddress = function () {
                var d1 = $.cv.css.deliveryAddress.getDeliveryAddressForCurrentOrder();

                d1.done(function (msg) {
                    var data = msg.data;
                    if (!msg.sessionHasTimedOut) {
                        processAddressData(data);
                    } else {
                        viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                    }
                }).fail(function () {
                    viewModel.setMessage(widget.options.textErrorGettingDeliveryAddress, $.cv.css.messageTypes.error);
                });

                return d1;
            };

            var processAddressData = function (data) {
                var deliveryAddressMode =  viewModel.get("deliveryAddressMode");
                viewModel.set("isInitialLoad", true);
                if (data.result && data.result.length > 0) {
                    for (var i = 0; i < 1; i++) {
                        viewModel.set("deliveryInstructions", data.result[i].SoDelIns);
                        viewModel.set("orderReference", data.result[i].SoCustReference);
                        viewModel.set("copyOrderConfirmation", data.result[i].CopyOrderConfirmationEmail);
                        viewModel.set("orderComments", data.result[i].SoComments);
                        viewModel.set("soDelCountry", data.result[i].SoDelCountry);
                        viewModel.set("soDelPhone", data.result[i].SoDelPhone);
                        if (data.result[i].ContactFirstName !== undefined) {
                            viewModel.set("contactFirstName", deliveryAddressMode == "Pickup" ? data.result[i].ContactFirstName : "");
                        }
                        if (data.result[i].ContactLastName !== undefined) {
                            viewModel.set("contactLastName", deliveryAddressMode == "Pickup" ? data.result[i].ContactLastName : "");
                        }
                        if (data.result[i].ContactPhoneNumber !== undefined) {
                            viewModel.set("contactPhoneNumber", deliveryAddressMode == "Pickup" ? data.result[i].ContactPhoneNumber : "");
                        }
                        if (viewModel.get("deliveryAddressObjectKey").length == 0)
                            viewModel.set("deliveryAddressObjectKey", data.result[i]._objectKey);
                        var dataListTemplates = _getAddressDataView(data.result[i].AddressFieldData, false);
                        if (dataListTemplates.length > 0)
                            viewModel.set('addressFieldsExist', true);
                        viewModel.set('addressItemList', dataListTemplates);
                        if (data.result[i].BillingAddressFieldData && data.result[i].BillingAddressFieldData != null) {
                            var billingAddressListTemplates = _getAddressDataView(data.result[i].BillingAddressFieldData, true);
                            if (billingAddressListTemplates.length > 0)
                                viewModel.set('billingAddressFieldsExist', true);
                            viewModel.set('billingAddressItemList', billingAddressListTemplates);
                            if (widget.options.addressMapMode == COPYBILLINGTOADDRESS && widget.options.loadBlankBillingFromDelivery && viewModel.isBillingAddressEmpty() && !viewModel.isDeliveryAddressEmpty()) {
                                widget.options.addressMapMode = COPYADDRESSTOBILLING;
                                viewModel.copyAddressFields(false);
                                viewModel.triggerCopyUpdate();
                                widget.options.addressMapMode = COPYBILLINGTOADDRESS;
                            }
                            if (viewModel.shouldAddressBeCopied() && !viewModel.deliveryAndBillingMatch()) {
                                // only copy if they are not already the same
                                if (!widget.options.preserveLoadedDeliveryAddress
                                    || (widget.options.addressMapMode == COPYBILLINGTOADDRESS && viewModel.isDeliveryAddressEmpty())
                                    || widget.options.addressMapMode == COPYADDRESSTOBILLING) {
                                    viewModel.copyAddressFields();
                                    viewModel.triggerCopyUpdate();
                                } else {
                                    viewModel.set("preserveAddressData", true);
                                    if (widget.options.useNegationLogicForLinkingAddress) {
                                        viewModel.set("linkDeliveryAndBilling", true);
                                    } else {
                                        viewModel.set("linkDeliveryAndBilling", false);
                                    }
                                    viewModel.set("preserveAddressData", false);
                                }
                            }
                            viewModel.disableEnableCopiedAddressFields();
                        }
                        widget.trigger(ADDRESSRENDERED);
                    }
                }

                viewModel.validateInputFields(false);
                viewModel.set("isInitialLoad", false);
            };

            var localProcessAddressData = function (data) {
                if (data.result && data.result.length > 0) {
                    for (var i = 0; i < 1; i++) {
                        viewModel.set("deliveryInstructions", data.result[i].SoDelIns);
                        viewModel.set("orderReference", data.result[i].SoCustReference);
                        viewModel.set("copyOrderConfirmation", data.result[i].CopyOrderConfirmationEmail);
                        viewModel.set("orderComments", data.result[i].SoComments);
                        viewModel.set("soDelCountry", data.result[i].SoDelCountry);
                        viewModel.set("soDelPhone", data.result[i].SoDelPhone);
                        viewModel.set("attentionUserName", data.result[i].AttentionUserName);
                        viewModel.set("attentionPhoneNumber", data.result[i].AttentionPhoneNumber);
                        if (data.result[i].ContactFirstName !== undefined) {
                            viewModel.set("contactFirstName", data.result[i].ContactFirstName);
                        }
                        if (data.result[i].ContactLastName !== undefined) {
                            viewModel.set("contactLastName", data.result[i].ContactLastName);
                        }
                        if (data.result[i].ContactPhoneNumber !== undefined) {
                            viewModel.set("contactPhoneNumber", data.result[i].ContactPhoneNumber);
                        }
                        if (data.result[i].AddressFieldData > 0)
                            viewModel.set('addressFieldsExist', true);
                        viewModel.set('addressItemList', data.result[i].AddressFieldData);
                        if (data.result[i].BillingAddressFieldData.length > 0)
                            viewModel.set('billingAddressFieldsExist', true);
                        viewModel.set('billingAddressItemList', data.result[i].BillingAddressFieldData);
                    }
                }
            };

            var getDeliveryAddresses = function () {
                var d1 = $.cv.css.deliveryAddress.getDeliveryAddressesForCurrentUser();
                $.when(d1).done(function (msg) {
                    var data = msg.data;
                    if (!msg.sessionHasTimedOut) {
                        widget.options.multipleDeliveryAddressDataSource = data;
                        setMultipleDeliveryAddressDataSource();
                    } else {
                        viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                    }
                }).fail(function () {
                    viewModel.setMessage(widget.options.textErrorGettingMutipleDeliveryAddress, $.cv.css.messageTypes.error);
                });
            };

            var setMultipleDeliveryAddressDataSource = function () {
                widget.multipleDeliveryAddressDataSource = kendo.data.DataSource.create(widget.options.multipleDeliveryAddressDataSource);

                if (widget.options.autoBind) {
                    widget.multipleDeliveryAddressDataSource.fetch();
                }
                viewModel.updateMultiAddressItemList();
            };

            var setDeliveryAddressValidationDataSource = function () {
                widget.deliveryAddressValidationDataSource = kendo.data.DataSource.create(widget.options.deliveryAddressValidationDataSource);

                if (widget.options.autoBind) {
                    widget.deliveryAddressValidationDataSource.fetch();
                }
                viewModel.updateAddressValidationItemList();
            };

            var triggerChangeOnTimeout = _.debounce(function (e, dataItem) {
                var obj = $(":focus");
                if (obj && obj.prevObject && obj.prevObject.length > 0) {
                    var activeElement = $(obj.prevObject[0].activeElement);
                    $(activeElement).change();
                    if (dataItem.fieldValid(e)) {
                        viewModel.set("changeTriggered", true);
                    }
                }
            }, widget.options.multiUpdateThrottleTimeout);

            var _getAddressDataView = function (data, isBilling) {
                //var widget = this;
                var array = [];
                // get index item
                var item = data;

                $.each(item, function (indexFields, fieldToUse) {
                    if (!isBilling && viewModel.get("disableDeliveryAddress")) {
                        fieldToUse.Readonly = true;
                    }
                    fieldToUse.addKeyupEvent = widget.options.multiUpdateThrottleTimeout != 0;
                    fieldToUse.addKeydownEvent = widget.options.multiUpdateThrottleTimeout != 0;
                    var dataItem = $.cv.util.getFieldItemData(fieldToUse);
                    // override default dataChanged event
                    dataItem.dataChanged = function (e) {
                        if (dataItem.fieldValid(e) && widget.options.updateAddressOnChanged && !viewModel.get("changeTriggered")) {
                            var soDelAddress = {};
                            var soBillAddress = {};
                            if (viewModel.shouldAddressBeCopied() && !(viewModel.get("deliveryAddressMode") == "Pickup" && widget.options.addressMapMode == COPYBILLINGTOADDRESS)) {
                                if (widget.options.multiUpdateThrottleTimeout != 0) {
                                    viewModel.processLinkedAddresses(e);
                                }
                                soDelAddress = viewModel.getSoDelAddress();
                                soBillAddress = viewModel.getSoBillAddress();
                                viewModel.updateDeliveryAddress(soDelAddress, "");
                                viewModel.updateBillingAddress(soBillAddress, "");
                            } else {
                                if (!isBilling) {
                                    soDelAddress = viewModel.getSoDelAddress();
                                    viewModel.updateDeliveryAddress(soDelAddress, "");
                                } else {
                                    soBillAddress = viewModel.getSoBillAddress();
                                    viewModel.updateBillingAddress(soBillAddress, "");
                                }
                            }
                            if (!isBilling) {
                                viewModel.set("isAddressBeingEdited", false);
                            } else {
                                viewModel.set("isBillingAddressBeingEdited", false);
                            }
                        } else {
                            viewModel.set("changeTriggered", false);
                        }
                    };
                    dataItem.dataKeydown = function (e) {
                        this._previousVal = this.get(this.fieldItem.FieldName);
                    };
                    dataItem.dataKeyup = function (e) {
                        var currVal = this.get(this.fieldItem.FieldName);
                        if (this._previousVal != undefined && this._previousVal != currVal) {
                            if (!isBilling) {
                                viewModel.set("isAddressBeingEdited", true);
                            } else {
                                viewModel.set("isBillingAddressBeingEdited", true);
                            }
                            if (!isBilling || (isBilling && viewModel.shouldAddressBeCopied() && !(viewModel.get("deliveryAddressMode") == "Pickup" && options.addressMapMode == COPYBILLINGTOADDRESS))) {
                                $.cv.css.trigger($.cv.css.eventnames.addressBeingEdited);
                            }
                            viewModel.set("changeTriggered", false);
                            triggerChangeOnTimeout(e, dataItem);
                        } else {
                            if (!isBilling) {
                                viewModel.set("isAddressBeingEdited", false);
                            } else {
                                viewModel.set("isBillingAddressBeingEdited", false);
                        }
                        }
                    };
                    array.push(dataItem);
                });

                return array;
            };

            var getMultiAddressDataView = function () {
                // check if ds is initialised
                if (!widget.multipleDeliveryAddressDataSource)
                    return [];
                var array = [];
                $.each(widget.multipleDeliveryAddressDataSource.view(), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    item.value = item[widget.options.multipleAddressValueField];
                    item.label = item[widget.options.multipleAddressTextField];
                    array.push(item);
                });
                if (array.length > 0) {
                    var defaultMultipleAddress = {};
                    defaultMultipleAddress["label"] = widget.options.defaultMultipleAddressText;
                    defaultMultipleAddress["value"] = widget.options.defaultMultipleAddressValue;
                    array = $.merge([defaultMultipleAddress], array);
                }
                return array;
            };

            var getAddressValidationDataView = function () {
                // check if ds is initialised
                if (!widget.deliveryAddressValidationDataSource)
                    return [];
                var array = [];
                $.each(widget.deliveryAddressValidationDataSource.view(), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    item.value = item[widget.options.addressValidationValueField];
                    item.label = item[widget.options.addressValidationTextField];
                    array.push(item);
                });
                if (array.length == 1)
                    viewModel.set("deliveryAddressValidationValue", array[0].value);
                else if (array.length > 1) {
                    var defaultAddressValidation = {};
                    defaultAddressValidation["label"] = widget.options.defaultAddressValidationText;
                    defaultAddressValidation["value"] = widget.options.defaultAddressValidationValue;
                    array = $.merge([defaultAddressValidation], array);
                }
                return array;
            };

            buildPickListArray = function (obj) {
                var pickList = new Array();
                pickList = [];
                for (key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        var el = {};
                        el[widget.options.addressValidationValueField] = key;
                        el[widget.options.addressValidationTextField] = obj[key];
                        pickList.push(el);
                    }
                }
                return pickList;
            };

            var viewModel = kendo.observable({


                // Properties for UI elements
                multipleDeliveryAddressDataSource: widget.options.multipleDeliveryAddressDataSource,

                deliveryAddressValidationDataSource: widget.options.deliveryAddressValidationDataSource,

                multipleDeliveryAddressValue: '',

                deliveryAddressValidationValue: '',

                hasMultipleDeliveryAddresses: widget.options.hasMultipleDeliveryAddresses,

                message: '',

                clearExistingMessages: true,

                deliveryAddressObjectKey: '',

                orderComments: widget.options.orderComments,

                deliveryInstructions: widget.options.deliveryInstructions,

                orderReference: widget.options.orderReference,

                orderReferenceHasError: false,

                orderReferenceErrorMsg: "",

                orderConfirmationHasError: false,

                orderConfirmationErrorMsg: "",

                attentionToHasError: false,

                attentionToErrorMsg: "",

                copyOrderConfirmation: widget.options.copyOrderConfirmation,

                soDelCountry: widget.options.soDelCountry,

                soDelPhone: widget.options.soDelPhone,

                addressFieldsExist: false,

                billingAddressFieldsExist: false,

                saveAddress: false,

                linkDeliveryAndBilling: (widget.options.defaultLinkDeliveryAndBilling && !widget.options.useNegationLogicForLinkingAddress) || (!widget.options.defaultLinkDeliveryAndBilling && widget.options.useNegationLogicForLinkingAddress),

                isAddressListEmpty: function (list) {
                    var empty = true;
                    $.each(this.get(list), function (idx, item) {
                        if (!(this.get(item.fieldItem.fieldName) == null) && $.trim(this.get(item.fieldItem.fieldName)).length > 0) {
                            empty = false;
                            return false;
                        }
                    });
                    return empty;
                },

                isBillingAddressEmpty: function () {
                    return this.isAddressListEmpty("billingAddressItemList");
                },
                
                isDeliveryAddressEmpty: function () {
                    return this.isAddressListEmpty("addressItemList");
                },

                deliveryAndBillingMatch: function () {
                    var vm = this, match = true;
                    if (widget.options.addressMapMode == COPYBILLINGTOADDRESS || widget.options.addressMapMode == COPYADDRESSTOBILLING) {
                        var fromList = widget.options.addressMapMode == COPYBILLINGTOADDRESS ? this.get("billingAddressItemList") : this.get("addressItemList"),
                            toList = widget.options.addressMapMode == COPYBILLINGTOADDRESS ? this.get("addressItemList") : this.get("billingAddressItemList"),
                            mapList = widget.options.fieldMapFrom.split(",");
                        $.each(mapList, function (index, item) {
                            if (!isNaN(item) && fromList.length > item && toList.length > item) {
                                if (fromList[item][fromList[item].fieldItem.fieldName] != null) {
                                    if ($.trim(toList[item].get(toList[item].fieldItem.fieldName)) != $.trim(fromList[item][fromList[item].fieldItem.fieldName].toString())) {
                                        match = false;
                                    }
                                }
                            }
                        });
                    }
                    return match;
                },

                preserveAddressData: false,

                shouldAddressBeCopied: function () {
                    var copyAddress = false;
                    if (widget.options.useNegationLogicForLinkingAddress) {
                        copyAddress = !this.get("linkDeliveryAndBilling");
                    } else {
                        copyAddress = this.get("linkDeliveryAndBilling");
                    }
                    return copyAddress;
                },

                copyingAddressFields: false,

                addressItemList: [],

                billingAddressItemList: [],

                pickupAddress: {},

                showAttentionToUserDetails: widget.options.showAttentionToUserDetails,

                attentionUserName: widget.options.attentionUserName,

                attentionPhoneNumber: widget.options.attentionPhoneNumber,

                showPickupDeliveryControls: widget.options.showPickupDeliveryControls,
                showDeliveryAddress: function () { return this.get("deliveryAddressMode") == "Delivery"; },
                showBillingAddress: function () { return this.get("deliveryAddressMode") == "Delivery" || (this.get("deliveryAddressMode") == "Pickup" && widget.options.showBillingAddressOnPickup); },
                deliveryAddressMode: widget.options.deliveryAddressMode,

                /*------------------------------------*\
                    TIMEOUTS
                \*------------------------------------*/

                redirectToTimeoutUrl: function (fallbackUrl, params, includeInBrowserHistory) {
                    if ($.cv.ajax.settings.timeoutRedirectUrl == "")
                        $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
                    else
                        $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
                },

                /*------------------------------------*\
                    DELIVERY ADDRESS
                \*------------------------------------*/

                getSoDelAddress: function () {
                    var newAddressFieldInfo = {};
                    $.each(this.get("addressItemList"), function (idx, item) {
                        newAddressFieldInfo[item.fieldItem.fieldName] = this.get(item.fieldItem.fieldName) == null ? '' : this.get(item.fieldItem.fieldName);
                    });
                    var soDelAddress = {
                        SoDelCountry: this.get("soDelCountry"),
                        SoDelPhone: this.get("soDelPhone"),
                        updatedAddressDetails: newAddressFieldInfo
                    };
                    return (soDelAddress);
                },

                clearDeliveryAddress: function (noThrottle) {
                    var vm = this,
                        deliveryAddressMode = vm.get("deliveryAddressMode");
                    noThrottle = typeof noThrottle != "undefined" ? noThrottle : true;
                    $.each(this.get("addressItemList"), function (idx, item) {
                        this.set(item.fieldItem.fieldName, "");
                    });
                    vm.set("soDelCountry", "");
                    vm.set("soDelPhone", "");

                    return vm.setDeliveryAddress(deliveryAddressMode == "Delivery", noThrottle);
                },

                /*------------------------------------*\
                    BILLING ADDRESS
                \*------------------------------------*/

                getSoBillAddress: function () {
                    var newBillAddressFieldInfo = {};
                    $.each(this.get("billingAddressItemList"), function (idx, item) {
                        newBillAddressFieldInfo[item.fieldItem.fieldName] = this.get(item.fieldItem.fieldName) == null ? '' : this.get(item.fieldItem.fieldName);
                    });
                    var soBillAddress = {
                        updatedBillingAddressDetails: newBillAddressFieldInfo
                    };
                    return (soBillAddress);
                },

                clearBillingAddress: function () {
                    $.each(this.get("billingAddressItemList"), function (idx, item) {
                        this.set(item.fieldItem.fieldName, "");
                    });
                    this.setBillingAddress();
                },

                // UI Element state
                enterOrderComments: widget.options.enterOrderComments,

                disableDeliveryAddress: widget.options.disableDeliveryAddress,

                disableBillingAddress: widget.options.disableBillingAddress,

                showDeliveryInstructions: widget.options.showDeliveryInstructions,

                forceOrderReference: widget.options.forceOrderReference,

                allowCopyOrderConfirmation: widget.options.allowCopyOrderConfirmation,

                copyOrderConfirmationMandatory: widget.options.copyOrderConfirmationMandatory,

                isInitialLoad: true,

                isSuburbAndPostCodeValid: widget.options.disableDeliveryAddress ? true : !widget.options.enableSuburbAndPostcodeValidation,
                isBillingSuburbAndPostCodeValid: widget.options.disableBillingAddress ? true : !widget.options.enableSuburbAndPostcodeValidation,

                deliveryAddressErrorMsg: "",

                orderCommentsErrorMsg: "",

                deliveryInstructionsErrorMsg: "",

                // functions for UI events

                localdeliveryAddressChanged: function () {
                    var address = $.cv.css.localGetcurrentDeliveryAddress();
                    if (address != null) {
                        localProcessAddressData(address);
                    }
                },

                updateMultiAddressItemList: function () {
                    this.set("multiAddressItemList", getMultiAddressDataView());
                    widget.trigger(MULTIADDRESSRENDERED);
                },

                multiAddressItemList: getMultiAddressDataView(),

                updateAddressValidationItemList: function () {
                    this.set("deliveryAddressValidationValue", "");
                    this.set("addressValidationItemList", getAddressValidationDataView());
                    widget.trigger(ADDRESSVALIDATIONRENDERED);
                },

                addressValidationItemList: getAddressValidationDataView(),

                hasAddressValidationPickList: function () {
                    return this.get("addressValidationItemList").length > 0;
                },

                hasAddressValidationVerifiedOptions: function () {
                    var count = 0, addressValidationItemList = this.get("addressValidationItemList");
                    $.each(addressValidationItemList, function (idx, item) {
                        if (item.value != "UnverifiedUseMyEnteredAddressKey")
                            count++;
                    });
                    return count > 0;
                },

                multiAddressItemSelected: function () {
                    var _this = this;
                    if (this.get("multipleDeliveryAddressValue") == '') {
                        //viewModel.setMessage(widget.options.textEnterNumeric, $.cv.css.messageTypes.error);
                    } else {
                        _this.clearMessage();
                        _this.set("deliveryAddressErrorMsg", "");
                        var d1 = $.cv.css.deliveryAddress.setDeliveryAddressByName({ deliveryAddressName: this.get("multipleDeliveryAddressValue") });
                        $.when(d1).done(function (msg) {
                            var data = msg.data;
                            if (!msg.sessionHasTimedOut) {
                                if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                    processAddressData(data);
                                    if (!data.success) {
                                        if (data.message != "") {
                                            _this.setMessage(data.message, $.cv.css.messageTypes.error);
                                            _this.set("deliveryAddressErrorMsg", data.message);
                                        }
                                    }
                                } else {
                                    _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                                    _this.set("deliveryAddressErrorMsg", data.message);
                                }
                            } else {
                                viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                            }
                        }).fail(function () {
                            _this.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.error);
                            _this.set("deliveryAddressErrorMsg", widget.options.textErrorUpdatingDeliveryAddress);
                        });
                    }
                },

                addressValidationItemSelected: function () {
                    var _this = this;
                    var moniker = _this.get("deliveryAddressValidationValue");
                    var d1 = $.cv.css.deliveryAddress.updateDeliveryAddressForCurrentOrder({ selectAddressMoniker: moniker });
                    $.when(d1).done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (data.result) {
                                    _this.setMessage(widget.options.textDeliveryAddressUpdatedSuccess, $.cv.css.messageTypes.success);
                                    loadSoDelAddress();
                                    if (_this.get("saveAddress")) {
                                        _this.saveDeliveryAddress();
                                    }
                                    widget.trigger(ADDRESSVALIDATIONOPTIONSELECTED);
                                } else {
                                    if (data.message != "")
                                        _this.setMessage(data.message, $.cv.css.messageTypes.error);
                                }
                            } else {
                                _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error)
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        _this.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.error);
                    });
                },

                clearMessage: function () {
                    var clearExistingMessages = this.get("clearExistingMessages");
                    this.set("clearExistingMessages", true);
                    this.set("message", "");
                    if (widget.options.triggerMessages || widget.options.triggerErrorMessages || widget.options.triggerExternalValidationMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'deliveryAddress', clearExisting: this.get("clearExistingMessages") });
                    this.set("clearExistingMessages", clearExistingMessages);
                },

                setMessage: function (message, type, triggerMessageOverride) {
                    var triggerMessages = typeof triggerMessageOverride !== 'undefined' ? triggerMessageOverride : widget.options.triggerMessages;
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: triggerMessages,
                        source: widget.name
                    });
                },

                validCustomerReference: function (showMessage) {
                    var valid = ((this.get("forceOrderReference") && this.get("orderReference").length) > 0 || !this.get("forceOrderReference"));
                    if (showMessage && !valid)
                        this.setMessage(widget.options.textOrderReferenceMandatory, $.cv.css.messageTypes.error);
                    if (!valid) {
                        this.set("orderReferenceHasError", widget.options.inputErrorClass);
                        this.set("orderReferenceErrorMsg", widget.options.textOrderReferenceMandatory);
                    } else {
                        this.set("orderReferenceHasError", "");
                        this.set("orderReferenceErrorMsg", "");
                    }
                    return valid;
                },

                setCustomerReference: function () {
                    var _this = this;
                    if (!_this.validCustomerReference(true))
                        return;
                    else {
                        if (widget.options.showCustomerReferenceUpdatedMessage)
                            _this.updateDeliveryAddressField({ SoCustReference: this.get("orderReference") }, widget.options.textCustomerReferenceUpdatedSuccess);
                        else
                            _this.updateDeliveryAddressField({ SoCustReference: this.get("orderReference") }, '');
                    }
                },

                clearingPickupContact: false,

                setPickupContact: function () {
                    var vm = this;
                    vm.updateDeliveryAddressField({
                        ContactFirstName: this.get("contactFirstName"),
                        ContactLastName: this.get("contactLastName"),
                        ContactPhoneNumber: this.get("contactPhoneNumber")
                    }, '');
                },

                clearPickupContact: function (triggerUpdate) {
                    var vm = this,
                        cleared = $.Deferred();
                    if (vm.get("contactFirstName") != undefined &&
                        vm.get("contactLastName") != undefined &&
                        vm.get("contactPhoneNumber") != undefined &&
                        (vm.get("contactFirstName").length > 0 || vm.get("contactLastName").length > 0 || vm.get("contactPhoneNumber").length > 0)) {
                        vm.set("clearingPickupContact", true);
                        vm.set("contactFirstName", "");
                        vm.set("contactLastName", "");
                        vm.set("contactPhoneNumber", "");
                        if (triggerUpdate) {
                            cleared = vm.updateDeliveryAddressField({
                                ContactFirstName: "",
                                ContactLastName: "",
                                ContactPhoneNumber: ""
                            }, '');
                        } else {
                            cleared.resolve();
                        }
                        vm.set("clearingPickupContact", false);
                    } else {
                        cleared.resolve();
                    }
                    return cleared;
                },

                contactFirstNameErrorMsg: "",
                contactLastNameErrorMsg: "",
                contactPhoneErrorMsg: "",

                validPickupContact: function (showMessages) {
                    var vm = this,
                        valid = true;
                    if (!widget.options.pickupContactFieldData.length == 0) {
                        $.each(widget.options.pickupContactFieldData, function (idx, item) {
                            if (item.Mandatory && !vm.validPickupContactField(item.FieldName, item.Prompt)) {
                                valid = false;
                            }
                        });
                    }
                    if (!valid && showMessages) {
                        vm.clearMessage();
                        vm.setMessage(widget.options.textPickupContactDetailsIncomplete, $.cv.css.messageTypes.error);
                    }
                    return valid;
                },

                validPickupContactField: function (field, fieldPrompt) {
                    var valid = true,
                        field = field.substr(0, 1).toLowerCase() + field.substr(1);
                    if ($.trim(this.get(field).length) == 0) {
                        this.set(field + "ErrorMsg", $.cv.css.mandatoryFieldIncompleteMessage.format(fieldPrompt));
                        valid = false;
                    } else {
                        this.set(field + "ErrorMsg", "");
                    }
                    return valid;
                },

                checkAttentionToDefaults: function () {
                    if (this.get("attentionUserName") == "")
                        this.set("attentionUserName", widget.options.attentionUserName);
                    if (this.get("attentionPhoneNumber") == "")
                        this.set("attentionPhoneNumber", widget.options.attentionPhoneNumber);
                },

                setAttention: function (triggerMessage) {
                    var _this = this;
                    triggerMessage = typeof triggerMessage !== 'undefined' ? triggerMessage : true;
                    this.set("attentionToHasError", "");
                    this.set("attentionToErrorMsg", "");
                    _this.checkAttentionToDefaults();
                    var d1 = $.cv.css.deliveryAddress.setDeliveryAddressAttentionTo({ name: _this.get("attentionUserName"), phone: _this.get("attentionPhoneNumber") });
                    $.when(d1).done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (triggerMessage) {
                                if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                    _this.setMessage(widget.options.textUpdatingAttentionToSuccess, $.cv.css.messageTypes.success);
                                } else {
                                    _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                                    _this.set("attentionToHasError", widget.options.inputErrorClass);
                                    _this.set("attentionToErrorMsg", msg.errorMessage);
                                }
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        if (triggerMessage) {
                            _this.setMessage(widget.options.textErrorUpdatingAttentionTo, $.cv.css.messageTypes.error);
                            _this.set("attentionToHasError", widget.options.inputErrorClass);
                            _this.set("attentionToErrorMsg", widget.options.textErrorUpdatingAttentionTo);
                        }
                    });
                },

                setAttentionToUserName: function (triggerMessage) {
                    var _this = this;
                    _this.setAttention(triggerMessage);
                },

                setAttentionToPhoneNumber: function (triggerMessage) {
                    var _this = this;
                    _this.setAttention(triggerMessage);
                },

                setDeliveryAddress: function (clearPickupContact, noThrottle) {
                    var soDelAddress = this.getSoDelAddress(), set = $.Deferred();
                    var _this = this;
                    noThrottle = typeof noThrottle != "undefined" ? noThrottle : false;
                    if (!noThrottle) {
                    set = _this.updateDeliveryAddress(soDelAddress, widget.options.textDeliveryAddressUpdatedSuccess, "deliveryAddressErrorMsg", _this.get("saveAddress"), clearPickupContact);
                    } else {
                        set = _this._updateDeliveryAddress(soDelAddress, widget.options.textDeliveryAddressUpdatedSuccess, "deliveryAddressErrorMsg", _this.get("saveAddress"), clearPickupContact);
                    }
                    return set;
                },

                setBillingAddress: function () {
                    var soBillAddress = this.getSoBillAddress();
                    var _this = this;
                    _this.updateBillingAddress(soBillAddress, widget.options.textBillingAddressUpdatedSuccess, "billingAddressErrorMsg");
                },

                setOrderComments: function () {
                    var _this = this;
                    _this.updateDeliveryAddressField({ SoComments: this.get("orderComments") }, widget.options.textOrderCommentsUpdatedSuccess, "orderCommentsErrorMsg");
                },

                setDeliveryInstructions: function () {
                    var _this = this;
                    _this.updateDeliveryAddressField({ SoDelIns: this.get("deliveryInstructions") }, widget.options.textDeliveryInstructionsUpdatedSuccess, "deliveryInstructionsErrorMsg");
                },

                validCopyOrderConfirmation: function (showMessages) {
                    var valid = !(this.get("allowCopyOrderConfirmation") && this.get("copyOrderConfirmationMandatory") && this.get("copyOrderConfirmation").length == 0);
                    if (!valid && showMessages)
                        this.setMessage(widget.options.textCopyOrderConfirmationMandatory, $.cv.css.messageTypes.error);
                    if (!valid) {
                        this.set("orderConfirmationHasError", widget.options.inputErrorClass);
                        this.set("orderConfirmationErrorMsg", widget.options.textCopyOrderConfirmationMandatory);
                    } else {
                        this.set("orderConfirmationHasError", "");
                        this.set("orderConfirmationErrorMsg", "");
                    }
                    return valid;
                },

                setCopyOrderConfirmation: function () {
                    var _this = this;
                    if (!this.validCopyOrderConfirmation(true))
                        return;
                    else {
                        if (_this.validateCopyConfirmationEmailAddresses())
                            _this.updateDeliveryAddressField({ CopyOrderConfirmationEmail: this.get("copyOrderConfirmation") }, widget.options.textCopyOrderConfirmationUpdatedSuccess, "orderConfirmationErrorMsg");
                        else
                            _this.setMessage(widget.options.textCopyOrderConfirmationInvalidEmails, $.cv.css.messageTypes.error);
                    }
                },

                validateCopyConfirmationEmailAddresses: function () {
                    var allEmailsValid = true;
                    if ($.trim(this.get("copyOrderConfirmation")) != "") {
                        var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
                        var emails = this.get("copyOrderConfirmation").split(";");
                        $.each(emails, function (idx, item) {
                            if (!pattern.test(item))
                                allEmailsValid = false;
                        });
                    }
                    if (!allEmailsValid) {
                        this.set("orderConfirmationHasError", widget.options.inputErrorClass);
                        this.set("orderConfirmationErrorMsg", widget.options.textCopyOrderConfirmationInvalidEmails);
                    } else {
                        this.set("orderConfirmationHasError", "");
                        this.set("orderConfirmationErrorMsg", "");
                    }
                    return allEmailsValid;
                },

                saveBillingAddressToUser: function () {
                    var _this = this, d = $.Deferred(), currentUser = $.cv.css.localGetUser(),
                        userUpdateData = {},
                        billingAddress = this.get("billingAddressItemList");

                    if (widget.options.saveAddressFieldGroup.length > 0) {
                        if (currentUser == null) {
                            d = $.cv.css.getCurrentUser();
                        } else {
                            d.resolve({ data: [currentUser] });
                        }
                        $.each(billingAddress, function (index, item) {
                            userUpdateData[item.fieldItem.FieldName] = item.get(item.fieldItem.FieldName);
                        });
                        $.when(d).done(function (usr) {
                            if (usr && usr.data && usr.data.length > 0) {
                                userUpdateData["_objectKey"] = usr.data[0]._objectKey;
                                $.cv.css.user.setCurrentUserDetails({ updateData: userUpdateData, jsonFieldGroup: widget.options.saveAddressFieldGroup });
                            }
                        });
                    }
                },

                saveDeliveryAddressToUser: function () {
                    var _this = this, d = $.Deferred(), currentUser = $.cv.css.localGetUser(),
                        saveFields = widget.options.userAddressFields.split(","),
                        userUpdateData = {},
                        address = this.get("addressItemList");

                    if (saveFields.length > 0 && widget.options.saveAddressFieldGroup.length > 0) {
                        if (currentUser == null) {
                            d = $.cv.css.getCurrentUser();
                        } else {
                            d.resolve({ data: [currentUser] });
                        }
                        $.each(saveFields, function (index, item) {
                            if (address.length > index) {
                                userUpdateData[item] = address[index].get(address[index].fieldItem.fieldName);
                            }
                        });
                        $.when(d).done(function (usr) {
                            if (usr && usr.data && usr.data.length > 0) {
                                userUpdateData["_objectKey"] = usr.data[0]._objectKey;
                                $.cv.css.user.setCurrentUserDetails({ updateData: userUpdateData, jsonFieldGroup: widget.options.saveAddressFieldGroup });
                            }
                        });
                    }
                },

                saveDeliveryAddress: function () {
                    var soDelAddress = this.getSoDelAddress(), _this = this;
                    var d1 = $.cv.css.deliveryAddress.saveAddress({ saveForUser: widget.options.saveAddressForUser, saveDeliveryInstructions: widget.options.saveDeliveryInstructions });
                    $.when(d1).done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (data.Success) {
                                    $.cv.css.trigger($.cv.css.eventnames.addressSaved);
                                } else {
                                    if (data.Messages.length > 0) {
                                        var message = "";
                                        for (var i = 0; i < data.Messages.length; i++)
                                            message = message.length == 0 ? data.Messages[i] : message + ", " + data.Messages[i];
                                        _this.setMessage(message, $.cv.css.messageTypes.error);
                                    }
                                }
                            } else {
                                _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        _this.setMessage(widget.options.textErrorSavingDeliveryAddress, $.cv.css.messageTypes.error);
                    });
                },

                changeTriggered: false,

                isAddressBeingEdited: false,

                _updateDeliveryAddressThrottled: $.noop,

                updateDeliveryAddress: function(options, successMessage, addressErrorMsgProperty, saveAddress, clearPickupContact) {
                    return widget._updateDeliveryAddressThrottled(options, successMessage, addressErrorMsgProperty, saveAddress, clearPickupContact);
                },

                _updateDeliveryAddress: function (options, successMessage, addressErrorMsgProperty, saveAddress, clearPickupContact) {
                    var vm = this,
                        processingLinkedAddresses = vm.get("processingLinkedAddresses"),
                        pickupContactCleared = {};
                    var opts = $.extend({
                        _objectKey: this.get("deliveryAddressObjectKey"),
                        validateMissingFields: !widget.options.updateAddressOnChanged
                    }, options);

                    saveAddress = typeof saveAddress !== 'undefined' ? saveAddress : false;
                    clearPickupContact = typeof clearPickupContact !== 'undefined' ? clearPickupContact : false;

                    vm.set("deliveryAddressErrorMsg", "");
                    vm.clearMessage();

                    var addressUpdated = $.cv.css.deliveryAddress.updateDeliveryAddressForCurrentOrder(opts);
                    addressUpdated.done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (data.result) {
                                    if (!widget.options.updateAddressOnChanged) {
                                        vm.setMessage(widget.options.textDeliveryAddressUpdatedSuccess, $.cv.css.messageTypes.success);
                                        loadSoDelAddress();
                                    }
                                    $.cv.css.trigger($.cv.css.eventnames.addressChanged);
                                    if (saveAddress) {
                                        vm.saveDeliveryAddress();
                                    }
                                    if (widget.options.saveAddressDownToUser) {
                                        vm.saveDeliveryAddressToUser();
                                    }
                                } else {
                                    if (data.message != "" && data.pickList == null) {
                                        if (!widget.options.updateAddressOnChanged) {
                                            loadSoDelAddress();
                                        }
                                        vm.setMessage(data.message, $.cv.css.messageTypes.error, (widget.options.triggerErrorMessages && !processingLinkedAddresses))
                                        if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                            vm.set(addressErrorMsgProperty, data.message);
                                        }
                                    }
                                    else if (data.pickList != null) {
                                        loadSoDelAddress();
                                        var pickList = buildPickListArray(data.pickList);
                                        if (pickList.length > 0) {
                                            widget.options.deliveryAddressValidationDataSource = pickList;
                                            setDeliveryAddressValidationDataSource();
                                        }
                                    }
                                }
                                if (clearPickupContact) {
                                    pickupContactCleared = vm.clearPickupContact(true);
                                }
                            } else {
                                vm.setMessage(msg.errorMessage, $.cv.css.messageTypes.error)
                                if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                    vm.set(addressErrorMsgProperty, msg.errorMessage);
                                }
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        vm.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.error);
                        if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                            vm.set(addressErrorMsgProperty, widget.options.textErrorUpdatingDeliveryAddress);
                        }
                    });

                    // Validate Suburb and Postcode...
                    var suburbValidated = {};

                    if (widget.options.enableSuburbAndPostcodeValidation === true) {
                        suburbValidated = vm.validateSuburbAndPostCodeInternal(
                            opts.updatedAddressDetails[widget.options.suburbFieldName],
                            opts.updatedAddressDetails[widget.options.postcodeFieldName],
                            opts.SoDelCountry, false /* not-billing */).fail(function () {
                                // NOTE: success and fail are handled internally, we just need to do
                                // a bita extra stuff
                                if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                    vm.set(addressErrorMsgProperty, widget.options.textErrorValidatingSuburbPostcode);
                                }
                            }).done(function () {
                                vm.validateInputFields(false);
                            });
                    } else {
                        // Considered valid by default.
                        vm.set("isSuburbAndPostCodeValid", true);
                        vm.updateSuburbPostcodeValidStatus();
                    }

                    // Return Promise which is resolved when both services are done (or failed)
                    // suburbValidated and pickupContactCleared will automatically be considered resolved if they are 
                    // plain old objects. 
                    return $.when(addressUpdated, suburbValidated, pickupContactCleared);
                },

                isBillingAddressBeingEdited: false,

                _updateBillingAddressThrottled: $.noop,

                updateBillingAddress: function (options, successMessage, addressErrorMsgProperty) {
                    return widget._updateBillingAddressThrottled(options, successMessage, addressErrorMsgProperty);
                },

                _updateBillingAddress: function (options, successMessage, addressErrorMsgProperty) {
                    var vm = this;
                    var opts = $.extend({
                        _objectKey: this.get("deliveryAddressObjectKey")
                    }, options);

                    vm.set("billingAddressErrorMsg", "");
                    vm.clearMessage();

                    var addressUpdated = $.cv.css.deliveryAddress.updateBillingAddressForCurrentOrder(opts);

                    addressUpdated.done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (data.result) {
                                    if (!widget.options.updateAddressOnChanged) {
                                        vm.setMessage(widget.options.textDeliveryAddressUpdatedSuccess, $.cv.css.messageTypes.success);
                                        loadSoDelAddress();
                                    }
                                    $.cv.css.trigger($.cv.css.eventnames.billingAddressChanged);
                                    if (widget.options.saveBillAddressDownToUser) {
                                        vm.saveBillingAddressToUser();
                                    }
                                } else {
                                    if (data.message != "") {
                                        if (!widget.options.updateAddressOnChanged) {
                                            loadSoDelAddress();
                                        }
                                        vm.setMessage(data.message, $.cv.css.messageTypes.error)
                                        if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                            vm.set(addressErrorMsgProperty, data.message);
                                        }
                                    }
                                }
                            } else {
                                vm.setMessage(msg.errorMessage, $.cv.css.messageTypes.error)
                                if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                    vm.set(addressErrorMsgProperty, msg.errorMessage);
                                }
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        vm.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.error);
                        if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                            vm.set(addressErrorMsgProperty, widget.options.textErrorUpdatingDeliveryAddress);
                        }
                    });

                    // Validate Suburb and Postcode...
                    var suburbValidated = {};

                    if (widget.options.enableSuburbAndPostcodeValidation === true) {
                        suburbValidated = vm.validateSuburbAndPostCodeInternal(
                            opts.updatedBillingAddressDetails[widget.options.billingSuburbFieldName],
                            opts.updatedBillingAddressDetails[widget.options.billingPostcodeFieldName],
                            opts.SoDelCountry, true /* billing */).fail(function () {
                                // NOTE: success and fail are handled internally, we just need to do
                                // a bita extra stuff
                                if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                    vm.set(addressErrorMsgProperty, widget.options.textErrorValidatingSuburbPostcode);
                                }
                            }).done(function() {
                                vm.validateInputFields(false);
                            });
                    } else {
                        // Considered valid by default.
                        vm.set("isBillingSuburbAndPostCodeValid", true);
                        vm.updateBillingSuburbPostcodeValidStatus();
                    }

                    // Return Promise which is resolved when both services are done (or failed)
                    // suburbValidated will automatically be considered resolved if it is 
                    // a plain old object. 
                    return $.when(addressUpdated, suburbValidated);
                },

                validateSuburbAndPostCodeInternal: function(suburb, postcode, country, isBilling) {
                    var vm = this;
                    var suburbValidated = $.Deferred();
                    // if suburb or postcode is not valid no need to call the validation method just return false
                    if (suburb.length == 0 || postcode.length == 0) {
                        if (isBilling == true) {
                            vm.set("isBillingSuburbAndPostCodeValid", false);
                            vm.updateBillingSuburbPostcodeValidStatus();
                        } else {
                            vm.set("isSuburbAndPostCodeValid", false);
                            vm.updateSuburbPostcodeValidStatus();
                        }
                        suburbValidated.resolve({ data: false });
                        return suburbValidated;
                    }
                    var suburbValidated = $.cv.css.deliveryAddress.validateSuburbAndPostCode({
                        suburb: suburb,
                        postcode: postcode,
                        country: country
                    });

                    suburbValidated.done(function (response) {
                        if (!response.sessionHasTimedOut) {
                            var isValid = response.data;
                            if (isBilling == true) {
                                vm.set("isBillingSuburbAndPostCodeValid", isValid);
                                vm.updateBillingSuburbPostcodeValidStatus();
                            } else {
                                vm.set("isSuburbAndPostCodeValid", isValid);
                                vm.updateSuburbPostcodeValidStatus();
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        // Don't allow validation to succeed!
                        if (isBilling == true) {
                            vm.set("isBillingSuburbAndPostCodeValid", false);
                            vm.updateBillingSuburbPostcodeValidStatus();
                        } else {
                            vm.set("isSuburbAndPostCodeValid", false);
                            vm.updateSuburbPostcodeValidStatus();
                        }

                        vm.setMessage(widget.options.textErrorValidatingSuburbPostcode, $.cv.css.messageTypes.error);
                    });

                    return suburbValidated;
                },

                updateDeliveryAddressField: function (options, successMessage, addressErrorMsgProperty) {
                    var _this = this;
                    var opts = $.extend({
                        _objectKey: this.get("deliveryAddressObjectKey")
                    }, options);
                    var d1 = $.cv.css.deliveryAddress.setDeliveryAddressFieldForCurrentOrder(opts);
                    $.when(d1).done(function (msg) {
                        var data = {};
                        data["result"] = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (data.result.length > 0) {
                                    _this.setMessage(successMessage, $.cv.css.messageTypes.success);
                                    if (!widget.options.updateAddressOnChanged) {
                                        processAddressData(data);
                                    }
                                } else {
                                    _this.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.success);
                                }
                            } else {
                                _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                                if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                                    _this.set(addressErrorMsgProperty, msg.errorMessage);
                                }
                            }
                        } else {
                            viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function () {
                        _this.setMessage(widget.options.textErrorUpdatingDeliveryAddress, $.cv.css.messageTypes.error);
                        if (addressErrorMsgProperty != undefined && addressErrorMsgProperty != "") {
                            _this.set(addressErrorMsgProperty, widget.options.textErrorUpdatingDeliveryAddress);
                        }
                    });
                    return d1;
                },

                validateAddressFields: function (list, showMessages) {
                    var allValid = true;
                    $.each(list, function (idx, item) {
                        if (!item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, showMessages)) {
                            allValid = false;
                        }
                    });
                    return allValid;
                },

                _validateAddressServiceCall: function () {
                    var vm = this,
                        validateDeferred = $.Deferred();
                    vm.clearMessage();
                    $.cv.css.deliveryAddress.validateCurrentOrderDeliveryAddress().done(function (msg) {
                        var data = msg.data;
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (!data.result && data.responseMessage.length > 0) {
                                    vm.setMessage(data.responseMessage, $.cv.css.messageTypes.error, widget.options.triggerExternalValidationMessages);
                                }
                            } else {
                                vm.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                            }
                            validateDeferred.resolve(data.result);
                        } else {
                            vm.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    });
                    return validateDeferred;
                },

                validateInputFields: function (showMessages, callValidateServiceOnCheckout) {
                    var vm = this, passedValidation = true, widgetReference = "deliveryAddress", missingAddressFields = false,
                        serviceCallDeferred = $.Deferred(),
                        validateDeferred = $.Deferred();
                    callValidateServiceOnCheckout = typeof callValidateServiceOnCheckout !== 'undefined' ? (this.get("deliveryAddressMode") == "Pickup" ? false : callValidateServiceOnCheckout) : false;
                    if (!widget.options.isViewOnly) {
                        // Check mandatory fields
                        if (this.get("deliveryAddressMode") == "Pickup" && !this.validPickupContact(showMessages)) {
                            passedValidation = false;
                        }

                        if (!this.validCustomerReference(showMessages)) {
                            passedValidation = false;
                        }

                        if (!this.validCopyOrderConfirmation(showMessages)) {
                            passedValidation = false;
                        }

                        if (!widget.options.disableDeliveryAddress && this.showDeliveryAddress()) {
                            if (!this.validateAddressFields(this.get("addressItemList"), showMessages)) {
                                passedValidation = false;
                                missingAddressFields = true;
                            }
                        }

                        if (!widget.options.disableBillingAddress && this.showBillingAddress()) {
                            if (!this.validateAddressFields(this.get("billingAddressItemList"), showMessages)) {
                                passedValidation = false;
                                missingAddressFields = true;
                            }
                        }

                        if (!passedValidation && missingAddressFields) {
                            if (showMessages) {
                                this.set("deliveryAddressErrorMsg", widget.options.textErrorMissingDeliveryAddressFields);
                                this.setMessage(widget.options.textErrorMissingDeliveryAddressFields, $.cv.css.messageTypes.error);
                            }
                        }

                        // Don't check suburb / postcode validation (ensures they pair up) at this point if that functionality not enabled. Otherwise it can 
                        // remove mandatory field / field length validation which is needed on these fields and will have already been performed.
                        if (!widget.options.disableDeliveryAddress && widget.options.enableSuburbAndPostcodeValidation && this.showDeliveryAddress()) {
                            if (!this.get("isSuburbAndPostCodeValid")) {
                                passedValidation = false;
                            }
                            if (showMessages === true) {
                                this.updateSuburbPostcodeValidStatus()
                            }
                        }

                        // Same for Billing suburb / postcode validation
                        if (!widget.options.disableBillingAddress && widget.options.enableSuburbAndPostcodeValidation && this.showBillingAddress()) {
                            if (!this.get("isBillingSuburbAndPostCodeValid")) {
                                passedValidation = false;
                            }
                            if (showMessages === true) {
                                this.updateBillingSuburbPostcodeValidStatus()
                            }
                        }
                        if (passedValidation && callValidateServiceOnCheckout) {
                            serviceCallDeferred = this._validateAddressServiceCall();
                        } else {
                            serviceCallDeferred.resolve(passedValidation);
                        }
                        serviceCallDeferred.done(function (passed) {
                            $.cv.css.addRemovePageValidationError(passed, widgetReference);
                            if (!vm.get("copyingAddressFields")) {
                                $.cv.css.trigger($.cv.css.eventnames.addressValidated);
                            }
                            validateDeferred.resolve();
                        });
                        return validateDeferred;
                    }
                },

                // Update error status of suburb and postcode based on vm status
                // @returns bool - true if fields are valid, false if not
                updateSuburbPostcodeValidStatus: function () {
                    var vm = this,
                        isValid = vm.get("isSuburbAndPostCodeValid"),
                        addressCopied = this.shouldAddressBeCopied() && widget.options.addressMapMode == COPYBILLINGTOADDRESS,
                        isPickup = vm.get("deliveryAddressMode") == "Pickup";

                    if (!addressCopied && !isPickup && !widget.options.isViewOnly) {
                        _.each(vm.get("addressItemList"), function (item) {
                            if (item.fieldItem.FieldName === widget.options.suburbFieldName ||
                                item.fieldItem.FieldName === widget.options.postcodeFieldName) {
                                // Clear or set invalid message
                                if (isValid) {
                                    item.fieldItem.set("hasNonFieldValidMessage", false); // allow fieldItem.fieldValid method to add its own message if required
                                    item.setError(item.fieldItem, "", "");
                                } else {
                                    item.fieldItem.set("hasNonFieldValidMessage", true); // stop fieldItem.fieldValid method clearing this message
                                    item.setError(item.fieldItem, widget.options.suburbAndPostcodeInvalidMessage, item.fieldItem.classForErrors);
                                }
                            }
                        });
                    } else {
                        isValid = true;
                    }

                    return isValid;
                },

                // Update error status of suburb and postcode based on vm status for Billing address
                // @returns bool - true if fields are valid, false if not
                updateBillingSuburbPostcodeValidStatus: function () {
                    var vm = this,
                        isValid = vm.get("isBillingSuburbAndPostCodeValid"),
                        addressCopied = this.shouldAddressBeCopied() && widget.options.addressMapMode == COPYADDRESSTOBILLING,
                        validate = (vm.get("deliveryAddressMode") == "Pickup" && widget.options.showBillingAddressOnPickup) || vm.get("deliveryAddressMode") == "Delivery";

                    if (!addressCopied && validate && !widget.options.isViewOnly) {
                        _.each(vm.get("billingAddressItemList"), function (item) {
                            if (item.fieldItem.FieldName === widget.options.billingSuburbFieldName ||
                                item.fieldItem.FieldName === widget.options.billingPostcodeFieldName) {
                                // Clear or set invalid message
                                if (isValid) {
                                    item.fieldItem.set("hasNonFieldValidMessage", false); // allow fieldItem.fieldValid method to add its own message if required
                                    item.setError(item.fieldItem, "", "");
                                } else {
                                    item.fieldItem.set("hasNonFieldValidMessage", true); // stop fieldItem.fieldValid method clearing this message
                                    item.setError(item.fieldItem, widget.options.suburbAndPostcodeInvalidMessage, item.fieldItem.classForErrors);
                                }
                            }
                        });
                    } else {
                        isValid = true;
                    }

                    return isValid;
                },

                deliveryAddressModeChanged: function (e) {
                    var vm = this,
                        deliveryAddressMode = vm.get("deliveryAddressMode"),
                        cleared = {};
                    // If mode changed back to delivery and we need to copy billing to address
                    // Otherwise clear delivery address.
                    if (deliveryAddressMode == "Delivery" && widget.options.addressMapMode == COPYBILLINGTOADDRESS) {
                        vm.processLinkedAddresses(e);
                    } else {
                        cleared = this.clearDeliveryAddress();
                    }
                    $.when(cleared).done(function () {
                    if (deliveryAddressMode == "Delivery" && widget.options.addressMapMode == "") {
                        init();
                    }
                        vm.validateInputFields(false);
                    })

                    // Trigger event "delivery address mode changed".
                    $.cv.css.trigger($.cv.css.eventnames.deliveryAddressModeChanged,
                        { message: this.get("deliveryAddressMode"), type: "", source: "deliveryAddress", clearExisting: true });
                },

                processingLinkedAddresses: false,

                processLinkedAddresses: function (e) {
                    var vm = this, deliveryAddressMode = vm.get("deliveryAddressMode");
                    if (!(deliveryAddressMode == "Pickup" && widget.options.addressMapMode == COPYBILLINGTOADDRESS)) {
                        if (this.shouldAddressBeCopied() && !this.get("copyingAddressFields")) {
                            this.copyAddressFields();
                        }
                        if (e.field == "linkDeliveryAndBilling" || (e.field == "deliveryAddressMode" && deliveryAddressMode == "Delivery" && widget.options.addressMapMode == COPYBILLINGTOADDRESS)) {
                            vm.set("processingLinkedAddresses", true);
                            if (this.shouldAddressBeCopied()) {
                                this.triggerCopyUpdate();
                            }
                            if (!this.shouldAddressBeCopied() && widget.options.clearOnUncheckedLink && !this.get("preserveAddressData")) {
                                this.clearCopiedAddressFields();
                            }
                            vm.set("processingLinkedAddresses", false);
                        }
                        viewModel.disableEnableCopiedAddressFields();
                    }
                },

                triggerCopyUpdate: function () {
                    if (widget.options.addressMapMode == COPYBILLINGTOADDRESS) {
                        var soDelAddress = viewModel.getSoDelAddress();
                        this.updateDeliveryAddress(soDelAddress, "");
                    } else {
                        var soBillAddress = viewModel.getSoBillAddress();
                        this.updateBillingAddress(soBillAddress, "");
                    }
                },

                copyAddressFields: function (runDisableEnable) {
                    runDisableEnable = typeof runDisableEnable !== 'undefined' ? runDisableEnable : true;
                    if (widget.options.addressMapMode == COPYBILLINGTOADDRESS || widget.options.addressMapMode == COPYADDRESSTOBILLING) {
                        var _this = this,
                            fromList = widget.options.addressMapMode == COPYBILLINGTOADDRESS ? this.get("billingAddressItemList") : this.get("addressItemList"),
                            toList = widget.options.addressMapMode == COPYBILLINGTOADDRESS ? this.get("addressItemList") : this.get("billingAddressItemList"),
                            mapList = widget.options.fieldMapFrom.split(",");
                        _this.set("copyingAddressFields", true);
                        $.each(mapList, function (index, item) {
                            if (!isNaN(item) && fromList.length > item && toList.length > item) {
                                if (fromList[item][fromList[item].fieldItem.fieldName] != null) {
                                    toList[item].set(toList[item].fieldItem.fieldName, fromList[item][fromList[item].fieldItem.fieldName].toString());
                                } else {
                                    toList[item].set(toList[item].fieldItem.fieldName, "");
                                }
                            }
                        });
                        _this.validateInputFields();
                        if (runDisableEnable) {
                            _this.disableEnableCopiedAddressFields();
                        }
                        _this.set("copyingAddressFields", false);
                    }
                },

                disableEnableCopiedAddressFields: function () {
                    var _this = this,
                        toList = widget.options.addressMapMode == COPYBILLINGTOADDRESS ? this.get("addressItemList") : this.get("billingAddressItemList");
                    $.each(toList, function (index, item) {
                        if (_this.shouldAddressBeCopied()) {
                            item.set(item.fieldItem.fieldName + "_isEnabled", false);
                        } else {
                            item.set(item.fieldItem.fieldName + "_isEnabled", true);
                        }
                    });
                },

                clearCopiedAddressFields: function () {
                    if (widget.options.addressMapMode == COPYBILLINGTOADDRESS) {
                        this.clearDeliveryAddress();
                    } else {
                        this.clearBillingAddress();
                    }
                    this.validateInputFields(false);
                },
                
                setLocalDeliveryAddress: function() {
                    if (!widget.options.isViewOnly && this.get("addressItemList").length > 0 || this.get("billingAddressItemList").length > 0) {
                        var data = {},
                            deliveryAddressMode = this.get("deliveryAddressMode");
                        data["result"] = [];
                        data.result.push({
                            AddressFieldData: this.get("addressItemList"),
                            BillingAddressFieldData: this.get("billingAddressItemList"),
                            CopyOrderConfirmationEmail: this.get("copyOrderConfirmation"),
                            SoComments: this.get("orderComments"),
                            SoCustReference: this.get("orderReference"),
                            SoDelIns: this.get("deliveryInstructions"),
                            SoDelCountry: this.get("soDelCountry"),
                            SoDelPhone: this.get("soDelPhone"),
                            AttentionUserName: this.get("attentionUserName"),
                            AttentionPhoneNumber: this.get("attentionPhoneNumber"),
                            ContactFirstName: deliveryAddressMode == "Pickup" ? this.get("contactFirstName") : "",
                            ContactLastName: deliveryAddressMode == "Pickup" ? this.get("contactLastName") : "",
                            ContactPhoneNumber: deliveryAddressMode == "Pickup" ? this.get("contactPhoneNumber") : ""
                        });
                        $.cv.css.localSetcurrentDeliveryAddress(data);
                        $.cv.css.trigger($.cv.css.eventnames.localdeliveryAddressChanged);
                    }
                }
            });

            // "Pickup warehouse changed" event.
            $.cv.css.bind($.cv.css.eventnames.pickupWarehouseChanged, function (msg) {
                if (widget.options.isViewOnly) {
                    var pickupAddress = $.cv.css.localGetcurrentPickupAddress();
                    if (pickupAddress != null) {
                        viewModel.set("pickupAddress", pickupAddress);
                    }
                    return;
                }
                if (msg.warehouse == "") {
                    viewModel.clearDeliveryAddress(false);

                    var soDelAddress = viewModel.getSoDelAddress();
                    soDelAddress.updatedAddressDetails.SoWhseCode = "";
                    viewModel.updateDeliveryAddress(soDelAddress, "");
                    viewModel.validateInputFields(false);
                    return;
                }

                var p = $.cv.css.storeLocator.getWarehouse({ warehouseCode: msg.warehouse });
                $.when(p).done(function (data) {
                    viewModel.set("isInitialLoad", true);

                    viewModel.set("soDelPhone", data.data[0].Phone);
                    viewModel.set("soDelCountry", "");

                    $.each(viewModel.get("addressItemList"), function (idx, item) {
                        switch (item.fieldItem.fieldName.toUpperCase()) {
                            case "SODELADDR1":
                                viewModel.set(item.fieldItem.fieldName, data.data[0].AddressLine1);
                                item[item.fieldItem.fieldName] = data.data[0].AddressLine1 // Used by the view only address widget
                                break;

                            case "SODELADDR2":
                                viewModel.set(item.fieldItem.fieldName, data.data[0].AddressLine2);
                                item[item.fieldItem.fieldName] = data.data[0].AddressLine2 // Used by the view only address widget
                                break;

                            case "SODELSUBURB":
                                viewModel.set(item.fieldItem.fieldName, data.data[0].Suburb);
                                item[item.fieldItem.fieldName] = data.data[0].Suburb // Used by the view only address widget
                                break;

                            case widget.options.pickupSuburbFieldName.toUpperCase():
                                item[item.fieldItem.fieldName] = data.data[0].Suburb // Used by the view only address widget
                                break;

                            case "SODELSTATE":
                                viewModel.set(item.fieldItem.fieldName, data.data[0].State);
                                item[item.fieldItem.fieldName] = data.data[0].State // Used by the view only address widget
                                break;

                            case widget.options.pickupStateFieldName.toUpperCase():
                                item[item.fieldItem.fieldName] = data.data[0].State // Used by the view only address widget
                                break;

                            case "SODELPOSTCODE":
                                viewModel.set(item.fieldItem.fieldName, data.data[0].Postcode);
                                item[item.fieldItem.fieldName] = data.data[0].Postcode // Used by the view only address widget
                                break;
                        }
                    });

                    widget.trigger(ADDRESSRENDERED);
                    viewModel.setLocalDeliveryAddress();
                    viewModel.validateInputFields(false);
                    viewModel.set("isInitialLoad", false);
                });
            });

            viewModel.bind("change", function (e) {
                if (!viewModel.get("isInitialLoad") && !widget.options.isViewOnly) {
                    if (e.field == "multipleDeliveryAddressValue") {
                        viewModel.multiAddressItemSelected();
                    }
                    if (e.field == "deliveryAddressValidationValue") {
                        if (widget.options.autoSelectAddressValidationValue)
                            viewModel.addressValidationItemSelected();
                    }
                    if (e.field == "orderReference") {
                        viewModel.setCustomerReference();
                    }
                    if (e.field == "orderReference" || e.field == "copyOrderConfirmation") {
                        viewModel.validateInputFields();
                        if (e.field == "copyOrderConfirmation") {
                            viewModel.setCopyOrderConfirmation();
                        }
                    }
                    if (((e.field == "addressItemList" || e.field == "billingAddressItemList") && e.action == "itemchange")) {
                        if (widget.options.multiUpdateThrottleTimeout == 0 || (!viewModel.get("isAddressBeingEdited") && !viewModel.get("isBillingAddressBeingEdited"))) {
                            viewModel.processLinkedAddresses(e);
                        }
                    }
                    if (e.field == "linkDeliveryAndBilling") {
                        viewModel.processLinkedAddresses(e);
                    }
                    if (e.field == "deliveryAddressMode") {
                        viewModel.deliveryAddressModeChanged(e);
                    }
                    if ((e.field == "contactFirstName" || e.field == "contactLastName" || e.field == "contactPhoneNumber") && !viewModel.get("clearingPickupContact")) {
                        viewModel.setPickupContact();
                        viewModel.validateInputFields();
                    }
                }
                if (!widget.options.isViewOnly) {
                    viewModel.setLocalDeliveryAddress();
                }
            });

            init();

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        },

        _getDefaultAddressViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemViewTemplate + "'>"
                + "<div class='fieldContainer'><label data-bind='html: prompt'></label><span data-bind='html: fieldTemplate'></span></div>"
                + "</script>";
            return html;
        },

        _getDefaultMultipleAddressViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        },

        _getDefaultAddressValidationViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        }

    };

    // register the widget
    $.cv.ui.widget(deliveryAddressWidget);

})(jQuery);
;
/*
 *  Widget Skeleton: ViewModel pattern, no dataSource

    Reference for Kendo MVVM bindings: http://docs.kendoui.com/getting-started/framework/mvvm/bindings/value

    require
    Scripts/jquery-1.8.3.js
    Scripts/kendo.core.js
    Scripts/kendo.data.js
    Scripts/kendo.binder.js
    Scripts/cv.js
    Scripts/cv.css.js (For CSS Related Widgets)
    Scripts/cv.css.orders.js (For CSS Related Widgets)
    Scripts/cv.css.paymentProcessor.js (For CSS Related Widgets)
    Scripts/cv.util.js
    Scripts/cv.widget.kendo.js

    Conventions and requirements:
    * declare event names as variables to avoid typos
    * declare a widget definition object including the following:
    * include "name" property specifying the name of the widget
    * include options property containing the default widget option values
    *   avoid setting option default to "undefined", as this prevents declarative initialisation of that option
    *   note that options are case sensitive
    *   group options as follows: view model defaults, view model functional flags, event handlers (= null), view functional flags, view text defaults, viewTemplate (= null)
    * include events array for any events to be triggered
    * reference the widget object as "widget" - var widget = this; - inside widget object methods
    *   then reference options as widget.options, and the widget instance DOM element as widget.element
    * initialise method
    *   purpose - get the view - get the view model - bind the view model to the view
    *   check for an internal view
    *   if no internal view, 
    *       use options.viewTemplate or call _getDefaultViewTemplate 
    *       note tha viewTemplate option is an element ID, not template HTML
    *       evaluate the template passing the widget.options
    *       set _viewAppended to true
    *   bind the View Model to the View
    * destroy method
    *   if DOM elements have been appended, they should be removed
    *   remove the data element from widget.element
    * ViewModel
    *   declare _getViewModel() function which returns the widget view model
    *   initialise method should assign this to viewModel widget property
    *   refer to options using widget.options.xxx
    *   declare the viewModel object as kendo.observable() - do not extend widget.options - as this may include event handlers and other properties not required in the view model
    *   avoid referring to DOM elements, as they may not exist in a particular implementation's view
    *   avoid triggering events on DOM elements - trigger events on the widget object (they must be defined in the events array)
    *   avoid retrieving DOM event parameters from viewModel method parameters - unless they are explicitly defined as keyUp event handlers or similar where the DOM event parameters are necessary
    * View
    *   declare _getDefaultViewTemplate() method which returns the default View template html
    *   default view template should be modified depending on the widget options
    *
    * register the widget by passing the widget definition object to $.cv.ui.widget()

 */

// TODO: add in other payment options, currently only handles account, credit card (standard and eway), approval and paypal payments

;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        OPTIONSRENDERED = "optionsRendered",
        PAYMENTUNSUCCESSFUL = "paymentUnsuccessful",
        PAYMENTPROVIDERMODESTANDARD = "standard",
        PAYMENTPROVIDERMODEWAY = "eway",
        PAYMENTPROVIDERMODEWAYV3 = "eway-v3",
        EWAYPROCESSINGMODE = "ClientSidePost",
        MASTERCARD = "CARDMASTERCARD",
        VISA = "CARDVISA",
        AMEX = "CARDAMEX",
        DINERSCLUB = "CARDDINERS";

    var paymentOptionsWidget = {


        // Standard Variables

        // widget name
        name: "paymentOptions",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            autoBind: true,
            paymentOptionTextField: "label",
            paymentOptionValueField: "value",
            triggerMessages: true,
            // viewModel flags
            accountPayment: false,
            creditCard: false,
            paypal: false,
            bpay: false,
            weWillCallYou: false,
            eft: false,
            eftBankReceiptNumberDisabled: false,
            approval: false,
            isFreightQuoteSelected: false,
            isFreightQuoteInstructsVisisble: false,
            includeInBrowserHistory: true,
            showValidationMessagesNotPreventingCheckout: false,
            autoSelectCardType: true,
            currentSelectedOption: "accountPayment",
            paymentTypes: {
                account: "ACCOUNT",
                card: "CARD",
                paypal: "PAYPAL",
                bpay: "BPAY",
                weWillCallYou: "WEWILLCALLYOU",
                eft: "EFT",
                zero: "ZERO",
                approval: "APPROVAL", 
                approvalConfigRequired: "ApprovalConfigurationError"
            },
            paymentTypeLabels: { ACCOUNT: "Account", CARD: "Credit Card", PAYPAL: "Paypal", BPAY: "BPay", WEWILLCALLYOU: "We Will Call You", EFT: "EFT", APPROVAL: "Approval", ApprovalConfigurationError: "Configure Approver" },
            paymentTypeValues: {
                ACCOUNT: "accountPayment",
                CARD: "creditCard",
                PAYPAL: "paypal",
                BPAY: "bpay",
                WEWILLCALLYOU: "weWillCallYou",
                EFT: "eft",
                ZERO: "zero",
                APPROVAL: "approval",
                ApprovalConfigurationError: ""
            },
            cardPromptOverrides: { Visa: "", MasterCard: "Master Card", Amex: "" },
            approvalPromptOverrides: { APPROVALPAYONACCOUNT: "", APPROVALPAYONCARD: "" },
            cardExpiryMonthPrompts: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
            cardYearsToShow: 10,
            amexNumberOfDigits: 15,
            nonAmexNumberOfDigits: 16,
            // events
            // view flags
            sessionTimeOutRedirectUrl: 'login.aspx',
            paymentOkRedirectUrl: "OnlinePaymentOK.aspx",
            quoteSubmittedRedirectUrl: "CustomPage.aspx?CustomPage=QuoteSubmitted",
            // view text defaults
            textButtonAccountPayment: 'Submit Order',
            textButtonCreditCard: 'Finalise Order',
            textButtonPaypal: 'Process Order With PayPal',
            textButtonBpay: 'Submit Order',
            textButtonWeWillCallYou: 'Submit Order',
            textButtonEft: 'Submit Order',
            textButtonDefault: 'Submit Order',
            textButtonApproval: 'Submit Order',
            textErrorGettingPaymentOptions: "There was an error retrieving your payment options, please try again later",
            textPleaseSelectPrompt: "Please Select...",
            textSelectMonth: 'Select Month',
            textSelectYear: 'Select Year',
            textErrorNoApprovalTypeSelected: "Please select an approval type",
            textErrorNoBankReceiptInput: "Please input your EFT Receipt",
            invalidCreditCardInformation: "Your credit card information is invalid",
            giftCardBalanceNotZeroMessage: "Your gift card balance is not zero",
            textErrorNotAcknowledgedFreightQuote: "Please acknowledge that you wish to receive a freight quote",
            processPaymentUnsuccessfulMessage: "",
            // widget settings
            paymentProviderMode: PAYMENTPROVIDERMODESTANDARD,
            // view Template
            viewTemplate: '', // treat like its an id
            itemViewTemplate: null
        },

        events: [DATABINDING, DATABOUND, OPTIONSRENDERED, PAYMENTUNSUCCESSFUL],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,
        _itemViewAppended: false,

        // Standard Methods
        initialise: function (el, o) {
            var widget = this;

            // Register Widget so we can determine when they are all completed loading
            $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadRegistration, { name: widget.name }); // MAF USE ONLY

            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (!widget._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);

            // Subscribe to Approval Widget events
            //

            $.cv.css.bind($.cv.css.eventnames.approvalConfirmed,    $.proxy(widget.viewModel.approvalConfirmed, widget.viewModel));

            // Note: the following two are handled by the same method as we just 
            // want to re-load anyway, We can adjust the handlers for this as needed.
            $.cv.css.bind($.cv.css.eventnames.approvalError,        $.proxy(widget.viewModel.approvalConfirmed, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.approvalNotSpecified, $.proxy(widget.viewModel.approvalConfirmed, widget.viewModel));


            // Subscribe to Other Events
            //

            $.cv.css.bind($.cv.css.eventnames.orderChanged,         $.proxy(widget.viewModel.orderUpdated, widget.viewModel));
            
            // Listen to made visible and selection events of freight quote submit chosen on freightCarrier widget so can hide this display as not relevant to pay for order 
            // in that case, or to trigger validation error at submission if not freight quote visible but not selected / acknowledged . When submit will submit as quote 
            // instead of noraml payment submission.
            $.cv.css.bind($.cv.css.eventnames.freightQuoteSelected, $.proxy(widget.viewModel.freightQuoteSelected, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.freightQuoteInstructsVisisble, $.proxy(widget.viewModel.freightQuoteInstructsVisisble, widget.viewModel));
            
            widget.trigger(DATABOUND);
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.options.prefix + widget.options.name);
            // clean up the DOM
            if (widget._viewAppended) {
                widget.element.empty();
            }
        },

        submitOrder: function () {
            var widget = this;
            widget.viewModel.submitOrder();
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var init = function () {
                $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadStarted, { name: widget.name }); // MAF USE ONLY

                var d1 = $.cv.css.paymentProcessor.getPaymentOptionsForCurrentOrder();
                $.when(d1).done(function (msg) {
                    var data = msg.data;
                    if (msg.errorMessage === null || msg.errorMessage.length == 0) {
                        viewModel.set("paymentOptions", data);
                        if (data.length > 0) {
                            var approvalPayment = $.grep(viewModel.get("paymentOptions"), function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.approval) > -1 || item.Key == widget.options.paymentTypes.approvalConfigRequired); });
                            // for approvals only show the approval option
                            if (approvalPayment.length > 0) {
                                viewModel.set("approval", true);
                                viewModel.selectApprovalPayment();
                                if (approvalPayment[0].Key == widget.options.paymentTypes.approvalConfigRequired) {
                                    viewModel.set("approvalConfigRequired", true);
                                    viewModel.set("approvalRequired", true);
                                } else {
                                    if (approvalPayment.length == 1)
                                        viewModel.set("approvalType", approvalPayment[0].Key);
                                }
                            } else {
                                var cardOptions = $.grep(viewModel.get("paymentOptions"), function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.card) > -1); });
                                var nonCardOptions = $.grep(viewModel.get("paymentOptions"), function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.card) == -1); });
                                var cardsAvailable = cardOptions.length > 0 ? 1 : 0;
                                if (cardsAvailable && (widget.options.paymentProviderMode == PAYMENTPROVIDERMODEWAY || widget.options.paymentProviderMode == PAYMENTPROVIDERMODEWAYV3)) {
                                    getPaymentProviderDetails();
                                    ewayCreateAccessCode(false, 0);
                                }
                                // if only a single option is available default the selction to this option
                                var isSingleOption = (cardsAvailable + nonCardOptions.length) == 1;
                                $.each(data, function (idx, item) {
                                    switch (item.Key) {
                                        case widget.options.paymentTypes.account:
                                            viewModel.set("accountPayment", true);
                                            if (isSingleOption)
                                                viewModel.selectAccountPayment();
                                            break;
                                        case widget.options.paymentTypes.paypal:
                                            viewModel.set("paypal", true);
                                            if (isSingleOption)
                                                viewModel.selectPaypal();
                                            break;
                                        case widget.options.paymentTypes.bpay:
                                            viewModel.set("bpay", true);
                                            if (isSingleOption)
                                                viewModel.selectBpay();
                                            break;
                                        case widget.options.paymentTypes.weWillCallYou:
                                            viewModel.set("weWillCallYou", true);
                                            if (isSingleOption)
                                                viewModel.selectWeWillCallYou();
                                            break;
                                        case widget.options.paymentTypes.eft:
                                            viewModel.set("eft", true);
                                            viewModel.set("eftReceiptNumber", item.ReceiptNumber);
                                            if (isSingleOption)
                                                viewModel.selectEft();
                                            break;
                                        case widget.options.paymentTypes.zero:
                                            viewModel.set("zero", true);
                                            if (isSingleOption)
                                                viewModel.selectZero();
                                            break;
                                        default:
                                            if (item.Key.indexOf(widget.options.paymentTypes.card) > -1) {
                                                viewModel.set("creditCard", true);
                                                if (isSingleOption)
                                                    viewModel.selectCreditCard();
                                            }
                                    }
                                });
                            }
                            // bind the options to a data source so it can be viewed as a select list, radio button list etc
                            widget.options.dataSource = viewModel.get("paymentOptions");
                            setDataSource();
                        } else {
                            $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadFinished, { name: widget.name });
                        }
                    } else {
                        $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadFinished, { name: widget.name });
                    }
                }).fail(function (msg) {
                    $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadFinished, { name: widget.name });
                    viewModel.setMessage(widget.options.textErrorGettingPaymentOptions, $.cv.css.messageTypes.error);
                });
            };

            var setDataSource = function () {
                widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

                if (widget.options.autoBind) {
                    widget.dataSource.fetch(function () {
                        $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadFinished, { name: widget.name });
                    });
                }
                viewModel.updateItemList();
            };

            var getPaymentProviderDetails = function () {
                var d1 = $.cv.css.paymentProcessor.getPaymentProviderDetails(), titles = [], cardHolderTitle = "";
                $.when(d1).done(function (msg) {
                    if (!msg.errorMessage || msg.errorMessage.length == 0) {
                        $.each(msg.data, function (idx, item) {
                            if (item != "")
                                titles.push({ value: item, text: item });
                            else
                                titles.push({ value: item, text: widget.options.textPleaseSelectPrompt });
                        });
                        if (viewModel.get("cardHolderTitle").length > 0)
                            cardHolderTitle = viewModel.get("cardHolderTitle");
                        viewModel.set("cardHolderTitleDataSource", titles);
                        // timing thing, if the eway access code call returns before the provide details call the field could be disabled and the value not properly updated
                        if (cardHolderTitle.length > 0 && viewModel.get("cardDetailsRemembered")) {
                            viewModel.set("cardDetailsRemembered", false);
                            // set it back to nothing, then reset it to fire off the change event
                            viewModel.set("cardHolderTitle", "");
                            viewModel.set("cardHolderTitle", cardHolderTitle);
                            viewModel.set("cardDetailsRemembered", true);
                        }
                    }
                }).fail(function (msg) {
                    // TODO: should there be an error message displayed here?
                    //viewModel.setMessage(widget.options.textErrorGettingPaymentOptions, $.cv.css.messageTypes.error);
                });
            };

            var ewayCreateAccessCode = function (clearRememberedPaymentInfo, orderNoForAccessCode) {
                //var paymentInfo = {PaymentAmount: 50.00, IsAdditionalDeposit: false, IsDeposit: false, OrderNo: 0};
                //var d1 = $.cv.css.paymentProcessor.ewayCreateAccessCode({ paymentInfo: paymentInfo, clearRememberedPaymentInfo: false });
                if (orderNoForAccessCode == undefined)
                    orderNoForAccessCode = 0;
                var customer = null;
                // TODO: replace webervicesajax call with dynamic service call
                var d1 = $.cssWebServicesAjax.eWayCreateAccessCode({ clearRememberedPaymentInfo: clearRememberedPaymentInfo, orderNoForAccessCode: orderNoForAccessCode });
                $.when(d1).done(function (msg) {
                    var data = msg.d;
                    if (!data.IsError) {
                        if (data && data.Response && data.Response.Customer && data.Response.Customer.TokenCustomerID != null) {
                            customer = data.Response.Customer;
                            viewModel.setCardDetails(
                                false,
                                true,
                                customer.Title,
                                customer.CardName,
                                customer.CardNumber.substring(0, 4),
                                customer.CardNumber.substring(4, 8),
                                customer.CardNumber.substring(8, 12),
                                customer.CardNumber.substring(12, 16),
                                customer.CardExpiryMonth,
                                customer.CardExpiryYear,
                                "", "", "");
                        } else {
                            viewModel.clearCardDetails();
                        }
                    } else {
                        viewModel.setMessage(data.Message, $.cv.css.messageTypes.error);
                    }
                }).fail(function (msg) {
                    // TODO: should there be an error message displayed here?
                    //viewModel.setMessage(widget.options.textErrorGettingPaymentOptions, $.cv.css.messageTypes.error);
                });
            };

            var getDataView = function () {
                // check if ds is initialised
                var array = [];
                var cardAlreadyAdded = false;
                if (!widget.dataSource)
                    return array;
                $.each(widget.dataSource.view(), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    var key = item.Key;
                    if (key.indexOf(widget.options.paymentTypes.card) > -1)
                        key = widget.options.paymentTypes.card;
                    // only add once occurance of credit card to the option list
                    if (!cardAlreadyAdded) {
                        // allow the labels and values to be customised on the data source options list
                        item[widget.options.paymentOptionTextField] = widget.options.paymentTypeLabels[key];
                        item[widget.options.paymentOptionValueField] = widget.options.paymentTypeValues[key];
                        array.push(item);
                    }
                    if (key.indexOf(widget.options.paymentTypes.card) > -1)
                        cardAlreadyAdded = true;
                });
                return array;
            };

            var currentSurchargeType = "";
            var currentSurcharge = 0;
            var checkAgainst = [
                "CardAny",
                "CardAmex",
                "CardDinersClub",
                "CardMasterCard",
                "CardVisa",
                "BPay",
                "EFT",
                "PayPal",
                "3rdParty",
                "Call",
                "UserCall"
            ];
            var setSurcharge = function (currentSelection) {
                if (currentSelection) {
                    //Has It Changed?
                    if (currentSelection.toLowerCase() != currentSurchargeType.toLowerCase()) {
                        // Set it so we dont spam it
                        currentSurchargeType = currentSelection;
                        // Check all against possible
                        var indexToCheck = -1;
                        for (var index = 0; index < checkAgainst.length; index++) {
                            if (checkAgainst[index].toLowerCase() == currentSelection.toLowerCase()) {
                                indexToCheck = index;
                                break;
                            }
                        }
                      
                        var check = indexToCheck != -1 ?
                            $.cv.css.orders.applyPaymentSurcharge({ paymentMethod: checkAgainst[indexToCheck] }) :
                            $.cv.css.orders.removePaymentSurcharge();

                        check.done(function(response) {
                            if (indexToCheck != -1) {
                                var result = response.data.result[0];
                                widget.viewModel.set('cardChargeInfo', result);
                                if (response.data.errorMessage != null && response.data.errorMessage != "") {
                                    widget.viewModel.setMessage(response.data.errorMessage, $.cv.css.messageTypes.error);
                                }
                            } else {
                                widget.viewModel.set('cardChargeInfo', null);
                                if (response.errorMessage != null && response.errorMessage != "") {
                                    widget.viewModel.setMessage(response.errorMessage, $.cv.css.messageTypes.error);
                                }
                            }

                            $.cv.css.trigger($.cv.css.eventnames.orderChanged, { orderChangedBySurcharge: true });

                            currentSurcharge = indexToCheck != -1 ? 1 : 0;
                        });
                    }
                }
            };

            var viewModel = kendo.observable({

                // Properties for UI elements
                paymentSuccessful: false, // states on validation failure to update UI (used in mobile)

                paymentUnsuccessful: false,

                isProcessing: false,

                message: '',

                currentSelectedOption: widget.options.currentSelectedOption,

                itemList: getDataView(),

                dataSource: widget.options.dataSource,

                approvalType: "",

                creditCardType: "",

                cardHolderTitle: "", // eway

                cardHolderTitleDataSource: [], // eway

                cardHolderName: "",
                
                cardChargeInfo: {},

                cardNumber1: "",

                cardNumber2: "",

                cardNumber3: "",

                cardNumber4: "",

                cardNumber: "",

                cardExpiryMonth: "",

                cardExpiryYear: "",

                cardSecurityCode: "",

                rememberPayment: false, // eway

                cardDetailsRemembered: false, // eway

                isEWayProcessing: false,

                eWayFieldsEnabled: function () {
                    var enabled = true;
                    if (this.get("cardDetailsRemembered"))
                        enabled = false;
                    if (this.get("isEWayProcessing"))
                        enabled = true;
                    return enabled;
                },

                eWayFieldsReadOnly: function () {
                    var readOnly = false;
                    if (this.get("isEWayProcessing"))
                        readOnly = true;
                    return readOnly;
                },

                eWayAccessCode: "", // eway

                totalPaymentBalanceAfterEnteredGiftCards: -1,

                eftReceiptNumber: "",

                // UI Element state

                isGiftCardBalanceZero: function () {
                    return this.get("totalPaymentBalanceAfterEnteredGiftCards") == 0;
                },

                clearExistingMessages: true,

                accountPayment: widget.options.accountPayment,

                creditCard: widget.options.creditCard,

                paypal: widget.options.paypal,

                bpay: widget.options.bpay,

                weWillCallYou: widget.options.weWillCallYou,

                eft: widget.options.eft,

                approval: widget.options.approval,

                approvalConfigRequired: false,
                approvalRequired: false,

                paymentOptions: {},

                validationPreventingCheckout: false,

                hasOptions: false,

                hasMultipleOptions: function () {
                    var optionsCount = 0;
                    if (this.get("accountPayment"))
                        optionsCount++;
                    if (this.get("creditCard"))
                        optionsCount++;
                    if (this.get("paypal"))
                        optionsCount++;
                    if (this.get("bpay"))
                        optionsCount++;
                    if (this.get("weWillCallYou"))
                        optionsCount++;
                    if (this.get("eft"))
                        optionsCount++;
                    if (this.get("approval"))
                        optionsCount++;
                    return optionsCount > 1;
                },

                hasCardHolderTitles: function () {
                    return this.get("cardHolderTitleDataSource").length > 0;
                },

                accountPaymentSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.account];
                },

                creditCardSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.card];
                },

                paypalSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.paypal];
                },

                bpaySelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.bpay];
                },

                weWillCallYouSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.weWillCallYou];
                },

                eftSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.eft];
                },

                approvalSelected: function () {
                    return this.get("currentSelectedOption") == widget.options.paymentTypeValues[widget.options.paymentTypes.approval];
                },

                creditCardTypeValid: function () {
                    return (this.get("creditCardType") != "");
                },

                cardHolderNameValid: function () {
                    return this.get("cardHolderName") != "";
                },

                cardNumberValid: function () {
                    var cardNumber = this.cardNumberFull();
                    return cardNumber.length == this.creditCardDigitsRequired();
                },

                cardExpiryMonthValid: function () {
                    return this.get("cardExpiryMonth") != "";
                },

                cardExpiryYearValid: function () {
                    return this.get("cardExpiryYear") != "";
                },

                cardSecurityCodeValid: function () {
                    return this.get("cardSecurityCode") != "";
                },

                setCardDetails: function (rememberPayment, cardDetailsRemembered, cardHolderTitle, cardHolderName, cardNumber1, cardNumber2, cardNumber3, cardNumber4, cardExpiryMonth, cardExpiryYear, cardSecurityCode, cardNumber, creditCardType) {
                    this.set("rememberPayment", rememberPayment);
                    this.set("cardDetailsRemembered", cardDetailsRemembered);
                    this.set("cardHolderTitle", cardHolderTitle);
                    this.set("cardHolderName", cardHolderName);
                    this.set("cardNumber1", cardNumber1);
                    this.set("cardNumber2", cardNumber2);
                    this.set("cardNumber3", cardNumber3);
                    this.set("cardNumber4", cardNumber4);
                    var month = cardExpiryMonth;
                    if (month && month.toString().length == 1)
                        month = "0" + month.toString();
                    this.set("cardExpiryMonth", month);
                    var year = cardExpiryYear;
                    if (year && year.toString().length == 1)
                        year = "0" + year.toString();
                    this.set("cardExpiryYear", year);
                    this.set("cardSecurityCode", cardSecurityCode);
                    this.set("cardNumber", cardNumber);
                    this.set("creditCardType", creditCardType);
                    // reset success state
                    this.set("paymentSuccessful", false);
                    this.set("paymentUnsuccessful", false);
                },

                clearCardDetails: function () {
                    this.setCardDetails(false, false, "", "", "", "", "", "", "", "", "", "", "");
                },

                // functions for UI events

                orderUpdated: function (data) {
                    var order = $.cv.css.localGetCurrentOrder(widget.options.displayingOrderInformation);
                    if (order.TotalPaymentBalanceAfterEnteredGiftCards != undefined) {
                        this.set("totalPaymentBalanceAfterEnteredGiftCards", order.TotalPaymentBalanceAfterEnteredGiftCards);
                        if (this.isGiftCardBalanceZero()) {
                            viewModel.selectCreditCard();
                        }
                    } else {
                        this.set("totalPaymentBalanceAfterEnteredGiftCards", -1);
                    }
                    if (!data || (data && data.orderChangedBySurcharge != undefined && !data.orderChangedBySurcharge)) {
                        // only get into here if it was not the set surcharge method that trigger the order update
                        // don't go into a continuous loop
                        currentSurchargeType = "";
                        if (!this.creditCardSelected()) {
                            setSurcharge(this.get("currentSelectedOption"));
                        } else {
                            setSurcharge(this.get("creditCardType"));
                        }
                    }
                },

                submitButtonText: function () {
                    switch (this.get("currentSelectedOption")) {
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.account]:
                            return widget.options.textButtonAccountPayment;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.card]:
                            return widget.options.textButtonCreditCard;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.paypal]:
                            return widget.options.textButtonPaypal;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.bpay]:
                            return widget.options.textButtonBpay;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.weWillCallYou]:
                            return widget.options.textButtonWeWillCallYou;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.eft]:
                            return widget.options.textButtonEft;
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.approval]:
                            return widget.options.textButtonApproval;
                            break;
                        default:
                            return widget.options.textButtonDefault;
                    }
                },

                // Timeouts...
                redirectToUrl: function (fallbackUrl, params, includeInBrowserHistory) {
                    if ($.cv.ajax.settings.timeoutRedirectUrl == "")
                        $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
                    else
                        $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
                },

                // Successful payments...
                redirect: function (uri, params, includeInBrowserHistory) {
                    $.cv.util.redirect(uri, params, includeInBrowserHistory);
                },

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                clearMessage: function () {
                    this.set("clearExistingMessages", true);
                    this.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'paymentOptions', clearExisting: this.get("clearExistingMessages") });
                },

                updateItemList: function () {
                    var dataView = getDataView();
                    this.set("itemList", dataView);
                    this.set("hasOptions", dataView.length > 0);
                    widget.trigger(OPTIONSRENDERED);
                },

                approvalConfirmed: function () {
                    var _this = this;
                    _this.refreshDatasource();
                },

                freightQuoteSelected: function (opts) {
                    this.set("isFreightQuoteSelected", opts.isSelected);
                },

                freightQuoteInstructsVisisble: function (opts) {
                    this.set("isFreightQuoteInstructsVisisble", opts.isVisible);
                },

                refreshDatasource: function () {
                    this.set("accountPayment", widget.options.accountPayment);
                    this.set("creditCard", widget.options.creditCard);
                    this.set("paypal", widget.options.paypal);
                    this.set("bpay", widget.options.bpay);
                    this.set("weWillCallYou", widget.options.weWillCallYou);
                    this.set("eft", widget.options.eft);
                    this.set("approval", widget.options.approval);
                    this.set("approvalConfigRequired", false);
                    init();
                },

                approvalTypesDataSource: function () {
                    var payOptions = this.get("paymentOptions");
                    var typesAvailable = [];
                    var ds = [];
                    if (payOptions != null)
                        typesAvailable = $.grep(payOptions, function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.approval) > -1); });
                    if (typesAvailable.length > 1) {
                        ds.push({ text: "Select Type", value: "" });
                        $.each(typesAvailable, function (idx, item) {
                            if (widget.options.approvalPromptOverrides[item.Key] != undefined)
                                ds.push({ text: widget.options.approvalPromptOverrides[item.Key] != "" ? widget.options.approvalPromptOverrides[item.Key] : item.Value, value: item.Key });
                            else
                                ds.push({ text: item.Value, value: item.Key });
                        });
                    }
                    return new kendo.data.ObservableArray(ds);
                },

                hasMultipleApprovalTypes: function () {
                    return this.approvalTypesDataSource().length > 0;
                },

                creditCardTypesDataSource: function () {
                    var payOptions = this.get("paymentOptions");
                    var cardsAvailable = [];
                    var ds = [];
                    if (payOptions != null)
                        cardsAvailable = $.grep(payOptions, function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.card) > -1); });
                    ds.push({ text: "Select Type", value: "", key: "" });
                    $.each(cardsAvailable, function (idx, item) {
                        if (widget.options.cardPromptOverrides[item.Value] != undefined)
                            ds.push({ text: widget.options.cardPromptOverrides[item.Value] != "" ? widget.options.cardPromptOverrides[item.Value] : item.Value, value: item.Value, key: item.Key });
                        else
                            ds.push({ text: item.Value, value: item.Value, key: item.Key });
                    });
                    return new kendo.data.ObservableArray(ds);
                },

                creditCardDigitsRequired: function () {
                    return this.get("creditCardType") == AMEX ? widget.options.amexNumberOfDigits : widget.options.nonAmexNumberOfDigits;
                },

                cardNumberFull: function () {
                    if (this.get("cardNumber") != "")
                        return this.get("cardNumber");
                    else
                        return this.get("cardNumber1") + this.get("cardNumber2") + this.get("cardNumber3") + this.get("cardNumber4");
                },

                setCreditCardType: function () {
                    var _this = this, cardNumber = this.cardNumberFull();
                    //start without knowing the credit card type
                    var cardType = "";
                    //first check for MasterCard
                    if (/^5[1-5]/.test(cardNumber)) {
                        cardType = MASTERCARD;
                        setSurcharge(cardType);
                    }
                        //then check for Visa
                    else if (/^4/.test(cardNumber)) {
                        cardType = VISA;
                        setSurcharge(cardType);
                    }
                        //then check for AmEx
                    else if (/^3[47]/.test(cardNumber)) {
                        cardType = AMEX;
                        setSurcharge(cardType);
                    }
                        //then check for Diners
                    else if (/^3[068]/.test(cardNumber)) {
                        cardType = DINERSCLUB;
                        setSurcharge(cardType);
                    }

                    var paymentType = $.grep(_this.get("paymentOptions"), function (item, idx) { return (item.Key.indexOf(widget.options.paymentTypes.card) > -1 && item.Key == cardType); });
                    if (paymentType.length == 0) {
                        cardType = "";
                    }
                    _this.set("creditCardType", cardType);
                },

                cardExpiryMonthPrompts: widget.options.cardExpiryMonthPrompts,

                cardExpiryMonthsDataSource: function () {
                    var p = this.get("cardExpiryMonthPrompts").split(",");
                    var ds = [];
                    ds.push({ text: widget.options.textSelectMonth, value: "" });
                    for (i = 1; i <= p.length; i++) {
                        var s = i + "";
                        while (s.length < 2) s = "0" + s;
                        ds.push({ text: p[i - 1], value: s });
                    }
                    return new kendo.data.ObservableArray(ds);
                },

                cardExpiryYearDataSource: function () {
                    var ds = [];
                    var y = widget.options.cardYearsToShow > 0 ? widget.options.cardYearsToShow : 10;
                    var currentYear = new Date().getFullYear();
                    ds.push({ text: widget.options.textSelectYear, value: "" });
                    for (i = 0; i < y; i++) {
                        var year = currentYear + i;
                        ds.push({ text: year, value: (year + "").substr(2, 2) });
                    }
                    return new kendo.data.ObservableArray(ds);
                },

                newPaymentMethodSelected: function () {
                    this.clearMessage();
                },

                selectAccountPayment: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.account]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                selectCreditCard: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.card]);
                    this.setCreditCardType();
                },

                selectPaypal: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.paypal]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                selectBpay: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.bpay]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                selectWeWillCallYou: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.weWillCallYou]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                selectEft: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.eft]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                selectZero: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.zero]);
                },

                selectApprovalPayment: function () {
                    this.newPaymentMethodSelected();
                    this.set("currentSelectedOption", widget.options.paymentTypeValues[widget.options.paymentTypes.approval]);
                    setSurcharge(this.get("currentSelectedOption"));
                },

                submitOrderKeyUp: function (event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();
                        this.submitOrder();
                    }
                },

                submitOrder: function () {
                    // Final validation of order first
                    var vm = this;

                    vm.set('paymentUnsuccessful', false);

                    if (!_.isEmpty($.cv.css.pageValidationErrors)) {
                        vm.processPageValidationErrors();
                    } else {
                        vm.validateForCheckout();
                    }
                },

                // Triggers call to validate the order to see if it is ok for checkout...
                // we will show relevant messages in the widget if needed.
                validateForCheckout: function (options) {
                    var vm = this,
                        opts = $.extend({
                            processPayment: true
                        }, options);

                    vm.set("isProcessing", true);

                    $.cv.css.getCurrentOrder()
                        .done(function () {
                            var order = $.cv.css.localGetCurrentOrder();

                            // Bail! No order!
                            if (!order._objectKey) return;

                            vm.set("validationPreventingCheckout", false);

                            $.cv.css.orders.validateForCheckout({ _objectKey: order._objectKey })
                                .done(function (response) {
                                    var data = response.data,
                                        params = {};

                                    // Reload order and lines to pick up charge lines added to the order 
                                    // like small order fee etc... (done originally for MAF order complete)
                                    $.when($.cv.css.getCurrentOrder(), $.cv.css.getCurrentOrderLines()).done(function () {
                                        // Ensure stuff updates itself with latest order totals etc due
                                        // to ValidateForCheckout potentially adjusting the order (done originally for MAF order complete)
                                        $.cv.css.trigger($.cv.css.eventnames.orderChanged).done(function () {
                                            if (!data.sessionHasTimedOut) {
                                                vm.displayValidationMessages(data);

                                                // If no errors
                                                if (!vm.get("validationPreventingCheckout") && opts.processPayment === true) {
                                                    // Once pre submit validation is complete... process the payment...
                                                    $.cv.css.trigger($.cv.css.eventnames.preOrderSubmit).done(function () {
                                                        vm.processPaymentOption();
                                                    });
                                                } else {
                                                    vm.set("isProcessing", false);
                                                }
                                            } else {
                                                vm.redirectToUrl(widget.options.sessionTimeOutRedirectUrl, params, !widget.options.includeInBrowserHistory);
                                            }
                                        });
                                    });
                                }).fail(function (response) {
                                    vm.set("isProcessing", false);

                                    if (response.errorMessage != null) {
                                        vm.setMessage(response.errorMessage, $.cv.css.messageTypes.error);
                                    } else {
                                        vm.setMessage(widget.options.textCheckoutErrorDefaultMessage, $.cv.css.messageTypes.error);
                                    }
                                });
                        }).fail(function (response) {
                            vm.set("isProcessing", false);

                            if (response.errorMessage != null) {
                                vm.setMessage(response.errorMessage, $.cv.css.messageTypes.error);
                            }
                        });
                },

                // build message array from object passed in
                buildLineValidationMessageArray: function (obj) {
                    var lineValidationErrors = new Array();
                    lineValidationErrors = [];
                    for (key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            var el = {};
                            el["lineMessages"] = obj[key]
                            lineValidationErrors.push(el);
                        }
                    }
                    return lineValidationErrors;
                },

                displayValidationMessages: function (data) {
                    var _this = this;
                    _this.clearMessage();

                    var lineValidationErrors = _this.buildLineValidationMessageArray(data.lineValidationErrors);
                    if (data.headerValidateNotOk || data.linesValidateNotOk)
                        _this.set("validationPreventingCheckout", true);
                    if (data.headerValidationErrors.length > 0 || lineValidationErrors.length > 0) {
                        _this.set("clearExistingMessages", false);
                        $.each(data.headerValidationErrors, function (idx, item) {
                            if (item.preventsCheckOut) {
                                _this.setMessage(item.errorMessage, $.cv.css.messageTypes.error);
                            } else {
                                if (widget.options.showValidationMessagesNotPreventingCheckout)
                                    _this.setMessage(item.errorMessage, $.cv.css.messageTypes.warning);
                            }
                        });
                        $.each(lineValidationErrors, function (idx, item) {
                            $.each(item["lineMessages"], function (idx, lineItem) {
                                if (lineItem.errorMessage != "") {
                                    if (lineItem.preventsCheckOut) {
                                        _this.setMessage(lineItem.lineSeq + ', ' + lineItem.productCode + ': ' + lineItem.errorMessage, $.cv.css.messageTypes.error);
                                    } else {
                                        if (widget.options.showValidationMessagesNotPreventingCheckout)
                                            _this.setMessage(lineItem.lineSeq + ', ' + lineItem.productCode + ': ' + lineItem.errorMessage, $.cv.css.messageTypes.warning);
                                    }
                                }
                            });
                        });
                        _this.set("clearExistingMessages", true);
                    }
                },

                processPaymentOption: function () {
                    // TODO: add in other payment options
                    // If freight quote visible (i.e. other options won't be) handle this (it is not part of currentSelectedOption)
                    if (this.get("isFreightQuoteInstructsVisisble")) {
                        this.submitFreightQuote();
                        return;
                    }
                    
                    var paymentOption = this.get("currentSelectedOption");
                    switch (paymentOption) {
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.account]:
                            this.submitAccountPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.card]:
                            this.submitOrderPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.paypal]:
                            this.submitPaypalPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.approval]:
                            this.submitApprovalPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.bpay]:
                            this.submitBpayPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.eft]:
                            this.submitEFTPayment();
                            break;
                        case widget.options.paymentTypeValues[widget.options.paymentTypes.zero]:
                            this.submitZeroOrderPayment();
                            break;
                    }
                },
                
                submitFreightQuote: function () {
                    var _this = this, processPaymentUnsuccessfulMessage = "";

                    // If freight quote visible but not acknowledged / selected need to not continue.
                    if (!_this.get("isFreightQuoteSelected")) {
                        var message = widget.options.textErrorNotAcknowledgedFreightQuote;
                        _this.setMessage(message, $.cv.css.messageTypes.error);
                        $.cv.util.notify(_this, message, $.cv.css.messageTypes.error);
                        return;
                    }

                    var opts = [];
                    _this.set("isProcessing", true);
                    var d1 = $.cv.css.orders.submitQuote(opts);
                    $.when(d1).done(function (msg) {
                        var data = msg.data;
                        if (!data.sessionHasTimedOut) {
                            if (data.success) {
                                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrder);
                                $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
                                // set viewmodel to indicate success
                                _this.set("paymentSuccessful", true);

                                // Redirect ----
                                // Don't use $.cv.util.redirect(), instead use internal
                                // redirect view model method as it can be overridden. It IS
                                // overridden in cv.mobile.ui.paymentOptions so that we don't
                                // redirect away from SPA.
                                if (widget.options.quoteSubmittedRedirectUrl != "") {
                                    _this.redirect(widget.options.quoteSubmittedRedirectUrl, {}, widget.options.includeInBrowserHistory);
                                }
                            } else {
                                var defaultMessageSetting = _this.get("clearExistingMessages");
                                _this.set("paymentUnsuccessful", true);
                                _this.set("clearExistingMessages", true);
                                if (data.message && data.message.length > 0) {
                                    _this.setMessage(data.message, $.cv.css.messageTypes.error);
                                    if (widget.options.processPaymentUnsuccessfulMessage == "") {
                                        processPaymentUnsuccessfulMessage = processPaymentUnsuccessfulMessage.length > 0 ? processPaymentUnsuccessfulMessage + ", " + data.message : data.message;
                                    }
                                    widget.trigger(PAYMENTUNSUCCESSFUL, { message: processPaymentUnsuccessfulMessage.length > 0 ? processPaymentUnsuccessfulMessage : widget.options.processPaymentUnsuccessfulMessage });
                                    _this.set("clearExistingMessages", defaultMessageSetting);
                                }
                                _this.set("isProcessing", false);
                            }
                        } else {
                            _this.redirectToUrl(data.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function (msg) {
                        _this.set("isProcessing", false);
                        if (msg.errorMessage != null)
                            _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                    });
                },
                
                submitBpayPayment: function () {
                    var opts = { paymentType: widget.options.paymentTypes.bpay, paymentInfo: {} };
                    this.processPayment(opts);
                },

                validateEFTInformation: function () {
                    var valid = false;
                    if (this.get("currentSelectedOption").toUpperCase() == widget.options.paymentTypes.eft.toUpperCase()
                        && (widget.options.eftBankReceiptNumberDisabled || (this.get("bankReceiptNumber") !== "" && typeof this.get("bankReceiptNumber") !== "undefined"))) {
                        valid = true;
                    } else {
                        this.setMessage(widget.options.textErrorNoBankReceiptInput, $.cv.css.messageTypes.error);
                        this.set("isProcessing", false);
                    }
                    return valid;
                },

                submitEFTPayment: function () {
                    var opts = {};
                    if (this.validateEFTInformation()) {
                        opts = { paymentType: widget.options.paymentTypes.eft, paymentInfo: { BankReceiptNumber: this.get("bankReceiptNumber") } };
                        this.processPayment(opts);
                    }
                },

                submitAccountPayment: function () {
                    var opts = { paymentType: widget.options.paymentTypes.account, paymentInfo: {} };
                    this.processPayment(opts);
                },

                submitPaypalPayment: function () {
                    var opts = { paymentType: widget.options.paymentTypes.paypal, paymentInfo: {} };
                    this.processPayment(opts);
                },

                validateApprovalInformation: function () {
                    var valid = true;
                    if (this.hasMultipleApprovalTypes() && this.get("approvalType") == "") {
                        this.setMessage(widget.options.textErrorNoApprovalTypeSelected, $.cv.css.messageTypes.error);
                        valid = false;
                    }
                    return valid;
                },

                submitApprovalPayment: function () {
                    var opts = {};
                    if (this.validateApprovalInformation()) {
                        if (this.hasMultipleApprovalTypes())
                            opts = { paymentType: this.get("approvalType"), paymentInfo: {} };
                        else
                            opts = { paymentType: widget.options.paymentTypes.approval, paymentInfo: {} };
                        this.processPayment(opts);
                    }
                },

                submitZeroOrderPayment: function () {
                    this.processPayment({ paymentType: 'ZERO' });
                },

                clearRememberedPaymentInfo: function () {
                    ewayCreateAccessCode(true);
                },

                validateCreditCardInformation: function () {
                    var valid = true;
                    var message = ""
                    if (!this.creditCardTypeValid())
                        message += ",Card Type";
                    if (!this.cardHolderNameValid())
                        message += ",Name On Card";
                    if (!this.cardNumberValid())
                        message += ",Credit Card Number (incorrect number of digits)";
                    if (!this.cardExpiryMonthValid())
                        message += ",Expiry Month";
                    if (!this.cardExpiryYearValid())
                        message += ",Expiry Year";
                    if (!this.cardSecurityCodeValid())
                        message += ",Security Code";
                    if (message.length > 0) {
                        message = "The following card details have errors: " + message.substr(1);
                        valid = false;
                        this.setMessage(message, $.cv.css.messageTypes.error);
                    }
                    return valid;
                },

                submitOrderPayment: function () {
                    // TODO: process credit card payments
                    var _this = this,
                        opts = {},
                        cardName = _this.get("creditCardType").replace(/CARD/, ""),
                        submitOrderPaymentUnsuccessfulMessage = "",
                        isGiftCardBalanceZero = this.isGiftCardBalanceZero(),
                        validCreditCardInformation = true;

                    if (!isGiftCardBalanceZero) {
                        validCreditCardInformation = this.validateCreditCardInformation();
                    }

                    if (isGiftCardBalanceZero || validCreditCardInformation) {
                        if (widget.options.paymentProviderMode == PAYMENTPROVIDERMODESTANDARD) {
                            opts = {
                                paymentType: widget.options.paymentTypes.card,
                                paymentInfo: {
                                    CardName: cardName,
                                    CardHolderName: _this.get("cardHolderName"), // Any Name
                                    CardNumber: _this.cardNumberFull(), //"4111111111111111", // This is DPS Test Card Number - Payment Express 
                                    CardExpiryMonth: _this.get("cardExpiryMonth"), // Any For DPS Test
                                    CardExpiryYear: _this.get("cardExpiryYear"),
                                    CardSecurityCode: _this.get("cardSecurityCode")
                                }
                            };
                        } else if (widget.options.paymentProviderMode == PAYMENTPROVIDERMODEWAY || widget.options.paymentProviderMode == PAYMENTPROVIDERMODEWAYV3) {
                            opts = {
                                paymentType: widget.options.paymentTypes.card,
                                paymentInfo: {
                                    CardDetailProcessingMode: EWAYPROCESSINGMODE,
                                    CardName: cardName,
                                    CreateTokenID: _this.get("rememberPayment"),
                                    CardHolderTitle: _this.get("rememberPayment") ? this.get("cardHolderTitle") : ""
                                }
                            };
                        }
                        // eway years need to be 2 digits
                        this.processPayment(opts);
                    } else {
                        if (this.get("totalPaymentBalanceAfterEnteredGiftCards") != "-1" && !this.isGiftCardBalanceZero()) {
                            submitOrderPaymentUnsuccessfulMessage = widget.options.giftCardBalanceNotZeroMessage;
                        }
                        if (!validCreditCardInformation) {
                            submitOrderPaymentUnsuccessfulMessage = submitOrderPaymentUnsuccessfulMessage.length > 0 ? submitOrderPaymentUnsuccessfulMessage + ", " + widget.options.invalidCreditCardInformation : widget.options.invalidCreditCardInformation;
                        }
                        this.set("paymentUnsuccessful", true);
                        widget.trigger(PAYMENTUNSUCCESSFUL, { message: submitOrderPaymentUnsuccessfulMessage });
                        if (!validCreditCardInformation) {
                            if (!widget.options.triggerMessages) {
                                _this.setMessage(submitOrderPaymentUnsuccessfulMessage, $.cv.css.messageTypes.error);
                            } else {
                                widget.options.triggerMessages = false;
                                _this.setMessage(submitOrderPaymentUnsuccessfulMessage, $.cv.css.messageTypes.error);
                                widget.options.triggerMessages = true;
                            }
                        } else {
                            _this.setMessage(submitOrderPaymentUnsuccessfulMessage, $.cv.css.messageTypes.error);
                        }
                        _this.set("isProcessing", false);
                    }
                },

                processPageValidationErrors: function () {
                    var widgetElement = "[data-role='{0}']", widget;
                    $.each(_.values($.cv.css.pageValidationErrors), function (idx, item) {
                        $(widgetElement.format(item.toLowerCase())).each(function () {
                            widget = $(this).data(item);
                            if (widget) {
                                if ($.isFunction(widget.validateInputFields))
                                    widget.validateInputFields(true);
                            }
                        });
                    });
                },

                processPayment: function (opts) {
                    var _this = this, processPaymentUnsuccessfulMessage = "";
                    _this.set("isProcessing", true);
                    var d1 = $.cv.css.paymentProcessor.processPayment(opts);
                    $.when(d1).done(function (msg) {
                        var data = msg.data;
                        if (!data.sessionHasTimedOut) {
                            if (opts.paymentType.indexOf(widget.options.paymentTypes.card) < 0 || (opts.paymentType.indexOf(widget.options.paymentTypes.card) >= 0 && widget.options.paymentProviderMode != PAYMENTPROVIDERMODEWAY && widget.options.paymentProviderMode != PAYMENTPROVIDERMODEWAYV3)) {
                                if (data.Status == 0) {
                                    $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrder);
                                    $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
                                    // set viewmodel to indicate success
                                    _this.set("paymentSuccessful", true);
                                    
                                    // Redirect ----
                                    // Don't use $.cv.util.redirect(), instead use internal
                                    // redirect view model method as it can be overridden. It IS
                                    // overridden in cv.mobile.ui.paymentOptions so that we don't
                                    // redirect away from SPA.
                                    if (data.IsRedirect && data.RedirectUrl != "") {
                                        _this.redirect(data.RedirectUrl, {}, widget.options.includeInBrowserHistory);
                                    } else {
                                        _this.redirect(widget.options.paymentOkRedirectUrl, {}, widget.options.includeInBrowserHistory);
                                    }
                                } else {
                                    var defaultMessageSetting = _this.get("clearExistingMessages");
                                    _this.set("paymentUnsuccessful", true);
                                    _this.set("clearExistingMessages", true);
                                    $.each(data.Messages, function (idx, item) {
                                        _this.setMessage(item, $.cv.css.messageTypes.error);
                                        if (widget.options.processPaymentUnsuccessfulMessage == "") {
                                            processPaymentUnsuccessfulMessage = processPaymentUnsuccessfulMessage.length > 0 ? processPaymentUnsuccessfulMessage + "," + item : item;
                                        }
                                    });
                                    widget.trigger(PAYMENTUNSUCCESSFUL, { message: processPaymentUnsuccessfulMessage.length > 0 ? processPaymentUnsuccessfulMessage : widget.options.processPaymentUnsuccessfulMessage });
                                    _this.set("clearExistingMessages", defaultMessageSetting);
                                    _this.set("isProcessing", false);
                                }
                            } else {
                                _this.set("isEWayProcessing", true);
                                var accessCode;
                                var postBackUrl;
                                // Get eWay information passed back.
                                for (var i in data.AdditionalResultData) {
                                    if (data.AdditionalResultData[i].Key === "eWay_AccessCode")
                                        accessCode = data.AdditionalResultData[i].Value;

                                    if (data.AdditionalResultData[i].Key === "eWay_PostbackUrl")
                                        postBackUrl = data.AdditionalResultData[i].Value;
                                }
                                if (accessCode && postBackUrl) {
                                    // Set access code field
                                    _this.set("eWayAccessCode", accessCode);

                                    // Set post back uri and submit
                                    var form = $("form:first");
                                    form.attr("action", postBackUrl);
                                    form.submit();
                                } else {
                                    _this.set("isEWayProcessing", false);
                                    _this.set("isProcessing", false);
                                }
                            }
                        } else {
                            _this.redirectToUrl(data.sessionTimeOutRedirectUrl, {}, !widget.options.includeInBrowserHistory);
                        }
                    }).fail(function (msg) {
                        _this.set("isProcessing", false);
                        if (msg.errorMessage != null)
                            _this.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                    });
                }

            });

            viewModel.bind("change", function (e) {
                if (widget.options.autoSelectCardType) {
                    if (e.field == "cardNumber" || e.field == "cardNumber1") {
                        viewModel.setCreditCardType();
                    }
                }
            });

            init();

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";

            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemTemplateId + "'>";
            html += "<div>";
            html += "";
            html += "</div></script>";
            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(paymentOptionsWidget);

})(jQuery);
;
/* Name: Freight carrier
* Author: Aidan Thomas 
* Created: 20130220
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
*          /Scripts/cv.css.freightCarrier.js
* Params:  
*           dataSource: 
*           pleaseSelectText:
*           freightOptionsTextField: 
*           freightOptionsValueField: 
*           clearExistingMessages:
*           autoBind: 
*           triggerMessages:
*           textErrorSettingFreight:
*           viewTemplate: kendo template id for the main view
*/

// TODO: Current only works with Standard Freight, Live Freight and Delivery Methods. The other methods still need to be implemented
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        FREIGHTSELECTED = "freightSelected",
        FREIGHTSELECTEDINITIALOWNCARRIER = "freightSelectedInitialOwnCarrier",
        FREIGHTSELECTEDOWNCARRIER = "freightSelectedOwnCarrier",
        PICKUPWAREHOUSESLOADED = "pickupWarehousesLoaded";

    var freightCarrierWidget = {

        // widget name
        name: "freightCarrier",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            freightOptionsTextField: "CarrierDescription",
            freightOptionsValueField: "ID",
            clearExistingMessages: true,
            isFreightCalculator: false,
            getDataOnInit: true,
            validatePostcode: false,
            // viewModel flags
            autoBind: true,
            triggerMessages: true,
            // events
            // view flags
            // view text defaults
            textErrorSettingFreight: 'There was an error setting the freight',
            textPostCodeRequired: "Please enter a postcode",
            textPostCodeInWrongFormat: "Postcode is in the wrong format",
            textNoFreightFoundDefaultMessage: "No freight could be found for your entered details",
            textNoOwnCarrierNameMessage: "Please select one of your own carriers",
            textNoOwnCarrierAccountMessage: "Please enter your Freight Account Number",
            textPleaseSelectWarehouse: "Please select...",
            freightOptionNotSelected: "Please select a freight option",
            warehouseNotSelected: "Please select a pickup location",
            // widget settings
            currentOrderPostcode: "",
            currentOrderCountry: { Code: "Australia", Description: "Australia" },
            defaultCountry: "Australia",
            availableCountries: [{ Code: "Australia", Description: "Australia" }],
            storeName: "",
            // view Template
            viewTemplate: null // Treat these as IDs, remove the last one.
        },

        events: [DATABINDING, DATABOUND, FREIGHTSELECTED, FREIGHTSELECTEDINITIALOWNCARRIER, FREIGHTSELECTEDOWNCARRIER, PICKUPWAREHOUSESLOADED],

        viewModel: null,

        view: null,

        // private property
        _viewAppended: false,

        initialise: function (el, o) {
            var widget = this;

            // Register Widget so we can determine when they are all completed loading
            $.cv.css.trigger($.cv.css.eventnames.orderCompleteLoadRegistration, { name: widget.name }); // MAF USE ONLY

            //var view = null;
            // check for an internal view
            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                widget.element.html(widget.view);
            }
            // now MMVM bind
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            $.cv.css.bind($.cv.css.eventnames.addressChanged, $.proxy(widget.viewModel.addressChanged, widget.viewModel));
            $.cv.css.bind($.cv.css.eventnames.addressBeingEdited, $.proxy(widget.viewModel.addressBeingEdited, widget.viewModel));
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        validateInputFields: function (showMessages) {
            var widget = this;
            widget.viewModel.validateInputFields(showMessages);
        },

        _getViewModel: function () {
            var widget = this;

            var initDataSource = function () {
                if (!widget.options.isFreightCalculator && !viewModel.get("showWarehousesForPickup") && !viewModel.get("showWarehousesForPickupOrIsLoading")) {
                    if (widget.options.storeName.length > 0) {
                        viewModel.getWarehousesForPickup(widget.options.storeName);
                    }
                    var d1 = $.cv.css.freightCarrier.getFreightForCurrentOrder();
                    $.when(d1).done(function (msg) {
                        viewModel.set("showFreightSelector", msg.data.ShowFreightSelector);
                        if (msg.data.ShowFreightSelector) {
                            if (msg.data.OptionGroups.length > 0) {
                                widget.options.dataSource = msg.data.OptionGroups; // [0].FreightOptions;
                                if (msg.data.OptionGroups[0].TemplateName != "" && msg.data.OptionGroups[0].TemplateName != null)
                                    viewModel.setFreightTemplateHtml(msg.data.OptionGroups[0].TemplateName);


                                // Need to remove the Store Pickup option from the normal freight options list as it would already have been shown if available in the first selection list
                                // that was shown to user where could choose if wanted to use own carriers or not. It would be confusing to have it listed in both locations  
                                var index = -1;
                                $.each(msg.data.OptionGroups[0].FreightOptions, function (idx, item) {
                                    if (item.IsStorePickup) {
                                        index = idx;
                                        viewModel.set("isStorePickupAvailable", true);
                                        viewModel.set("storePickupCarrierCode", item.CarrierCode);
                                        return false;
                                    }
                                });
                                
                                // Set if Own Carriers available, will make normal freight selection initially not visible
                                // instead first showing the choice for user if they want to use own carriers or normal freight.
                                if (msg.data.IsFreightOwnCarriersAvailable) {
                                    viewModel.set("isFreightOwnCarriersAvailable", true);
                                    viewModel.set("isFreightNormalCarriersVisisble", false);

                                    if (index > -1)
                                        msg.data.OptionGroups[0].FreightOptions.splice(index, 1);
                                } else {
                                    // If only have one freight option and it is store pickup (i.e. not a real carrier) this means no real freight found.
                                    // So if there is also the config to allow feright quote submission then make this visible from the get go.
                                    if (viewModel.get("isProntoFreightQuoteEnabled")
                                        && (msg.data.OptionGroups.length == 0 || msg.data.OptionGroups[0].FreightOptions.length == 0
                                            || (msg.data.OptionGroups[0].FreightOptions.length == 1 && viewModel.get("isStorePickupAvailable")))) {
                                        viewModel.set("isFreightQuoteInstructsVisisble", true);
                                    }
                                }
                            } else
                                widget.options.dataSource = [];
                        } else {
                            widget.options.dataSource = [];
                        }
                        setDataSource();
                        viewModel.set("isInitialLoad", false);
                        widget.options.storeName = "";
                    });
                }
            };

            // returns the carrier code from the current order
            var getSoCarrierCode = function () {
                var order = $.cv.css.localGetCurrentOrder();
                if (order != null) {
                    if (order.CarrierCode)
                        return order.CarrierCode;
                    else
                        return '';
                } else {
                    return '';
                }
            };

            // returns the OwnCarrierName from the current order
            var getOwnCarrierName = function () {
                var order = $.cv.css.localGetCurrentOrder();
                if (order != null) {
                    if (order.OwnCarrierName)
                        return order.OwnCarrierName;
                    else
                        return '';
                } else {
                    return '';
                }
            };

            // returns the OwnCarrierNameOwnCarrierAccount  from the current order
            var getOwnCarrierAccount = function () {
                var order = $.cv.css.localGetCurrentOrder();
                if (order != null) {
                    if (order.OwnCarrierAccount)
                        return order.OwnCarrierAccount;
                    else
                        return '';
                } else {
                    return '';
                }
            };

            var setDataSource = function () {
                widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

                if (widget.options.autoBind)
                    widget.dataSource.fetch();

                viewModel.updateItemList();
                viewModel.updateItemListInitialOwnCarriers();
                viewModel.updateItemListOwnCarriers();

                // check the carrier code on the current order, if populated update the view model to use this value
                var soCarrierCode = getSoCarrierCode();
                var dSourceView = widget.dataSource.view();

                // If ShowFreightSelector was false in the data that came back from $.cv.css.freightCarrier.getFreightForCurrentOrder() in initDataSource()
                // then there won't be any FreightOptions on the widget.dataSource.view() at this point so need to check!
                if (dSourceView && dSourceView.length > 0 && !viewModel.get("showWarehousesForPickup") && !viewModel.get("showWarehousesForPickupOrIsLoading")) {
                    var result = $.grep(dSourceView[0].FreightOptions, function (e) { return e.CarrierCode == soCarrierCode; });
                    if (result.length > 0) {
                        if (viewModel.get("currentFreightID") == result[0].ID && !viewModel.get("isInitialLoad")) {
                            viewModel.freightSelected();
                            viewModel.validateInputFields();
                        } else {
                            viewModel.set("currentFreightID", result[0].ID);
                        }

                        // Handle own carriers options preselection that normal carriers selected
                        if (!result[0].IsStorePickup) {
                            viewModel.set("currentFreightUseOwnCarrier", "UseNormalFreightCarriers");
                            viewModel.freightSelectedInitialOwnCarrier();
                        }
                    } else {
                        _selectMinimFreight(true);
                    }
                }

                //
                // Handle own carriers options preselection
                //

                // If StorePickup chosen set viewmodel that this option chosen
                if(soCarrierCode.length > 0 && soCarrierCode == viewModel.get("storePickupCarrierCode")) {
                    var isAlreadySP = viewModel.get("currentFreightUseOwnCarrier") == soCarrierCode; // store if current value is the same
                    viewModel.set("currentFreightUseOwnCarrier", soCarrierCode); // if it was different the binding event on this viewmodel property will trigger the change event

                    // if it was the same then it won't have got triggered so do it manually as must ensure it is called, otherwise the freight may not have been set.
                    if (isAlreadySP)
                        viewModel.freightSelectedInitialOwnCarrier();
                }
                
                // If have an own carrier chosen set viewmodel that this option chosen
                var ownCarrierName = getOwnCarrierName();

                if (ownCarrierName.length > 0) {
                    var isAlreadyOC = viewModel.get("currentFreightUseOwnCarrier") == "UseFreightOwnCarriers"; // store if current value is the same
                    viewModel.set("currentFreightUseOwnCarrier", "UseFreightOwnCarriers");  // if it was different the binding event on this viewmodel property will trigger the change event

                    // if it was the same then it won't have got triggered so do it manually as must ensure it is called, otherwise may not show correct options
                    if (isAlreadyOC)
                        viewModel.freightSelectedInitialOwnCarrier();
                }
            };

            var _selectMinimFreight = function (triggerFreightSelected) {
                if (!widget.dataSource || viewModel.get("showWarehousesForPickup") || viewModel.get("showWarehousesForPickupOrIsLoading"))
                    return [];
                var array = [],
                    minAmount = 0,
                    minIndex = 0,
                    dview = widget.dataSource.view();
                triggerFreightSelected = typeof triggerFreightSelected !== 'undefined' ? triggerFreightSelected : false;
                if (dview.length > 0 && dview[0].FreightOptions) {
                    var nonOwnCarriersOptionGrp = dview[0].FreightOptions; // Always first group will not be the one not for Own Carriers
                    if (nonOwnCarriersOptionGrp.length > 0)
                        minAmount = nonOwnCarriersOptionGrp[0].AmountIncTax;
                    $.each(nonOwnCarriersOptionGrp, function (idx, item) {
                        // add standard commands
                        item.Index = idx;
                        if (item.AmountIncTax < minAmount) {
                            minAmount = item.AmountIncTax;
                            minIndex = idx;
                        }
                        array.push(item);
                    });
                }

                // Don't do an initial call to set freight when can choose from an own carrier
                var isOwnCarriersAvailable = viewModel.get("isFreightOwnCarriersAvailable");
                if (!isOwnCarriersAvailable && array.length > 0 && viewModel.get("currentFreightID") == '' && widget.options.storeName.length == 0) {
                    viewModel.set("currentFreightID", array[minIndex].ID);
                } else if (!isOwnCarriersAvailable && array.length > 0 && triggerFreightSelected && viewModel.get("currentFreightID") != '' && viewModel.get("currentFreightID") == array[minIndex].ID) {
                    viewModel.freightSelected();
                    viewModel.validateInputFields();
                }
            };

            var getDataView = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];
                var array = [];
                
                var dview = widget.dataSource.view();
                if (dview.length > 0 && dview[0].FreightOptions) {
                    var nonOwnCarriersOptionGrp = dview[0].FreightOptions; // Always first group will not be the one not for Own Carriers
                    array = nonOwnCarriersOptionGrp;
                }
                _selectMinimFreight();
                return array;
            };

            var getDataViewUseOwnCarriers = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];

                if (viewModel.get("isFreightOwnCarriersAvailable") == false)
                    return [];

                var array = [];
                // Iterate the OptionGroups, the data source should be list of them, and find the Own Carriers one (should be not the first if present)
                var dview = widget.dataSource.view();
                $.each(dview, function (idx, item) {
                    if (item.Name == "FreightOwnCarrier") {
                        $.each(item.FreightOptionsUseOwnCariers, function (idx2, uocItem) {
                            array.push(uocItem);
                        });
                        return false;
                    }
                });

                return array;
            };

            var getDataViewOwnCarriers = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];

                if (viewModel.get("isFreightOwnCarriersAvailable") == false)
                    return [];

                var array = [];
                // Iterate the OptionGroups, the data source should be list of them, and find the Own Carriers one (should be not the first if present)
                var dview = widget.dataSource.view();
                $.each(dview, function (idx, item) {
                    if (item.Name == "FreightOwnCarrier") {
                        $.each(item.FreightOptions, function (idx2, ocItem) {
                            array.push(ocItem);
                        });
                        return false;
                    }
                });

                return array;
            };
            var viewModel = kendo.observable({

                // Properties for UI elements
                dataSource: widget.options.dataSource,

                message: '',

                currentFreightID: '',
                previousFreightID: '',
                currentFreightUseOwnCarrier: '',
                currentFreightOwnCarrierName: getOwnCarrierName(),
                currentFreightOwnCarrierAccount: getOwnCarrierAccount(),
                currentCarrierCode: getSoCarrierCode(),
                storePickupCarrierCode: '',

                freightTemplateHtml: "",

                freightOptionTemplateHtml: "",

                clearExistingMessages: widget.options.clearExistingMessages,

                calculatorPostcode: widget.options.currentOrderPostcode,

                calculatorCountryCode: widget.options.currentOrderCountry != null ? widget.options.currentOrderCountry.Code : '',

                calculatorCountryDescription: widget.options.currentOrderCountry != null ? widget.options.currentOrderCountry.Description : '',

                calculatorAvailableCountries: widget.options.availableCountries,
                
                isProntoFreightQuoteEnabled: widget.options.isProntoFreightQuoteEnabled != null ? widget.options.isProntoFreightQuoteEnabled : false,

                storeLocationDataLoaded: false,
                storeLocationData: [{ StoreName: widget.options.textPleaseSelectWarehouse, WarehouseCode: "" }],
                showWarehousesForPickup: false,
                showWarehousesForPickupOrIsLoading: false,
                warehouseLoadedFromOrder: false,
                selectedWarehouse: null,
                selectedWarehouseValue: function () {
                    var value = "", selectedWarehouse = this.get("selectedWarehouse");
                    if (selectedWarehouse) {
                        // whne bound to a kendo drop down list
                        if (typeof selectedWarehouse == "object") {
                            value = selectedWarehouse.WarehouseCode;
                        } else {
                            value = selectedWarehouse;
                        }
                    }
                    return value;
                },

                // UI Element state

                isInitialLoad: true,

                hasFreightOptions: false,
                hasFreightOptionsOrCanSubmitForFreightQuote: false, // used for when binding visibility in case no freight options available but can still submit for freight quote.

                isFreightOwnCarriersAvailable: false,
                isFreightUseOwnCarriersVisisble: false, // initially don't show
                isFreightNormalCarriersVisisble: true, // initially default to showing if don't have any own carriers.
                isFreightNormalCarriersLabelVisisble: false, // Only show this with normal freight option choice when have own carriers available but choose to use normal freight.
                isFreightQuoteInstructsVisisble: false, // Sets to true when not have any freight and can submit for freight quote instead to show msg about this.
                isFreightQuoteSelected: false, // Gets set if user chooses wish to submit as a quote to get freight quote.
                isStorePickupAvailable: false,

                isSingleFreightOption: false,

                showFreightSelector: false,

                isProcessing: false,

                isLoadingWarehouses: false,

                // functions for UI events

                clearMessage: function () {
                    var clearExistingMessages = this.get("clearExistingMessages");
                    this.set("clearExistingMessages", true);
                    this.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'freightCarrier', clearExisting: this.get("clearExistingMessages") });
                    this.set("clearExistingMessages", clearExistingMessages);
                },

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                updateItemList: function () {
                    var dataView = getDataView();
                    this.set("itemList", dataView);
                    this.set("hasFreightOptions", dataView.length > 0);
                    this.set("hasFreightOptionsOrCanSubmitForFreightQuote", dataView.length > 0 || this.get("isProntoFreightQuoteEnabled"));
                    this.set("isSingleFreightOption", dataView.length == 1);
                },

                updateItemListInitialOwnCarriers: function () {
                    var dataView = getDataViewUseOwnCarriers();
                    this.set("itemListInitialOwnCarriers", dataView);
                    this.set("isFreightOwnCarriersAvailable", dataView.length > 0);
                },

                updateItemListOwnCarriers: function () {
                    var dataView = getDataViewOwnCarriers();
                    this.set("itemListOwnCarriers", dataView);
                },

                // Holds the list of normal freight ptions to choose from, i.e. not own carriers
                itemList: getDataView(),

                // Used for list where can select if want to use Own Carriers or go with normal freight on order
                itemListInitialOwnCarriers: getDataViewUseOwnCarriers(),

                // Has the actaul list of Own Carriers available
                itemListOwnCarriers: getDataViewOwnCarriers(),

                addressChanged: function () {
                    this.set("isAddressBeingEdited", false);
                    initDataSource();
                },

                isAddressBeingEdited: false,
                addressBeingEdited: function () {
                    this.set("isAddressBeingEdited", true);
                    $.cv.css.addRemovePageValidationError(false, widget.name);
                },

                setFreightTemplateHtml: function (templateName) {
                    var _this = this;
                    if (templateName != "" && templateName != null) {
                        var d1 = $.cv.css.getParsedTemplate({ templateName: templateName });
                        $.when(d1).done(function (msg) {
                            var data = msg.data;
                            _this.set("freightTemplateHtml", data);
                        }).fail(function () {

                        });
                    } else {
                        _this.set("freightOptionTemplateHtml", "");
                    }
                },

                freightSelected: function () {
                    var _this = viewModel;
                    var currentFreightID = this.get("currentFreightID");
                    if ((currentFreightID != undefined && currentFreightID != "") || currentFreightID == 0) {
                        var option = $.grep(this.get("itemList"), function (item, idx) { return (item.ID == currentFreightID); });
                        if (option.length > 0) {
                            // check if a template name exists for the current selected freight option, parse and insert the template, otherwise empty the freight option HTML
                            if (option[0].TemplateName != "" && option[0].TemplateName != null) {
                                var d1 = $.cv.css.getParsedTemplate({ templateName: option[0].TemplateName });
                                $.when(d1).done(function (msg) {
                                    var data = msg.data;
                                    _this.set("freightOptionTemplateHtml", data);
                                }).fail(function () {

                                });
                            } else {
                                _this.set("freightOptionTemplateHtml", "");
                            }
                        }

                        this.setFreight();
                    }
                },

                freightSelectedInitialOwnCarrier: function () {
                    var _this = viewModel;
                    var currFrghtUseOwnCarrier = _this.get("currentFreightUseOwnCarrier");
                    if ((currFrghtUseOwnCarrier != undefined && currFrghtUseOwnCarrier != "")) {
                        var option = $.grep(_this.get("itemListInitialOwnCarriers"), function (item, idx) { return (item.CarrierCode == currFrghtUseOwnCarrier); });
                        if (option.length > 0) {
                            var currentIsFghtQuoteSel = _this.get("isFreightQuoteSelected");
                            
                            if (option[0].IsStorePickup) {
                                _this.set("isFreightNormalCarriersVisisble", false);
                                _this.set("isFreightUseOwnCarriersVisisble", false);
                                _this.set("isFreightNormalCarriersLabelVisisble", false);
                                // Clear these as will now have store pickup set
                                _this.set("currentFreightOwnCarrierName", '');
                                _this.set("currentFreightOwnCarrierAccount", '');
                                _this.set("isFreightQuoteInstructsVisisble", false);

                                // Change to not set if was previously set
                                if(currentIsFghtQuoteSel) {
                                    _this.set("isFreightQuoteSelected",false);
                                }

                                // Let it be known what selection state is: Show the payment options
                                $.cv.css.trigger($.cv.css.eventnames.freightQuoteInstructsVisisble, { isVisible: false });
                                $.cv.css.trigger($.cv.css.eventnames.freightQuoteSelected, { isSelected: false });
                                
                                // Handle setting freight for pickup
                                this.set("currentFreightID", option[0].ID); // this will trigger setFreight()
                            }
                            else if (option[0].CarrierCode == "UseNormalFreightCarriers") {
                                // Handle making normal freight selection now visible
                                this.set("isFreightUseOwnCarriersVisisble", false);
                                // Clear these as will not now set
                                _this.set("currentFreightOwnCarrierName", '');
                                _this.set("currentFreightOwnCarrierAccount", '');

                                if (!_this.get("hasFreightOptions") && _this.get("isProntoFreightQuoteEnabled")) {
                                    _this.set("isFreightQuoteInstructsVisisble", true);

                                    // Let it be known what selection state is: Hide the payment options
                                    $.cv.css.trigger($.cv.css.eventnames.freightQuoteInstructsVisisble, { isVisible: true });
                                    $.cv.css.trigger($.cv.css.eventnames.freightQuoteSelected, { isSelected: false });
                                } else {
                                    _this.set("isFreightNormalCarriersVisisble", true);
                                    _this.set("isFreightNormalCarriersLabelVisisble", true);
                                }

                            }
                            else if (option[0].CarrierCode == "UseFreightOwnCarriers") {
                                // Handle making own freightcarriers actual list of carriers now visible for selection 
                                _this.set("isFreightUseOwnCarriersVisisble", true);
                                _this.set("isFreightNormalCarriersVisisble", false);
                                _this.set("isFreightNormalCarriersLabelVisisble", false);
                                _this.set("isFreightQuoteInstructsVisisble", false);
                                     
                                // Change to not set if was previously set
                                if (currentIsFghtQuoteSel) {
                                    _this.set("isFreightQuoteSelected", false);
                                }

                                // Let it be known what selection state is: Show the payment options
                                $.cv.css.trigger($.cv.css.eventnames.freightQuoteInstructsVisisble, { isVisible: false });
                                $.cv.css.trigger($.cv.css.eventnames.freightQuoteSelected, { isSelected: false });
                            }
                        }
                    }
                },
                
                setFreightQuoteSelected: function () {

                    var _this = viewModel;

                    // Need this hack for firefox to give a delay so that whenthis chekced function is called, the view model has chance to be updated otherwise gets opposite value.
                    window.setTimeout(_this.setFreightQuoteSelectedDoWork, 10);
                },

                setFreightQuoteSelectedDoWork: function () {

                    var _this = viewModel;
                    var isFghtQuoteSel = _this.get("isFreightQuoteSelected");

                    if (isFghtQuoteSel) {
                        var currFrghtId = _this.get("currentFreightID");
                        _this.set("previousFreightID", currFrghtId); // Store temp in case need to restore if unselect quote option
                        _this.set("currentFreightID", 0); // this will trigger setFreight() and clear.
                        _this.set("isFreightNormalCarriersVisisble", false); // Hide the Normal Carrier options if visible
                        _this.set("isFreightNormalCarriersLabelVisisble", false);
                    } else {
                        if (_this.get("hasFreightOptions") && !_this.get("isFreightOwnCarriersAvailable")) {
                            var prevFrghtId = _this.get("previousFreightID");
                            _this.set("currentFreightID", prevFrghtId); // Restore prev slection this will trigger setFreight()
                            _this.set("isFreightNormalCarriersVisisble", true); // make visible again
                            _this.set("isFreightNormalCarriersLabelVisisble", true);
                        }
                    }

                    // Let it be known what slection state is
                    $.cv.css.trigger($.cv.css.eventnames.freightQuoteSelected, { isSelected: isFghtQuoteSel });
                },


                setFreightOwnCarrier: function () {
                    var fw = $("#freight-wrapper");
                    var validator = fw.data("kendoValidator");

                    if (!validator && fw.kendoValidator) {
                        fw.kendoValidator(widget.options.validationOptions);
                        validator = fw.data("kendoValidator");
                    }

                    // Validate: Return if not valid so user forced to enter valid values.
                    if (validator && !validator.validate()) {
                        return;
                    }

                    var _this = viewModel;
                    var ownCarrierName = this.get("currentFreightOwnCarrierName");
                    var ownCarrierAccount = this.get("currentFreightOwnCarrierAccount");

                    if ((ownCarrierName == undefined || ownCarrierName == "")) {
                        _this.setMessage(widget.options.textNoOwnCarrierNameMessage, $.cv.css.messageTypes.error);
                        return;
                    }

                    if ((ownCarrierAccount == undefined || ownCarrierAccount == "")) {
                        _this.setMessage(widget.options.textNoOwnCarrierAccountMessage, $.cv.css.messageTypes.error);
                        return;
                    }

                    var option = $.grep(this.get("itemListOwnCarriers"), function (item, idx) { return (item.CarrierCode == ownCarrierName); });
                    if (option.length > 0) {
                        // Handle setting freight using selected own carrier, will need to set the currentFreightId for passing into to dyn serv call
                        this.set("currentFreightID", option[0].ID); // this will trigger setFreight()
                    }
                },

                setFreight: function () {
                    var _this = this;

                    _this.clearMessage();

                    var freightOptionIDs = [];
                    var currFrghtId = this.get("currentFreightID");

                    if (currFrghtId != null && currFrghtId !== "" && currFrghtId !== '' && currFrghtId >= 0 && !_this.get("isFreightQuoteSelected"))
                        freightOptionIDs.push(this.get("currentFreightID"));
                    
                    // clear out any empty strings
                    freightOptionIDs = $.grep(freightOptionIDs, function (data) {
                        return (data !== null && data !== "" && data !== '');
                    });
                    var warehouseCode = _this.get("showWarehousesForPickup") || _this.get("isLoadingWarehouses") ? _this.selectedWarehouseValue() : "";
                    var ownCarrierAccount = _this.get("currentFreightOwnCarrierAccount");

                    if (freightOptionIDs.length >= 0 || warehouseCode) {
                        _this.set("isProcessing", true);
                        var d1 = $.cv.css.freightCarrier.setFreightForCurrentOrder({ freightOptionIDs: freightOptionIDs, warehouseCode: warehouseCode, ownCarrierAccount: ownCarrierAccount });
                        $.when(d1).done(function (msg) {
                            _this.set("isProcessing", false);
                            var data = msg.data;
                            if (data.result) {
                                //$.cv.css.getCurrentOrder(); // reload the current order after successfully setting the freight
                                widget.trigger(FREIGHTSELECTED);
                            } else
                                _this.setMessage(data.errorMessage, $.cv.css.messageTypes.error);
                        }).fail(function () {
                            _this.set("isProcessing", false);
                            _this.setMessage(widget.options.textErrorSettingFreight, $.cv.css.messageTypes.error);
                        });
                    }
                },

                estimateFreightOnEnter: function(event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();
                        this.calculateFreight();
                    }
                },

                calculateFreight: function () {
                    var _this = this, opts = {};
                    _this.clearMessage();
                    widget.options.dataSource = [];
                    setDataSource();
                    if (_this.get("calculatorPostcode") == "") {
                        _this.setMessage(widget.options.textPostCodeRequired, $.cv.css.messageTypes.error);
                    } else {
                        if (widget.options.validatePostcode && !$.cv.util.testRegEx(/^\d{4}$/, _this.get("calculatorPostcode"), false)) {
                            _this.setMessage(widget.options.textPostCodeInWrongFormat, $.cv.css.messageTypes.error);
                            return;
                        }
                        opts.postCode = _this.get("calculatorPostcode");
                        if (_this.get("calculatorCountryCode") != "")
                            opts.countryCode = _this.get("calculatorCountryCode");
                        _this.set("isProcessing", true);
                        var d1 = $.cv.css.freightCarrier.getFreightEstimateForCurrentOrder(opts);
                        $.when(d1).done(function (msg) {
                            _this.set("isProcessing", false);
                            if (msg.data.OptionGroups.length > 0) {
                                widget.options.dataSource = msg.data.OptionGroups; // [0].FreightOptions; Now using all freight groups as datasource, widget has been adjusted to cater for this.
                            }
                            else {
                                widget.options.dataSource = [];
                                _this.setMessage(widget.options.textNoFreightFoundDefaultMessage, $.cv.css.messageTypes.error);
                            }
                            setDataSource();
                        });
                    }
                },

                // A warehouse is selected from the drop-down list.
                pickupWarehouseChanged: function (e) {
                    this.setWarehouse();
                },

                flagSelectedWarehouse: function () {
                    var _this = this, selectedWarehouse = _this.selectedWarehouseValue();
                    $.each(_this.get("storeLocationData"), function (index, item) {
                        if (this.WarehouseCode && this.WarehouseCode == selectedWarehouse) {
                            item.set("isSelected", true)
                        } else {
                            item.set("isSelected", false)
                        }
                    });
                },

                setWarehouse: function () {
                    this.setFreight();
                    $.cv.css.trigger($.cv.css.eventnames.pickupWarehouseChanged, { warehouse: this.selectedWarehouseValue() });
                },

                validateInputFields: function (showMessages) {
                    // ensure you can't checkout without selecting a freight option or pickup warehouse
                  //  $.cv.util.clearMessage.apply(widget.viewModel);

                    // Clear messages
                    $.cv.util.clearNotifications(widget.viewModel);

                    if (this.get("isAddressBeingEdited")) {
                        freightCarrierValid = false;
                    } else {

                        var freightCarrierValid = true, errorMessage = "";
                        if (!this.get("showWarehousesForPickup") && !this.get("showWarehousesForPickupOrIsLoading") && this.get("showFreightSelector") && !(this.get("currentFreightID") != null && this.get("currentFreightID").toString() != "")) {
                            freightCarrierValid = false;
                            errorMessage = widget.options.freightOptionNotSelected;
                        }
                        if (this.get("showWarehousesForPickup") && this.get("selectedWarehouse") == null) {
                            freightCarrierValid = false;
                            errorMessage = widget.options.warehouseNotSelected;
                        }
                        if (!this.get("isInitialLoad") && errorMessage.length > 0) {
                            this.setMessage(errorMessage, $.cv.css.messageTypes.error);
                        }
                    }
                    $.cv.css.addRemovePageValidationError(freightCarrierValid, widget.name);
                    $.cv.css.trigger($.cv.css.eventnames.freightValidated);
                },

                getWarehousesForPickup: function (storeName) {
                    var vm = this;
                    // remove any freight that may have been previosuly selected
                    vm.set("isInitialLoad", true);
                    vm.set("showWarehousesForPickupOrIsLoading", true);
                    if (vm.get("storeLocationDataLoaded") == false) {
                        // Load store locations.
                        vm.set("isLoadingWarehouses", true);
                        var p = $.cv.css.storeLocator.getWarehousesForPickup();
                        $.when(p).done(function (data) {
                            $.each(data.data, function (idx, item) {
                                item.selectWarehouse = function () {
                                    vm.set("selectedWarehouse", item);
                                };
                            });
                            vm.set("storeLocationData", $.merge([{ StoreName: widget.options.textPleaseSelectWarehouse, WarehouseCode: "" }], data.data));
                            if (storeName.length > 0) {
                                $.each(data.data, function (idx, item) {
                                    vm.set("warehouseLoadedFromOrder", true);
                                    if (item.StoreName == storeName) {
                                        vm.set("selectedWarehouse", item);
                                    }
                                    vm.set("warehouseLoadedFromOrder", false);
                                });
                            }
                            if (vm.get("selectedWarehouse") == null) {
                                $.each(data.data, function (idx, item) {
                                    if (item.IsDefaultWarehouse) {
                                        vm.set("selectedWarehouse", item);
                                    }
                                });
                            }

                            vm.set("storeLocationDataLoaded", true);
                            widget.trigger(PICKUPWAREHOUSESLOADED);
                            // Show warehouse dropdown list.
                            vm.set("showWarehousesForPickup", true);
                            vm.set("isLoadingWarehouses", false);
                            vm.set("currentFreightID", "");
                            vm.validateInputFields();
                            vm.set("isInitialLoad", false);
                        });
                    } else {
                        vm.set("showWarehousesForPickup", true);
                        vm.set("currentFreightID", "");
                        if (vm.get("selectedWarehouse") != null) {
                            vm.pickupWarehouseChanged();
                        }
                        vm.validateInputFields();
                        vm.set("isInitialLoad", false);
                    }
                }
            });

            // "Delivery address mode changed" event.
            $.cv.css.bind($.cv.css.eventnames.deliveryAddressModeChanged, function (msg) {
                switch (msg.message) {
                    case "Delivery":
                        // Hide warehouse dropdown list.
                        viewModel.set("showWarehousesForPickup", false);
                        viewModel.set("showWarehousesForPickupOrIsLoading", false);
                        viewModel.set("isInitialLoad", true);
                        break;

                    case "Pickup":
                        viewModel.getWarehousesForPickup("");
                        break;
                }
            }),

            viewModel.bind("change", function (e) {
                if (e.field == "currentFreightID" && !widget.options.isFreightCalculator && !viewModel.get("showWarehousesForPickup") && !viewModel.get("showWarehousesForPickupOrIsLoading")) {
                    viewModel.freightSelected();
                    viewModel.validateInputFields();
                }
                else if (e.field == "currentFreightUseOwnCarrier" && !widget.options.isFreightCalculator) {
                    viewModel.freightSelectedInitialOwnCarrier();
                }
                if (e.field == "selectedWarehouse") {
                    $.cv.css.localSetcurrentPickupAddress(viewModel.get("selectedWarehouse"));
                    viewModel.flagSelectedWarehouse();
                    if (!viewModel.get("warehouseLoadedFromOrder")) {
                        viewModel.pickupWarehouseChanged();
                    }
                    viewModel.validateInputFields();
                }
            });

            if (widget.options.getDataOnInit)
                initDataSource();

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var self = this;
            var html =
                    "<div id='freight-wrapper'>" +
					    "<!-- If have functionality configured choose if wish to use own carriers instead, or just use normal freight, also Store Pickup if normally available -->" +
			            "<select data-bind='visible:isFreightOwnCarriersAvailable, source:itemListInitialOwnCarriers, value: currentFreightUseOwnCarrier' data-text-field='CarrierDescription' data-value-field='CarrierCode'>&nbsp;</select>" +
						"<!-- If Chose want to use own carriers instead of normal freight, now choose an actual own carrier from those available -->" +
						"<div id='divFreightOwnCarriersSelection' data-bind='visible:isFreightUseOwnCarriersVisisble'>" +
							"<label><span><br /><br />Select from your list of own Carriers<br /></span></label>" +
			                "<select data-bind=' source:itemListOwnCarriers, value: currentFreightOwnCarrierName' data-text-field='CarrierDescription' required data-required-msg='Required' validationMessage='Your own Carrier selection is required'  data-value-field='CarrierCode'>&nbsp;</select>" +
							"<label><span><br /><br />Your Freight Account No.</span></label>" +
							"<input maxlength='25' type='text' required data-required-msg='Required' validationMessage='Your own Freight Account No. is required' data-bind='value: currentFreightOwnCarrierAccount'>" +
                            "<a href='javascript:$.noop()' id='button-save-freight-own-carrier' data-bind='click: setFreightOwnCarrier'>Update</a>" +
						"</div>" +
						"<!-- Choose from normal freight options available -->" +
						"<label data-bind='visible:isFreightNormalCarriersLabelVisisble'><span><br /><br />Select freight on order<br /></span></label>" +
			            "<select data-bind='visible:isFreightNormalCarriersVisisble, source: itemList, value: currentFreightID' data-text-field='CarrierDescription' data-value-field='ID'>&nbsp;</select>" +
                        "</ul>" +
                   "</div>";

            return html;
        }

    };

    $.cv.ui.widget(freightCarrierWidget);

})(jQuery);
;
/* Name: promotional code
* Author: Aidan Thomas
* Created: 20130418 
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
*          /Scripts/cv.css.orders.js
* Params:  
*           dataSource: 
*           autoBind: 
*           listUpdated: event to call once the list of promotion codes applied to the order has been updated
*           triggerMessages: triggers messages so that can be picked up by the messages widget (it will still set the widget internal message)
*           clearWidgetMessages: determines if global messages fired off by this widget should be cleared before displaying the new messages
*           textApplyPromotionCodeFailed: message displayed when the apply promo code dynamic service fails
*           textPromotionCodeSuccessfullyRemoved: message displayed when a promotion code has successfully been removed
*           textRemovePromotionCodeFailed: message displayed when the remove promo code dynamic service fails
*           textApplyPromotionalCodePrompt: text that appears on the apply button in the default view template
*           textRemovePromotionalCodePrompt: text that appears on the remove button in the default view template
*           viewTemplate: kendo template id for the main view
*           itemViewTemplate: kendo template id for each item
*/

;

(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        LISTUPDATED = "listUpdated",
        LOCATIONNONE = "None",
        LOCATIONTOP = "Top",
        LOCATIONBOTTOM = "Bottom",
        LOCATIONTOPANDBOTTOM = "TopAndBottom";


    var promotionalCodesWidget = {

        // Standard Variables

        // widget name
        name: "promotionalCodes",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            promoCodeFailedMessageType: $.cv.css.messageTypes.error,
            // viewModel flags
            autoBind: true,
            // events
            listUpdated: '',
            // view flags
            triggerMessages: true,
            clearWidgetMessages: true,
            // view text defaults
            textApplyPromotionCodeFailed: 'Failed to apply the promotion code',
            textPromotionCodeSuccessfullyRemoved: 'Promotion code successfully removed',
            textRemovePromotionCodeFailed: 'Failed to remove the promotion code',
            textApplyPromotionalCodePrompt: 'Apply',
            textRemovePromotionalCodePrompt: 'Remove',
            textPromotionCodeNowValid: 'Promotion code: {0} ({1}) now valid',
            textPromotionCodeNowInValid: 'Promotion code: {0} ({1}) now invalid',
            // widget settings
            promotionalCodeEntryLocation: "Top",
            // view Template
            viewTemplate: null, // TODO: Treat these as IDs, remove the last one.
            itemViewTemplate: null
        },

        events: [DATABINDING, DATABOUND, LISTUPDATED],

        viewModel: null,

        view: null,

        // MVVM Support

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup grid view
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (widget._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            widget.trigger(DATABOUND);
            $.cv.css.bind($.cv.css.eventnames.orderChanged, $.proxy(widget.viewModel.orderUpdated, widget.viewModel));
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            // initialise the datasource
            var initDataSource = function () {
                if (widget.options.dataSource.length == 0) {
                    var d1 = $.Deferred();
                    if (viewModel.get("order") == null) {
                        // if no order in set on the view model call the get order dynamic service (the order on the view model defaults to what is is storage)
                        d1 = $.cv.css.getCurrentOrder();
                    } else {
                        d1.resolve();
                    }
                    $.when(d1).done(function () {
                        var order = $.cv.css.localGetCurrentOrder();
                        // store the order into the view model
                        viewModel.set('order', order);

                        // Set the view model datasource to the array of promotional codes on the order
                        widget.options.dataSource = order.PromotionalCodes;
                        
                        if (widget.options.currentDataSource != undefined) {
                            // see if the status of any of the promotional codes on the order has changed now the order has updated, display message accordingly
                            $.each(widget.options.dataSource, function (idx, item) {
                                $.each(widget.options.currentDataSource, function (idx2, item2) {
                                    if (item.PromotionCode == item2.PromotionCode && item.IsValidOnCurrentOrder != item2.IsValidOnCurrentOrder) {
                                        if(item.IsValidOnCurrentOrder)
                                            viewModel.setMessage(widget.options.textPromotionCodeNowValid.format(item.PromotionCode, item.Description), $.cv.css.messageTypes.success);
                                        else
                                            viewModel.setMessage(widget.options.textPromotionCodeNowInValid.format(item.PromotionCode, item.Description), $.cv.css.messageTypes.error);
                                    }
                                });
                            });
                        }
                        widget.options.currentDataSource = widget.options.dataSource;
                        setDataSource();
                    });
                }
                else
                    setDataSource();
            }

            var setDataSource = function () {
                // create kendo data source
                widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

                if (widget.options.autoBind) {
                    widget.dataSource.fetch();
                }
                // update the list of promotion codes on the view model
                viewModel.updateItemList();
            }

            var getDataView = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];
                var array = [];
                $.each(widget.dataSource.view(), function (idx, item) {
                    // add standard commands
                    item.Index = idx;
                    item.isRemoving = false;
                    // create a destroy command on each promotion code in the list of promotion codes on the order, this will allow removal of individual promotion codes
                    item.execCommand_destroy = function () {
                        item.set("isRemoving", true);
                        // remove the promo code
                        var d1 = $.cv.css.orders.removePromoCode({ _objectKey: viewModel.get("order")._objectKey, promoCode: item.PromotionCode });
                        $.when(d1).done(function (data) {
                            // remove the promo code from the kendo datasource
                            widget.dataSource.remove(item);
                            $.cv.css.trigger($.cv.css.eventnames.promoCodeRemoved, { promoCode: item.PromotionCode });
                            // reload the order
                            d2 = $.cv.css.getCurrentOrder();
                            $.when(d2).done(function (data) {
                                viewModel.clearMessage();
                                viewModel.setMessage(widget.options.textPromotionCodeSuccessfullyRemoved, $.cv.css.messageTypes.success);
                                // trigger the order changed event so widgets can pick up the change, including this widget
                                $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                                item.set("isRemoving", false);
                            });
                            //this.parent().parent().updateItemList();
                        }).fail(function (msg) {
                            //msg = JSON.parse(msg.errorMesage);
                            viewModel.clearMessage();
                            viewModel.setMessage(widget.options.textRemovePromotionCodeFailed, $.cv.css.messageTypes.error);
                            item.set("isRemoving", false);
                        });
                    }
                    array.push(item);
                });
                return array;
            }

            var viewModel = kendo.observable({

                // Properties for UI elements
                order: $.cv.css.localGetCurrentOrder(),

                promotionCode: '',

                usePromotionCodes: widget.options.promotionalCodeEntryLocation == LOCATIONNONE ? false : true,

                dataSource: widget.options.dataSource,

                message: '',

                clearWidgetMessages: widget.options.clearWidgetMessages,

                promotionalCodesExist: false,

                isAdding: false,

                showMessages: function (messages, type) {
                    var _this = this;
                    var msgs = messages;
                    _this.set("clearWidgetMessages", false);
                    $.each(msgs, function (idx, msg) {
                        if (msg != "")
                            _this.setMessage(msg, type);
                    });
                    _this.set("clearWidgetMessages", widget.options.clearWidgetMessages);
                },

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name,
                        clearExisting: widget.options.clearWidgetMessages
                    });
                },

                clearMessage: function () {
                    this.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'promotionalCodes', clearExisting: true });
                },

                orderUpdated: function () {
                    //widget.options.currentDataSource = widget.options.dataSource;
                    widget.options.dataSource = [];
                    initDataSource();
                },

                updateItemList: function () {
                    var dataView = getDataView();
                    this.set("itemList", dataView);
                    this.set("promotionalCodesExist", dataView.length > 0);
                    widget.trigger(LISTUPDATED, { count: dataView.length });
                },

                itemList: getDataView(),

                promoCodeInputKeyUp: function (event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();
                        this.applyPromotionCode();
                    }
                },

                applyPromotionCode: function () {
                    var _this = this;
                    _this.set("isAdding", true);
                    var d1 = $.cv.css.orders.applyPromoCode({ _objectKey: _this.get("order")._objectKey, promoCode: _this.promotionCode });
                    d1.done(function (msg) {
                        var data = msg.data;
                        $.cv.css.trigger($.cv.css.eventnames.promoCodeApplied, { promoCode: _this.promotionCode });
                        // trigger order reload
                        d2 = $.cv.css.getCurrentOrder();
                        d2.done(function (msg) {
                            _this.clearMessage();
                            if (data.IsValid) {
                                // clear input
                                _this.set('promotionCode', '');
                                _this.setMessage(data.Message, $.cv.css.messageTypes.success);
                            } else {
                                if (data.Message != "") {
                                    _this.setMessage(data.Message, widget.options.promoCodeFailedMessageType);
                                } else {
                                    _this.setMessage(widget.options.textApplyPromotionCodeFailed, widget.options.promoCodeFailedMessageType);
                                }
                            }
                            _this.set("isAdding", false);
                        });
                    }).fail(function (msg) {
                        _this.clearMessage();
                        _this.setMessage(widget.options.textApplyPromotionCodeFailed, $.cv.css.messageTypes.error);
                        _this.set("isAdding", false);
                    });
                }

            });

            initDataSource();

            return viewModel;
        },


        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "<div data-view='true'>"
                          + "<div>"
                              + "<input data-bind='value: promotionCode, events: {keyup: promoCodeInputKeyUp}' class='promotional-code-input' type='text' />"
                              + "<input data-bind='click: applyPromotionCode' type='button' href='javascript:$.noop()' id='promotional-code-apply' value='" + widget.options.textApplyPromotionalCodePrompt + "' />"
                              + "<div class='itemList' data-bind='source: itemList, visible: promotionalCodesExist' data-template='" + widget.options.itemViewTemplate + "'>"
                              + "</div>"
                          + "</div>"
                      + "</div>";
            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = ""
                    + "<script type='text/x-kendo-template' id='" + widget.options.itemViewTemplate + "'>"
                        + "<div class='promotional-code'>"
						    + "#= Description #"
                            + "<input data-bind='click: execCommand_destroy' type='button' href='javascript:$.noop()' class='promotional-code-remove' value='" + widget.options.textRemovePromotionalCodePrompt + "' />"
						+ "</div>"
                    + "</script>";
            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(promotionalCodesWidget);

})(jQuery);;
/* Name: checkout messages
* Author: Chad Paynter
* Created: 20130510
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
* Params:  
*           dataSource: 
*           autoBind: 
*           triggerMessages
*           fieldsUpdated: 
*           showUpdateButton
*           textErrorUpdating
*           textErrorEnteredDateInWrongFormat
*           textErrorEnteredEmailInWrongFormat
*           textErrorEnteredDecimalInWrongFormat
*           textErrorEnteredUrlInWrongFormat
*           viewTemplate: kendo template id for the main view
*           itemViewTemplate: kendo template id for each item
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        FIELDSUPDATED = "fieldsUpdated",
        FIELDSCOMPLETED = "fieldsCompleted",
        WIDGETDATAINTIALISED = "widgetDataInitialised",
        FIELDSCHANGED = "fieldsChanged";


    var orderCompleteFieldsWidget = {
        // Standard Variables

        // widget name
        name: "orderCompleteFields",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            inputErrorClass: "input-error",
            // viewModel flags
            autoBind: true,
            hideEmptyPrompt: true,
            // messages
            triggerMessages: false,
            // events
            fieldsUpdated: '',
            // view flags
            showUpdateButton: false,
            updateOnChange: true,

            // text resources
            textErrorUpdating: 'Error updating order',
            textErrorEnteredDateInWrongFormat: 'Date entered is in the wrong format',
            textErrorEnteredEmailInWrongFormat: 'Email entered is in the wrong format',
            textErrorEnteredDecimalInWrongFormat: 'Decimal entered in the wrong format',
            textErrorEnteredUrlInWrongFormat: 'Url entered in the wrong format',
            textErrorNotAllFieldsComplete: "The following fields are incomplete:",
            textMandatoryFieldNotComplete: "Mandatory field",
            textErrorNotAllFieldsValid: "The following fields are invalid:",

            // view Template
            viewTemplate: null,
            itemViewTemplate: null
        },

        events: [DATABINDING, DATABOUND, FIELDSUPDATED, FIELDSCOMPLETED, WIDGETDATAINTIALISED, FIELDSCHANGED],

        viewModel: null,

        view: null,

        // MVVM Support

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be crevated
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (widget._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            widget.trigger(DATABOUND);
            $.cv.css.bind($.cv.css.eventnames.orderChanged, $.proxy(widget.viewModel.orderUpdated, widget.viewModel));
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        validateInputFields: function (showMessages) {
            var widget = this;
            widget.viewModel.validateInputFields(showMessages);
        },

        setUpdateOnChange: function (updateOnChange) {
            var widget = this;
            widget.viewModel.set("updateOnChange", updateOnChange);
        },

        updateOrderCompleteFields: function () {
            var widget = this;
            widget.viewModel.updateOrderCompleteFields();
        },

        // private function

        _getViewModel: function () {
            var widget = this;

            var _getDataView = function (data, posToProcess) {
                var widget = this;
                var array = [];
                // get index item
                if (posToProcess < data.length) {
                    var item = data[posToProcess];

                    $.each(item.Fields, function (indexFields, fieldToUse) {
                        var dataItem = $.cv.util.getFieldItemData(fieldToUse);

                        // override default dataChanged event
                        dataItem.dataChanged = function (e) {
                            var updateOnChange = this.parent().parent().get("updateOnChange");
                            if (dataItem.fieldValid(e) && updateOnChange) {
                                this.parent().parent().updateOrderCompleteField(this.fieldItem.fieldName);
                                this.parent().parent().validateInputFields(false);
                            }
                        };

                        array.push(dataItem);
                    });
                }

                return array;
            };

            var initDataSource = function () {
                var d3 = $.cv.css.orders.getOrderCompleteFieldGroupData();
                $.when(d3).done(function (msg) {
                    if (!msg.errorMessage || msg.errorMessage.length == 0) {
                        widget.options.dataSource = msg.data;
                        // set data to list of fields bound
                        for (var intPos = 0; intPos < msg.data.length; intPos++) {
                            // get list of fields
                            var dataListTemplates = _getDataView(msg.data, intPos);
                            // set name of fieldgroup
                            $.each(dataListTemplates, function (index, field) {
                                var fieldToUse = field.fieldItem;
                                var order = widget.viewModel;
                                viewModel.set('fieldsExist' + (intPos + 1).toString(), true);
                            });
                            viewModel.set('itemList' + (intPos + 1).toString(), dataListTemplates); //kendo.dataSource();
                            // get display label of fieldgroup
                            viewModel.set('fieldGroup' + (intPos + 1).toString() + 'Name', msg.data[intPos].FieldGroupName);
                        }
                        viewModel.validateInputFields(false);

                    } else {
                        widget.options.dataSource = [];
                        widget.viewModel.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                    }
                    widget.trigger(WIDGETDATAINTIALISED);
                });
            };

            var viewModel = kendo.observable({
                // Properties for UI elements
                order: null,

                /*------------------------------------*\
                   TIMEOUTS
               \*------------------------------------*/

                redirectToTimeoutUrl: function (fallbackUrl, params, includeInBrowserHistory) {
                    if ($.cv.ajax.settings.timeoutRedirectUrl == "")
                        $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
                    else
                        $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
                },

                /*------------------------------------*\
                   MESSAGES
               \*------------------------------------*/

                clearMessage: function () {
                    var clearExistingMessages = this.get("clearExistingMessages");
                    this.set("clearExistingMessages", true);
                    this.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'orderCompleteFields', clearExisting: this.get("clearExistingMessages") });
                    this.set("clearExistingMessages", clearExistingMessages);
                },

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                clearExistingMessages: true,
                message: '',

                dataSource: widget.options.dataSource,

                // indicates if all fields entered
                fieldsCompleted: function (displayMessages) {
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : false;
                    var fields = [];
                    var completed = true, incompleteFields = "";
                    $.merge($.merge(fields, this.get('itemList1')), this.get('itemList2'));
                    $.each(fields, function (index, item) {
                        if (!item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, displayMessages)) {
                            completed = false;
                            if (!item.emptyPrompt)
                                incompleteFields = incompleteFields.length == 0 ? " " + item.fieldItem.Prompt : incompleteFields + ", " + item.fieldItem.Prompt;
                        }
                    });
                    if (completed === true)
                        widget.trigger(FIELDSCOMPLETED);
                    if (!completed && displayMessages)
                        this.setMessage(widget.options.textErrorNotAllFieldsComplete + incompleteFields, $.cv.css.messageTypes.error);
                    return completed;
                },

                // indicates if all fields valid
                fieldsValid: function (displayMessages) {
                    displayMessages = typeof displayMessages !== 'undefined' ? displayMessages : false;
                    var fields = [];
                    var valid = true, invalidFields = "";
                    $.merge($.merge(fields, this.get('itemList1')), this.get('itemList2'));
                    $.each(fields, function (index, item) {
                        var val = this.get(item.fieldItem.fieldName);
                        var itemValid = true;
                        if (val != null) {
                            itemValid = item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, displayMessages);
                        }
                        if (!itemValid) {
                            valid = false;
                            if (!item.emptyPrompt)
                                invalidFields = invalidFields.length == 0 ? " " + item.fieldItem.Prompt : invalidFields + ", " + item.fieldItem.Prompt;
                            
                        }
                    });
                    if (!valid && displayMessages)
                        this.setMessage(widget.options.textErrorNotAllFieldsValid + invalidFields, $.cv.css.messageTypes.error);
                    return valid;
                },

                orderUpdated: function () {

                },

                updateItemList: function () {
                    var dataView = widget._getDataView();
                    this.set("itemList", dataView);
                    widget.trigger(FIELDSUPDATED, { count: dataView.length });
                },

                updateOnChange: widget.options.updateOnChange,

                updateOrderCompleteField: function (fieldname) {
                    // create payload
                    var list = {};
                    var list2 = {};
                    var valid = true;
                    widget.viewModel.clearMessage();
                    $.each([this.get('itemList1'), this.get('itemList2')], function (index, fieldgroupdata) {
                        $.each(fieldgroupdata, function (idx, item) {
                            // if a fieldname triggered it, just validate and send that
                            if (fieldname != null && item.fieldItem.fieldName === fieldname) {
                                var val = this.get(item.fieldItem.fieldName);
                                if (item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, false)) {
                                    if (index == 0)
                                        list[item.fieldItem.fieldName] = val;
                                    else if (index == 1)
                                        list2[item.fieldItem.fieldName] = val;
                                } else
                                    valid = false;
                            }
                        });
                    });

                    var pload = {};

                    pload[this.get('fieldGroup1Name')] = list;
                    pload[this.get('fieldGroup2Name')] = list2;

                    this.updateOrderCompleteFieldList(pload);
                },

                updateOrderCompleteFields: function () {
                    // create payload
                    var list = {};
                    var list2 = {};
                    var valid = true;
                    widget.viewModel.clearMessage();
                    $.each([this.get('itemList1'), this.get('itemList2')], function (index, fieldgroupdata) {
                        $.each(fieldgroupdata, function (idx, item) {
                            var val = this.get(item.fieldItem.fieldName);
                            if (item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, false)) {
                                if (index == 0)
                                    list[item.fieldItem.fieldName] = val;
                                else if (index == 1)
                                    list2[item.fieldItem.fieldName] = val;
                            } else
                                valid = false;
                        });
                    });

                    var pload = {};

                    pload[this.get('fieldGroup1Name')] = list;
                    pload[this.get('fieldGroup2Name')] = list2;

                    this.updateOrderCompleteFieldList(pload);
                    this.validateInputFields(false);

                },

                updateOrderCompleteFieldList: function (pload) {
                    var prom = $.cv.css.orders.updateOrderCompleteFieldGroupData({
                        newValues: pload
                    });
                    prom.done(function (msg) {
                        if (msg.errorMessage != null)
                            widget.viewModel.setMessage(widget.options.textErrorUpdating, $.cv.css.messageTypes.error);
                        else {
                            widget.viewModel.orderUpdated();
                            widget.trigger(FIELDSCHANGED);
                        }
                    });
                    prom.fail(function (msg) {
                        widget.viewModel.setMessage(widget.options.textErrorUpdating, $.cv.css.messageTypes.error);
                    });
                },

                // list of field templates and fieldgroup names to bind to list item template
                itemList1: [],
                itemList2: [],
                // data binding flags to show /hide fieldgroups
                fieldsExist1: false,
                fieldsExist2: false,
                // names of fieldgroups to use for updating data
                fieldGroup1Name: '',
                fieldGroup2Name: '',

                validateInputFields: function (showMessages) {
                    var passedValidation = true, widgetReference = "orderCompleteFields";
                    // check mandatory fields
                    // using the this command inside this validation function ensures it fires of when one of the fields being monitored is updated
                    this.clearMessage();
                    if (!this.fieldsCompleted(showMessages))
                        passedValidation = false;
                    else if (!this.fieldsValid(showMessages))
                        passedValidation = false;
                    $.cv.css.addRemovePageValidationError(passedValidation, widgetReference);
                    $.cv.css.trigger($.cv.css.eventnames.orderCompleteFieldsValidated);
                }


            });

            viewModel.bind("change", function (e) {

            });
            initDataSource();
            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = ""
                + "<div data-view='true'>"
                + "<div>"
                //+ "<span data-bind='html: message'></span>"
                + "<div data-bind='visible: fieldsExist1'>"
                + "<fieldset class='order-complete-fields'><legend data-bind='text: fieldGroup1Name'></legend><div class='itemList1' data-bind='source: itemList1' data-template='" + widget.options.itemViewTemplate + "'></div></fieldset>"
                + "</div><div data-bind='visible: fieldsExist2'><fieldset class='order-complete-fields' data-bind='visible: fieldsExist2'><legend data-bind='text: fieldGroup2Name'></legend><div class='itemList2' data-bind='source: itemList2' data-template='" + widget.options.itemViewTemplate + "'></div></fieldset>"
                + "</div>" + (widget.options.showUpdateButton ? "<input type='button' data-bind='click: updateOrderCompleteFields' value='Update' />" : "")
                + "</div>"
                + "</div>";
            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemViewTemplate + "'>"
                + "<div class='fieldContainer'>"
                + (widget.options.hideEmptyPrompt ? "<label data-bind='html: prompt, invisible: emptyPrompt'></label>" : "<label data-bind='html: prompt'></label>")
                + "<span data-bind='html: fieldTemplate'></span>"
                + "</div>"
                + "</script>";
            return html;
        }
    };

    // register the widget

    $.cv.ui.widget(orderCompleteFieldsWidget);

})(jQuery);;
;
/* Name: metadatasummary
* Author: Chad Paynter
* Created: 20130527
* Description: Displays product metadata (also known as product features) for the products shown on productdisplay page
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*          --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.data.kendo.js
*          /Scripts/cv.ajax.js
*          /Scripts/cv.css.js
* Params:  
*          sessionTimeOutRedirectUrl - redirect if session timeout and no cv.ajax setting defined
*/
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        WIDGETINITIALISED = "widgetInitialised",
        LISTUPDATED = "listUpdated",
        ITEMCHANGE = "itemchange";

    var metaDataSummary = {

        // Standard Variables

        // widget name
        name: "metaDataSummary",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: null,
            // viewModel flags
            autoBind: true,
            showAll: false,
            redirectOnFilter: true,
            allowCombinations: false,
            refreshOnItemChange: false,
            filterOnItemChange: true,
            // events
            // view flags
            bindDataSource: true,
            filterJoinOperator: 'OR',
            sessionTimeOutRedirectUrl: '',
            triggerMessages: false,
            textErrorGettingFilters: "There was an error getting the current product features",

            // widget settings
            textCurrentFilterHeading: 'Current Filter',
            textCurrentFilterInstructions: 'Choose the filter below.',
            textAvailableFilterHeading: 'Filter the results',

            // view Template
            viewTemplate: null,
            itemViewTemplate: null
        },

        events: [DATABINDING, DATABOUND, LISTUPDATED, WIDGETINITIALISED],

        viewModel: null,

        view: null,

        // MVVM Support

        items: function () {
            return this.element.find("tbody").children();
        },

        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource) {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
            // redraw the datasource items
            this.refresh();
        },

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup view
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (!widget.options._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            // dataSource default should be []
            widget._dataSource();
            // if autobound, refresh is called when the DS is fetched
            widget.trigger(WIDGETINITIALISED);
        },

        _dataSource: function () {
            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler) {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            }
            else {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            if (widget.options.autoBind) {
                widget.dataSource.fetch();
            }
        },

        refresh: function (e) {
            var widget = this;
            // don't refresh recursively
            if (widget.refreshing)
                return;
            if (e.action == ITEMCHANGE && !widget.options.refreshOnItemChange)
                return;
            widget.viewModel.dataSource = widget.options.dataSource;
            widget.refreshing = true;
            widget.viewModel.updateItemList();
            widget.refreshing = false;
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var removeUriFilters = function (filters) {
                var uriFilters = widget.options.redirectOnFilter ? $.cv.util.getFilterFeatures() : viewModel.get("currentFilters");
                $.each(filters, function (idx, item) {
                    uriFilters = removeUriFilter(uriFilters, item.get("featureKey"), item.get("featureName"), item.get("featureKeyMode"));
                });
                // Generate New Uri
                generateNewUri(uriFilters);
            };
            var removeUriFilter = function (uriFilters, key, value, keyCondition) {
                var keyLabel = (keyCondition == "FeaturesByGroup" ? "ProductFeatureNames.Group" : "ProductFeatureNames.Name");
                var newUriFilters = [];

                for (var i = 0; i < uriFilters.length; i++) { // < Union
                    (function () {
                        for (var j = 0; j < uriFilters[i].length; j += 2) { // The filter values come in pairs so go through list 2 at a time
                            var newUnionEntry = [],
                                includeThisUnion = true;
                            if (uriFilters[i][j] && uriFilters[i][j + 1]) {
                                if (uriFilters[i][j].Key == keyLabel &&
                                    uriFilters[i][j].Value == key &&
                                    uriFilters[i][j + 1].Key == "Value" &&
                                    uriFilters[i][j + 1].Value == value) // WARNING: not 'value' is decoded value!!! so compare to Value not RawValue!
                                {
                                    includeThisUnion = false;
                                    break;
                                } else {
                                    newUnionEntry.push(uriFilters[i][j]);
                                    newUnionEntry.push(uriFilters[i][j + 1]);
                                }
                            } else {
                                includeThisUnion = false;
                            }
                            if (includeThisUnion === true) {
                                newUriFilters.push(newUnionEntry);
                            }
                        }
                    })();
                }
                return newUriFilters;
            };

            var addUriFilter = function (uriFilters, key, value, keyCondition) {
                var keyLabel = (keyCondition == "FeaturesByGroup" ? "ProductFeatureNames.Group" : "ProductFeatureNames.Name"),
                    newUnionPart = [];

                newUnionPart.push({ Key: keyLabel, Condition: 'eq', RawValue: $.cv.util.encodeFilterFeatureValue(key), Value: key });
                newUnionPart.push({ Key: 'Value', Condition: 'eq', RawValue: $.cv.util.encodeFilterFeatureValue(value), Value: value });
                uriFilters.push(newUnionPart);
                return uriFilters;
            };

            var applyFilters = function (filters) {
                var uriFilters = [];
                $.each(filters, function (idx, item) {
                    uriFilters = addUriFilter(uriFilters, item.get("featureKey"), item.get("featureName"), item.get("featureKeyMode"));
                });
                // Generate New Uri
                generateNewUri(uriFilters);
            };

            var setupFilter = function (key, value, keyCondition, add) {
                var uriFilters = widget.options.redirectOnFilter ? $.cv.util.getFilterFeatures() : viewModel.get("currentFilters");

                if (!add) {
                    // REMOVE: Remove the union containing the filter condition key/value filters (x2)
                    uriFilters = removeUriFilter(uriFilters, key, value, keyCondition);
                } else {
                    // Add: Add new union with filter conditions (x2)
                    uriFilters = addUriFilter(uriFilters, key, value, keyCondition);
                }

                // Generate New Uri
                generateNewUri(uriFilters);
            };

            var generateNewUri = function (uriFilters) {
                var uriParts = [],
                    uniqueFilters = [],
                    filter = "";
                
                // build a list of unique filters
                for (var i = 0; i < uriFilters.length; i++) {
                    filter = uriFilters[i][0].Key + ':' + uriFilters[i][0].Condition + ':' + uriFilters[i][0].RawValue;
                    if (_.indexOf(uniqueFilters, filter) == -1) {
                        uniqueFilters.push(filter);
                    }
                }

                // build filter combination
                for (var i = 0; i < uniqueFilters.length; i++) {
                    (function () {
                        var filterCombinations = [];
                        for (var j = 0; j < uriFilters.length; j++) {
                            filter = uriFilters[j][0].Key + ':' + uriFilters[j][0].Condition + ':' + uriFilters[j][0].RawValue;
                            if (uniqueFilters[i] == filter) {
                                (function () {
                                    var filterItems = [];

                                    for (var k = 0; k < uriFilters[j].length; k++) { // < Filter Entries for said union
                                        filterItems.push(uriFilters[j][k].Key + ':' +
                                        uriFilters[j][k].Condition + ':' +
                                        uriFilters[j][k].RawValue);
                                    }

                                    filterCombinations.push(filterItems.join(','));
                                })();
                            }
                        }
                        uriParts.push(filterCombinations.join('|'));
                    })();
                }
                var newUri = uriParts.join('%C3%B1');

                var filterToSet = { FilterFeature: newUri, PageProduct: "1" };
                if (widget.options.redirectOnFilter) {
                    $.cv.util.redirect(null, filterToSet, false);
                } else {
                    viewModel.set("currentFilters", uriFilters);
                    viewModel.set("currentFilterValue", filterToSet.FilterFeature);
                    $.cv.css.trigger($.cv.css.eventnames.metadataSummaryFilterChanged, { filterArray: uriFilters, filterString: decodeURI(filterToSet.FilterFeature) });
                }
            };

            var generateEncodedString = function (array) {
                var newFilterArrayEncoded = [];
                $.each(array, function (idx1, item1) {
                    var filterEncoded = [];
                    $.each(this.split(":"), function (idx2, item2) {
                        filterEncoded.push(encodeURIComponent(item2));
                    });
                    newFilterArrayEncoded.push(filterEncoded.join(":"));
                });
                return newFilterArrayEncoded.join("%C3%B1");
            };

            var initDataSource = function () {
                // make dynamic service call
                if (!widget.options.dataSource || widget.options.dataSource == null) {
                    var d1 = $.cv.css.getAvailableProductFeatureValues({ mode: widget.options.showAll ? "showAll" : "" });
                    $.when(d1).done(function (msg) {
                        if (!msg.sessionHasTimedOut) {
                            if (!msg.errorMessage || msg.errorMessage.length == 0) {
                                if (msg.data) {
                                    // set mode
                                    var filterMode = '';
                                    if (msg.data.Mode != undefined) {
                                        viewModel.set('filterMode', msg.data.Mode);
                                        filterMode = msg.data.Mode;
                                    }
                                    var FeatureValues = [];
                                    var currentFilter = $.cv.util.queryStringValue("FilterFeature");
                                    $.each(msg.data.FeatureValues, function (index, item) {
                                        if (item.length > 0) {
                                            var featureArray = [];
                                            $.each(item, function (indexRow, rowItem) {
                                                // check not already in filters
                                                if (currentFilter == null || currentFilter.indexOf("Value:eq:" + rowItem.Value) < 0 || widget.options.showAll) {
                                                    // create new array item with click function
                                                    featureArray.push({
                                                        featureName: rowItem.Value,
                                                        featureNameID: rowItem.Value.replace(/ /g, ''),
                                                        featureProductCount: rowItem.Count,
                                                        featureKey: index,
                                                        featureKeyMode: filterMode,
                                                        isSelected: currentFilter != null && currentFilter.indexOf("Value:eq:" + rowItem.Value) != -1,
                                                        toggleIsSelected: function () {
                                                            this.set("isSelected", !this.get("isSelected"));
                                                            if (widget.options.filterOnItemChange) {
                                                                this.isSelectedChanged();
                                                            }
                                                        },
                                                        isSelectedChanged: function () {
                                                            if (widget.options.filterOnItemChange) {
                                                                this.setFilter(true);
                                                            }
                                                        },
                                                        setFilter: function (negateIsSelected) {
                                                            negateIsSelected = (typeof negateIsSelected !== 'undefined' && typeof negateIsSelected !== 'object') ? negateIsSelected : false;
                                                            var isSelected = negateIsSelected ? !this.get("isSelected") : this.get("isSelected");
                                                            if (!negateIsSelected) {
                                                                this.set("isSelected", !this.get("isSelected"));
                                                            }
                                                            if (widget.options.allowCombinations || this.get("featureProductCount") > 0) {
                                                                if (!isSelected) {
                                                                    setupFilter(this.get("featureKey"), this.get("featureName"), this.get("featureKeyMode"), true);
                                                                } else {
                                                                    setupFilter(this.get("featureKey"), this.get("featureName"), this.get("featureKeyMode"), false);
                                                                }
                                                                
                                                            }
                                                        }
                                                    });
                                                }
                                            });
                                            FeatureValues.push({
                                                Feature: index,
                                                FeatureID: index.replace(/ /g, ''),
                                                Data: featureArray,
                                                HasData: featureArray.length > 0,
                                                selectedFilter: null,
                                                hasSelectedFilters: function() {
                                                    return this.selectedFilters().length > 0;
                                                },
                                                selectedFilters: function () {
                                                    return _.filter(this.Data, function (item) { return item.isSelected; });
                                                },
                                                isApplying: false,
                                                apply: function () {
                                                    this.set("isApplying", true); // only used be redirect method for now, so no need to unset
                                                    viewModel.applyFilters();
                                                },
                                                isClearing: false,
                                                clear: function () {
                                                    this.set("isClearing", true); // only used be redirect method for now, so no need to unset
                                                    viewModel.removeFilters(this.selectedFilters());
                                                }
                                            });
                                        }
                                    });
                                    widget.options.dataSource = FeatureValues;

                                }
                            } else
                                widget.viewModel.setMessage(msg.errorMessage, $.cv.css.messageTypes.error);
                        } else {
                            widget.viewModel.redirectToTimeoutUrl(widget.options.sessionTimeOutRedirectUrl, params, false);
                        }
                        setDataSource();
                        // indicate loading done for placeholder loading icon
                        viewModel.set('contentLoading', false);
                    }).fail(function () {
                        viewModel.setMessage(widget.options.textErrorGettingFilters, $.cv.css.messageTypes.error);
                        viewModel.set('contentLoading', false);
                    });
                }
            };

            var setDataSource = function () {
                widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

                if (widget.options.autoBind) {
                    widget.dataSource.fetch();
                }
                viewModel.updateItemList();

                $(".sidebarSlideToggleContent").hide();

                $(".sidebarSlideToggleHeader").on('click', function () {
                    $(this).next().slideToggle("medium");
                    $(this).next().find(".upArrow").toggle();
                    $(this).next().find(".downArrow").toggle();
                });
            };

            var getDataView = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];
                var array = [];
                var view = widget.dataSource.view();
                $.each(view, function (idx, item) {
                    // add standard commands
                    array.push(item);
                });
                return array;
            };

            // build filters - =Name:eq:Width,Value:eq:12
            var activeFilters = $.cv.util.getFilterFeatures();
            activeFilters = _.map(activeFilters, function (item) {
                item[1].Key = item[0].Value;
                return item[1];
            });

            var viewModel = kendo.observable({

                // Properties for UI elements
                dataSource: widget.options.dataSource,

                updateItemList: function () {
                    viewModel.set('contentLoading', true);
                    var dataView = getDataView();
                    this.set("itemList", dataView);
                    this.set("hasFilterOptions", dataView.length > 0);
                    this.set("showAvailableFilterPane", dataView.length > 0);
                    widget.trigger(LISTUPDATED, { count: dataView.length });
                },

                itemList: getDataView(),

                message: '',

                clearMessage: function () {
                    this.set("message", "");
                    if (widget.options.triggerMessages)
                        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'metaDataSummary', clearExisting: true });
                },

                setMessage: function (message, type) {
                    $.cv.util.notify(this, message, type, {
                        triggerMessages: widget.options.triggerMessages,
                        source: widget.name
                    });
                },

                clearExistingMessages: true,

                // filters
                showAvailableFilterPane: true,
                hasFilterOptions: true,
                ActiveFilters: activeFilters,
                hasActiveFilters: activeFilters.length > 0,
                removeFilter: function () {
                    setupFilter(this.Key, this.Value, widget.viewModel.get("filterMode"));
                },
                removeFilters: function (filters) {
                    removeUriFilters(filters)
                },
                applyFilters: function () {
                    var itemList = this.get("itemList"),
                        filters = [],
                        selectedFilters = [];
                    $.each(itemList, function (idx, item) {
                        selectedFilters = this.selectedFilters();
                        $.each(selectedFilters, function () {
                            filters.push(this);
                        });
                    });
                    applyFilters(filters);
                },
                setFilter: function () {
                    setupFilter(this.Key, this.Value, this.get('filterMode'), true);
                },
                filterMode: '',
                isClearingAll: false,
                removeAllFilters: function () {
                    this.set("isClearingAll", true);
                    $.cv.util.redirect(null, { FilterFeature: "", PageProduct: "1" }, false);
                },
                currentFilters: [],
                currentFilterValue: "",

                // labels
                textCurrentFilterHeading: widget.options.textCurrentFilterHeading,
                textCurrentFilterInstructions: widget.options.textCurrentFilterInstructions,
                textAvailableFilterHeading: widget.options.textAvailableFilterHeading,

                //  TIMEOUTS
                redirectToTimeoutUrl: function (fallbackUrl, params, includeInBrowserHistory) {
                    if ($.cv.ajax.settings.timeoutRedirectUrl == "")
                        $.cv.util.redirect(fallbackUrl, params, includeInBrowserHistory);
                    else
                        $.cv.util.redirect($.cv.ajax.settings.timeoutRedirectUrl, params, includeInBrowserHistory);
                },

                // appearance
                contentLoading: false
            });

            initDataSource();

            viewModel.bind("change", function (e) {
                if (e.field == "itemList" && e.action && e.action == "itemchange" && widget.options.filterOnItemChange) {
                    if (e.items && e.items.length > 0) {
                        if (e.items[0].Feature != undefined && e.items[0].selectedFilter != undefined) {
                            setupFilter(e.items[0].Feature, e.items[0].selectedFilter, viewModel.get('filterMode'), true);
                        }
                    }
                }
            });

            return viewModel;
        },


        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = "";
            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemTemplateId + "'>";
            html += "";
            html += "</script>";
            return html;
        }

    }
    // register the widget

    $.cv.ui.widget(metaDataSummary);

})(jQuery);;
/// <reference path="../jquery-1.7.2.js" />
/*
*  TODO
*    Convert to dynamic services 
*       login
*       retrievePassword
*       changePassword
*
*/
/*
* Name: login
* Author: Aidan Thomas
* Date Created: 2013/01/04
* Description: contains logging in, change password and forgot password functionality
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js (built with jquery-1.7.1.min.js)
*          kendo.web.js (kendo.web.min.js v2012.2.710)
*
*          --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.data.kendo.js
*          /Scripts/cv.ajax.js                  
*                  
* Params:
*          redirectOnLogin - Bool, reloads / redirects user to page after usccessful login or change of password
*          useCookie - Bool, shows the remoember me check box, stores the user id in a cookie after successful login when checked
*          cookieExpiry - Integer, length of time the cookie is valid for
*          
*          textPasswordMismatchError - Text, change password mismatch error message
*          textUserIdMandatory - Text, must enter a user id message
*          textPasswordChange - Text, password requires changing message
*          textPasswordChanged - Text, password successfully changed message
*          textPasswordSent - Text, password sent message
*          textEnterPassword - Text, enter a password message
*          textErrorChangingPassword - Text, error changing password message
*          
*          viewTemplate - Allows to specify an override template to be used for the view.
*          
*          textUserIdLabel - Text
*          textPasswordLabel - Text
*          textForgotPasswordLabel - Text
*          textRememberMeLabel - Text
*          textLoginButtonLabel - Text
*          textEmailMeButtonLabel - Text
*          textCancelSendPasswordButtonLabel - Text
*          textNewPasswordLabel - Text
*          textConfirmPasswordLabel - Text
*          textChangePasswordButtonLabel - Text
*          textNotificationEmailLabel - Text
*
*
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        LOGGEDIN = 'loggedin',
        FORGOTPASSWORDSENT = 'forgotpasswordsent',
        PASSWORDCHANGED = 'passwordchanged',
        CHANGINGPASSWORD = 'changingPassword',
        cookiekey = 'emailaddress',
        FIELDSUPDATED = "fieldsUpdated";
        FIELDSCOMPLETED = "fieldsCompleted",
        FIELDTYPEEMAIL = "email",
        FIELDTYPEDATE = "date",
        FIELDTYPEMONEY = "money",
        FIELDTYPEDOUBLE = "double",
        FIELDTYPEWEBADDRESS = "webaddress";

        var loginWidget = {


            // Standard Variables

            // widget name
            name: "login",

            // default widget options
            options: {
                inputErrorClass: "input-error",
                redirectOnLogin: true,
                overrideRedirectUrl: "",
                loadOrdersAfterLogin: true,
                useCookie: true,
                cookieExpiry: 365,
                useRegistration: false,
                sendRegisterEmail: true,
                notifyEmailRequired: false, // Email Address is not valid email (i.e. its a username) or Notify Email Address is not set
                userIdRequiredAsEmail: true,
                firstNameRequiredForRegistration: true,
                surnameRequiredForRegistration: true,
                phoneNumberRequiredForRegistration: true,
                termsRequiredForRegistration: false,
                includeInBrowserHistory: false,
                registerFullName: false,
                registerFullNameField: "",
                registerUseConfirmEmail: false,
                registerUseNotifyEmail: false,
                registerUseCustomerCode: false,
                registerInitialRole: "",
                registerCustomerCode: "",
                clearPasswordOnMismatch: false,
                registerUseTerms: false,
                showEmailingPassword: false, // Password Recovery Mode
                clearEmailAfterPasswordEmailed: false,
                showChangingPassword: false, // Confirm Password = 'forcechange'
                clearPasswordAfterChange: true,
                clearWidgetMessages: true,
                // messages
                textPasswordMismatchError: 'Your passwords do not match. Please try again',
                textEmailMismatchError: "Your email addresses do not match. Please try again",
                textUserIdMandatory: 'Please enter an email address',
                textPasswordChanged: 'Password changed',
                textPasswordChangeRequired: 'You are required to change your password',
                textPasswordSent: 'Password sent to your email address',
                textEnterPassword: 'Please enter a password',
                textErrorChangingPassword: 'Error changing password',
                textErrorEnteredDateInWrongFormat: 'Date entered is in the wrong format',
                textErrorEnteredEmailInWrongFormat: 'Email entered is in the wrong format',
                textErrorEnteredDecimalInWrongFormat: 'Decimal entered in the wrong format',
                textErrorEnteredUrlInWrongFormat: 'Url entered in the wrong format',
                textRegisterMandatoryFieldsMissing: "The following fields are required:",
                textRegisterUserErrorDefaultMessage: "There was an error registering your user at this time",
                textTermsNotComplete: "You must agree to the terms and conditions",
                textMandatoryFieldNotComplete: "",

                // view Template
                viewTemplate: null,
                itemViewTemplate: null,
                // standard view options
                triggerMessages: false,
                triggerRegisterMessages: false,
                textUserIdLabel: 'Email Address',
                textConfirmEmailLabel: 'Confirm Email Address',
                textPasswordLabel: 'Password',
                textForgotPasswordLabel: 'Forgot Password',
                textRememberMeLabel: 'Remember me',
                textLoginButtonLabel: 'Login',
                textEmailMeButtonLabel: 'Email Me',
                textCancelSendPasswordButtonLabel: 'Cancel',
                textNewPasswordLabel: 'New Password',
                textConfirmPasswordLabel: 'Confirm Password',
                textChangePasswordButtonLabel: 'Change Password',
                textNotificationEmailLabel: 'Notification Email Address',
                textFirstNamePrompt: "First Name",
                textSurnamePrompt: "Last Name",
                textCustomerCodePrompt: "Customer Code",
                textInitialRolePrompt: "Initial Role",
                textPhoneNumberPrompt: "Phone Number",
                textTermsPrompt: "I have read the terms & conditions"
                // others to be added to allow configuration of standard view
            },

            // private property
            _viewAppended: false,

            events: [LOGGEDIN, FORGOTPASSWORDSENT, PASSWORDCHANGED, FIELDSUPDATED, CHANGINGPASSWORD],

            viewModel: null,

            view: null,

            // Standard Methods
            initialise: function (el, o) {
                var widget = this;
                // check for an internal view
                var internalView = $(el).children(":first");
                if (internalView.data("view")) {
                    widget.view = internalView.html();
                } else {
                    if (!widget.options.itemViewTemplate) {
                        // generate an item template name and flag it to be crevated
                        widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                        widget._itemViewAppended = true;
                    }
                    // get template text and parse it with the options
                    var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                    var viewTemplate = kendo.template(templateText);
                    widget.view = viewTemplate(widget.options);
                    // add the itemView (not parsed)
                    if (widget._itemViewAppended) {
                        widget.view += widget._getDefaultItemViewTemplate();
                    }
                    widget.element.html(widget.view);
                }
                // now MMVM bind
                widget.viewModel = widget._getViewModel();
                var target = $(el).children(":first");
                kendo.bind(target, widget.viewModel);
                widget.trigger(DATABOUND);
                
                // after all theuser data has loaded continue with login process
                $.cv.css.bind($.cv.css.eventnames.login, function (data) {
                    var obj = widget.viewModel;
                    var redirectUrl = widget.options.overrideRedirectUrl.length > 0 ? widget.options.overrideRedirectUrl : ($.cv.util.queryStringRedirectValue("R") ? $.cv.util.queryStringRedirectValue("R") : data.redirectAfterLogin);
                    
                    if (data.result == '15' || data.result == '16') {
                        obj.set("isProcessing", false);
                        obj.set("redirectUrl", redirectUrl);
                        obj.set("changingPassword", true);
                        widget.trigger(CHANGINGPASSWORD);
                        $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textPasswordChangeRequired, $.cv.css.messageTypes.info]);
                        if (data.result == '16') {
                            obj.set('notifyEmailRequired', true);
                        }
                    }
                    else {
                        widget.trigger(LOGGEDIN, { userId: obj.get("userId") }); // example of passing parameters to event handler
                        if (widget.options.redirectOnLogin && redirectUrl.length > 0) {
                            $(location).attr('href', redirectUrl);
                        }
                    }
                });
            },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var _getDataView = function (data) {
                var array = [];

                $.each(data, function (indexFields, fieldToUse) {
                    fieldToUse.index = indexFields;
                    array.push(_getFieldItemData(fieldToUse));
                });

                return array;
            };

            var _getDefaultDataView = function () {
                var array = [];
                array.push(_getFieldItemData({ FieldName: "userId", Prompt: widget.options.textUserIdLabel, Value: "", FieldType: widget.options.userIdRequiredAsEmail ? "email" : "varchar", Rows: 0, Columns: 0, Length: 50, Mandatory: true, Lookup: {}, isViewModelField: true }));
                if (widget.options.registerUseConfirmEmail)
                    array.push(_getFieldItemData({ FieldName: "confirmEmailAddress", Prompt: widget.options.textConfirmEmailLabel, Value: "", FieldType: "email", Rows: 0, Columns: 0, Length: 50, Mandatory: true, Lookup: {}, isViewModelField: true }));
                if (widget.options.registerUseNotifyEmail)
                    array.push(_getFieldItemData({ FieldName: "notifyEmailAddress", Prompt: widget.options.textNotificationEmailLabel, Value: "", FieldType: "email", Rows: 0, Columns: 0, Length: 50, Mandatory: viewModel.get("notifyEmailRequired"), Lookup: {}, isViewModelField: true }));
                array.push(_getFieldItemData({ FieldName: "firstName", Prompt: widget.options.textFirstNamePrompt, Value: "", FieldType: "varchar", Rows: 0, Columns: 0, Length: 50, Mandatory: widget.options.firstNameRequiredForRegistration, Lookup: {}, isViewModelField: true }));
                array.push(_getFieldItemData({ FieldName: "surname", Prompt: widget.options.textSurnamePrompt, Value: "", FieldType: "varchar", Rows: 0, Columns: 0, Length: 50, Mandatory: widget.options.surnameRequiredForRegistration, Lookup: {}, isViewModelField: true }));
                array.push(_getFieldItemData({ FieldName: "phoneNumber", Prompt: widget.options.textPhoneNumberPrompt, Value: "", FieldType: "varchar", Rows: 0, Columns: 0, Length: 50, Mandatory: widget.options.phoneNumberRequiredForRegistration, Lookup: {}, isViewModelField: true }));
                array.push(_getFieldItemData({ FieldName: "password", Prompt: widget.options.textPasswordLabel, Value: "", FieldType: "password", Rows: 0, Columns: 0, Length: 50, Mandatory: true, Lookup: {}, isViewModelField: true }));
                array.push(_getFieldItemData({ FieldName: "confirmPassword", Prompt: widget.options.textConfirmPasswordLabel, Value: "", FieldType: "password", Rows: 0, Columns: 0, Length: 50, Mandatory: true, Lookup: {}, isViewModelField: true }));
                if (widget.options.registerUseCustomerCode)
                    array.push(_getFieldItemData({ FieldName: "customerCode", Prompt: "", Value: widget.options.registerCustomerCode, FieldType: "varchar", Rows: 0, Columns: 0, Length: 50, Mandatory: true, Lookup: {}, isViewModelField: true }));
                if (widget.options.registerUseTerms)
                    array.push(_getFieldItemData({ FieldName: "terms", Prompt: widget.options.textTermsPrompt, Value: "", FieldType: "bool", Rows: 0, Columns: 0, Length: 1, Mandatory: widget.options.termsRequiredForRegistration, Lookup: { regex: /true/ }, isViewModelField: true, MandatoryMessage: widget.options.textTermsNotComplete }));
                return array;
            };

            var _getFieldItemData = function (fieldToUse) {
                if(widget.options.textMandatoryFieldNotComplete.trim() != "")
                    fieldToUse.MandatoryMessage = widget.options.textMandatoryFieldNotComplete;
                var dataItem = $.cv.util.getFieldItemData(fieldToUse);

                // trigger event for field change as some use keyup, others use click/blur etc
                dataItem.dataChanged = function (e) {
                    var fieldItem = e.data["fieldItem"];
                    var val = e.data[fieldItem.fieldName];
                    dataItem.fieldValid(e);
                    if (fieldItem.isViewModelField) {
                        viewModel.set(fieldItem.fieldName, val);
                    }
                };

                return dataItem;
            };

            var _getMandatoryFieldList = function (data) {
                var itemList = viewModel.get("itemList"), mandatoryArray = [];
                $.each(itemList, function (idx, item) {
                    if (item.fieldItem.mandatory)
                        mandatoryArray.push($.extend(item, { emptyMessage: item.prompt }));
                });

                viewModel.set("mandatoryFieldList", mandatoryArray);
            };

            var initDataSource = function () {
                if (widget.options.useRegistration) {
                    var d1 = $.cv.css.user.registerUserFieldData();
                    $.when(d1).done(function (msg) {
                        var data = msg.data[0];
                        var dataListTemplates = _getDefaultDataView();
                        if (data.UserRegistrationFieldData && data.UserRegistrationFieldData.length > 0) {
                            dataListTemplates = $.merge(dataListTemplates, _getDataView(data.UserRegistrationFieldData));
                        }
                        widget.viewModel.set('itemList', dataListTemplates);
                        widget.trigger(FIELDSUPDATED, { count: dataListTemplates.length });
                        _getMandatoryFieldList();
                    });
                }
            };

            var viewModel = $.extend(kendo.observable(widget.options),
                {
                    userId: widget.options.useRegistration ? "" : widget.options.useCookie ? ($.cookie(cookiekey) ? $.cookie(cookiekey) : '') : '',

                    confirmEmailAddress: "",

                    firstName: "",

                    surname: "",

                    fullName: function () {
                        var firstName = this.get("firstName"), surname = this.get("surname");
                        return firstName + ($.trim(firstName).length == 0 ? "" : " ") + surname;
                    },

                    phoneNumber: "",

                    inputEventKeyUp: function (event) {
                        this.userIdKeyUp(event);
                    },

                    userIdKeyUp: function (event) {
                        if (event.which == 13) {
                            // stops the form from submitting when using the widget on a page that has form submit buttons
                            event.preventDefault();
                            event.stopPropagation();
                            if (widget.options.useRegistration) {
                                this.register();
                            } else {
                                if (!this.get("emailingPassword"))
                                    this.login();
                                else
                                    this.emailPassword();
                            }
                        }
                    },

                    password: '',

                    confirmPassword: "",

                    passwordEnabled: function () {
                        return this.get("userId").length > 0;
                    },

                    rememberMe: ($.cookie(cookiekey) != null),

                    changingPassword: widget.options.showChangingPassword,

                    emailingPassword: widget.options.showEmailingPassword,

                    emailPasswordSent: false,

                    changePassword1: '',

                    changePassword2: '',

                    changePasswordKeyUp: function (event) {
                        if (event.which == 13) {
                            // stops the form from submitting when using the widget on a page that has form submit buttons
                            event.preventDefault();
                            event.stopPropagation();
                            this.changePassword();
                        }
                    },

                    notifyEmailAddress: '',

                    notifyEmailRequired: widget.options.registerUseNotifyEmail && widget.options.notifyEmailRequired,

                    customerCode: widget.options.registerCustomerCode,

                    initialRole: widget.options.registerInitialRole,

                    itemList: [],

                    mandatoryFieldList: [],

                    redirectUrl: '',

                    isProcessing: false,

                    message: '',

                    hasMessage: function () {
                        return this.get("message").length > 0;
                    },

                    clearMessage: function () {
                        this.set("message", "");
                        if (widget.options.triggerMessages || widget.options.triggerRegisterMessages)
                            $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: '', source: 'login', clearExisting: true });
                    },

                    _setRememberMeCookie: function() {
                        var vm = this;
                        if (widget.options.useCookie) {
                            if (vm.get("rememberMe"))
                                $.cookie(cookiekey, vm.get("userId"), { expires: widget.options.cookieExpiry });
                            else
                                $.removeCookie(cookiekey);
                        }
                    },

                    login: function (e) {
                        var obj = this;
                        // validate
                        if (obj.get("userId").length == 0) {
                            $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textUserIdMandatory, $.cv.css.messageTypes.error]);
                            return;
                        }
                        $.cv.util.clearMessage.apply(widget.viewModel);

                        this.set("isProcessing", true);

                        var d = $.cv.css.login(obj.get("userId"), obj.get("password"), { loadOrdersAfterLogin: widget.options.loadOrdersAfterLogin });

                        $.when(d).done(function (msg) {
                            var data = msg.data;
                            if (data.result == '0' || data.result == '15' || data.result == '16') {
                                obj._setRememberMeCookie();
                            }
                            else {
                                obj.set("isProcessing", false);
                                $.cv.util.setMessage.apply(widget.viewModel, [data.responseMessage, $.cv.css.messageTypes.error]);
                            }
                        }).fail(function (msg) {
                            obj.set("isProcessing", false);
                            msg = JSON.parse(msg.responseText);
                            $.cv.util.setMessage.apply(widget.viewModel, [msg.message, $.cv.css.messageTypes.error]);
                        });
                    },

                    forgotPassword: function () {
                        var obj = this;
                        $.cv.util.clearMessage.apply(widget.viewModel);
                        obj.set("changingPassword", false);
                        obj.set("emailingPassword", true);
                    },

                    emailPassword: function (e) {
                        var obj = this;
                        // validate email
                        if (this.get("userId").length == 0) {
                            $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textUserIdMandatory, $.cv.css.messageTypes.error]);
                            return;
                        }

                        this.set("isProcessing", true);
                        var d = $.cv.css.retrieveUserPassword({ username: this.get("userId") });

                        $.when(d).done(function (msg) {
                            var data = msg.data;
                            obj.set("isProcessing", false);
                            if (data.result) {
                                obj.set("emailingPassword", widget.options.showEmailingPassword);
                                obj.set("emailPasswordSent", true);
                                widget.trigger(FORGOTPASSWORDSENT);
                                obj._setRememberMeCookie();
                                if (widget.options.clearEmailAfterPasswordEmailed) {
                                    obj.set("userId", "");
                                }
                                $.cv.util.setMessage.apply(widget.viewModel, [data.responseMessage, $.cv.css.messageTypes.success]);
                            }
                            else {
                                $.cv.util.setMessage.apply(widget.viewModel, [data.responseMessage, $.cv.css.messageTypes.error]);
                            }
                        }).fail(function (msg) {
                            obj.set("isProcessing", false);
                            msg = JSON.parse(msg.responseText);
                            $.cv.util.setMessage.apply(widget.viewModel, [msg.errorMessage, $.cv.css.messageTypes.error]);
                        });
                    },

                    cancelEmailPassword: function () {
                        this.set("emailingPassword", false);
                    },

                    changePassword: function (e) {
                        var obj = this;
                        var op = obj.get("password");
                        var usr = $.cv.css.localGetUser();
                        // validate
                        var p1 = this.get("changePassword1");
                        var p2 = this.get("changePassword2");
                        if (p1 != p2) {
                            $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textPasswordMismatchError, $.cv.css.messageTypes.error]);
                            if (widget.options.clearPasswordOnMismatch) {
                                this.set("changePassword1","");
                                this.set("changePassword2","");
                            }      
                            return;
                        }
                        if (p1.length == 0) {
                            $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textEnterPassword, $.cv.css.messageTypes.error]);
                            return;
                        }
                        // call WS
                        this.set("isProcessing", true);
                        this.clearMessage();
                        var d = $.cv.css.changeUserPassword({
                            username: obj.get("userId"),
                            newPassword: p1,
                            newPasswordConfirm: p2,
                            notifyEmail: obj.get('notifyEmailAddress')
                        });
                        $.when(d).done(function (msg) {
                            var data = msg.data;
                            obj.set("isProcessing", false);
                            if (data.result) {
                                $.cv.util.clearMessage.apply(widget.viewModel);
                                widget.trigger(PASSWORDCHANGED);
                                $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textPasswordChanged, $.cv.css.messageTypes.success]);
                                if (obj.get("redirectUrl").length > 0 && widget.options.redirectOnLogin) {
                                    $(location).attr('href', obj.get("redirectUrl"));
                                } else {
                                    if (widget.options.clearPasswordAfterChange) {
                                        obj.set("changePassword1", "");
                                        obj.set("changePassword2", "");
                                    }
                                    obj.set("changingPassword", widget.options.showChangingPassword);
                                }
                            }
                            else {
                                $.cv.util.setMessage.apply(widget.viewModel, [data.responseMessage, $.cv.css.messageTypes.error]);
                            }
                        }).fail(function (msg) {
                            obj.set("isProcessing", false);
                            msg = JSON.parse(msg.responseText);
                            $.cv.util.setMessage.apply(widget.viewModel, [msg.errorMessage, $.cv.css.messageTypes.error]);
                        });
                    },

                    getRegisterOptions: function () {
                        var opts = {};
                        opts.EmailAddress = this.get("userId");
                        opts.NotifyEmailAddress = this.get("notifyEmailAddress");
                        opts.FirstName = this.get("firstName");
                        opts.Surname = this.get("surname");
                        opts.Password = this.get("password");
                        opts.PhoneNumber = this.get("phoneNumber");
                        opts.sendEmail = widget.options.sendRegisterEmail;
                        if (widget.options.registerUseCustomerCode && this.get("customerCode") != "" && this.get("initialRole") != "") {
                            opts.CustomerCode = this.get("customerCode");
                            opts.initialRole = this.get("initialRole");
                            opts.setCustAndRole = true;
                        }
                        $.each(this.get("itemList"), function (idx, item) {
                            if (!item.fieldItem.isViewModelField) {
                                var val = this.get(item.fieldItem.fieldName);
                                if (val == null) {
                                    val = "";
                                }
                                if (val.toString() != "true" && val.toString() != "false") {
                                    opts[item.fieldItem.fieldName] = val.toString();
                                } else {
                                    opts[item.fieldItem.fieldName] = val;
                                }
                            }
                        });
                        if (widget.options.registerFullName && widget.options.registerFullNameField != "")
                            opts[widget.options.registerFullNameField] = this.fullName();
                        return opts;
                    },

                    setValidationError: function (item, message) {
                        if (item && item.setError && item.fieldItem) {
                            item.setError(item.fieldItem, message, message.length > 0 ? item.fieldItem.classForErrors : "");
                        }
                    },

                    checkAllRequiredFields: function () {
                        var allFieldsPopulated = true, emptyFields = "", mandatoryFieldList = this.get("mandatoryFieldList"), itemList = this.get("itemList"), _this = this, fieldGroupItem = [];
                        $.each(mandatoryFieldList, function (idx, item) {
                            if (!item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, true)) {
                                allFieldsPopulated = false;
                                emptyFields += "," + item.emptyMessage;
                            }
                        });

                        if (emptyFields != "") {
                            emptyFields = widget.options.textRegisterMandatoryFieldsMissing + " " + emptyFields.substring(1);
                            $.cv.util.setMessage.apply(widget.viewModel, [emptyFields, $.cv.css.messageTypes.error]);
                        }
                        return allFieldsPopulated;
                    },

                    checkPasswordsMatch: function () {
                        var passwordsMatch = true;
                        var passwordField = $.grep(this.get("itemList"), function (item) { return item.fieldItem.fieldName == "confirmPassword" })[0];
                        this.setValidationError(passwordField, "");
                        if (this.get("password") != this.get("confirmPassword")) {
                            passwordsMatch = false;
                            $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textPasswordMismatchError, $.cv.css.messageTypes.error]);
                            this.setValidationError(passwordField, widget.options.textPasswordMismatchError);
                        }
                        return passwordsMatch;
                    },

                    checkEmailAddressMatch: function () {
                        var emailsMatch = true;
                        if (widget.options.registerUseConfirmEmail) {
                            var emailField = $.grep(this.get("itemList"), function (item) { return item.fieldItem.fieldName == "confirmEmailAddress" })[0];
                            this.setValidationError(emailField, "");
                            if (this.get("userId") != this.get("confirmEmailAddress")) {
                                emailsMatch = false;
                                $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textEmailMismatchError, $.cv.css.messageTypes.error]);
                                this.setValidationError(emailField, widget.options.textEmailMismatchError);
                            }
                        }
                        return emailsMatch;
                    },

                    register: function () {
                        var _this = this;
                        _this.clearMessage();
                        if (this.checkAllRequiredFields() && this.checkPasswordsMatch() && this.checkEmailAddressMatch()) {
                            var _this = this, opts = _this.getRegisterOptions();
                            _this.set("isProcessing", true);
                            var d1 = $.cv.css.user.registerUser(opts);
                            $.when(d1).done(function (msg) {
                                var data = msg.data
                                var params = {};
                                if (!data.sessionHasTimedOut) {
                                    if (data.result == true) {
                                        _this.login();
                                    } else {
                                        _this.set("isProcessing", false);
                                        // this will allow you to not trigger validation messages and display only inline messages except for any error comes back from the dynamic service
                                        var triggerMessages = _this.get("triggerMessages");
                                        _this.set("triggerMessages", widget.options.triggerRegisterMessages);
                                        $.cv.util.setMessage.apply(widget.viewModel, [data.responseMessage, $.cv.css.messageTypes.error]);
                                        _this.set("triggerMessages", triggerMessages);
                                    }
                                }
                            }).fail(function (msg) {
                                _this.set("isProcessing", false);
                                // this will allow you to not trigger validation messages and display only inline messages except for any error comes back from the dynamic service
                                var triggerMessages = _this.get("triggerMessages");
                                _this.set("triggerMessages", widget.options.triggerRegisterMessages);
                                $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textRegisterUserErrorDefaultMessage, $.cv.css.messageTypes.error]);
                                _this.set("triggerMessages", triggerMessages);
                            });
                        }
                    }

                });

            initDataSource();

            return viewModel;
        },

        _getDefaultViewTemplate: function () {
            var widget = this;
            var html =
                "<div id='LoginWrapper'>"
                    // login
                    + "<div id='LoginContainer' data-bind='invisible: changingPassword'>"
                        + "<label id='UserIdLabel'>#= textUserIdLabel #</label>"
                        + "<input id='UserIdInput' type='text' data-bind='value: userId' data-value-update='keyup' />"
                        + "<label id='PasswordLabel' data-bind='invisible: emailingPassword'>#= textPasswordLabel #</label>"
                        + "<input id='PasswordInput' type='password' data-bind='value: password, enabled: passwordEnabled, invisible: emailingPassword, events:{keyup: userIdKeyUp}' />"
                        + "# if (useCookie) { #"
                        + "<label id='RememberMeLabel' data-bind='invisible: emailingPassword'>#= textRememberMeLabel #</label>"
                        + "<input id='RememberMeInput' type='checkbox' data-bind='checked: rememberMe,invisible: changingPassword, invisible: emailingPassword' />"
                        + "# } #"
                        // email password
                        + "<input id='LoginButton' type='button' value='#= textLoginButtonLabel #' data-bind='click: login, invisible: emailingPassword' />"
                        + "<input id='EmailMeButton' type='button' value='#= textEmailMeButtonLabel #' data-bind='click: emailPassword, visible: emailingPassword' />"
                        + "<input id='CancelSendPasswordButton' type='button' value='#= textCancelSendPasswordButtonLabel #' data-bind='click: cancelEmailPassword, visible: emailingPassword' />"
                        + "<a id='ForgotPasswordLink' href='javascript:void(0)' data-bind='click: forgotPassword, invisible: emailingPassword'>#= textForgotPasswordLabel #</a>"
                    + "</div>"
                    // change password / notify email address
                    + "<div id='ChangePasswordContainer' data-bind='visible: changingPassword'>"
                        + "<label id='NewPasswordLabel'>#= textNewPasswordLabel #</label>"
                        + "<input id='NewPasswordInput' type='password' data-bind='value: changePassword1, events:{keyup: changePasswordKeyUp ' />"
                        + "<label id='ConfirmPasswordLabel'>#= textConfirmPasswordLabel #</label>"
                        + "<input id='ConfirmPasswordInput' type='password' data-bind='value: changePassword2, events:{keyup: changePasswordKeyUp}' />"
                        + "<label id='NotificationEmailLabel' data-bind='visible: notifyEmailRequired'>#= textNotificationEmailLabel #</label>"
                        + "<input id='NotificationEmailInput' type='email' data-bind='value: notifyEmailAddress, visible: notifyEmailRequired, events:{keyup: changePasswordKeyUp}' />"
                        + "<input id='ChangePasswordButton' type='button' data-bind='click: changePassword' value='#= textChangePasswordButtonLabel #' />"
                    + "</div>"
                    + "<span id='ErrorMessageContainer' data-bind='html: message'></span>"
                + "</div>";

            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = "<script type='text/x-kendo-template' id='" + widget.options.itemViewTemplate + "'>"
                + "<div class='fieldContainer'>"
                + (widget.options.hideEmptyPrompt ? "<label data-bind='html: prompt, invisible: emptyPrompt'></label>" : "<label data-bind='html: prompt'></label>")
                + "<span data-bind='html: fieldTemplate'></span>"
                + "</div>"
                + "</script>";
            return html;
        }

    }

    // register the widget

    $.cv.ui.widget(loginWidget);

})(jQuery);
;
/**
    Widget:
        gift card

    Description:
        Management of gift cards on an order.

    Features:
        - Add and remove gift cards
        - Viewing of gift cards on the order
        - Order Total with gift cards applied.

    Documentation: http://confluence/x/vQGoAw

    Tests:
        A test runner for tests for this widget can be found here:
        http://localhost:49900/Scripts/widgets/Tests/cv.css.ui.giftCard.spec-runner.html

        The specification code:
        http://localhost:49900/Scripts/widgets/Tests/cv.css.ui.giftCard.spec.js

        Runner & Spec Language: Jasmin

    Author:
        Justin Wishart: 2013-07-31.
        Call: 58559
**/

;

(function ($, undefined) {

    var ADDING_GIFT_CARD          = 'addingGiftCard',
        ADDING_GIFT_CARD_FAILED   = 'addingGiftCardFailed',
        ADDED_GIFT_CARD           = 'addedGiftCard',
        REMOVING_GIFT_CARD        = 'removingGiftCard',
        REMOVING_GIFT_CARD_FAILED = 'removingGiftCardFailed',
        REMOVED_GIFT_CARD         = 'removedGiftCard';

    var giftCardWidget = {
        name:   "giftCard",
        extend: "mvvmwidget",

        options: {
            // Events
            itemTemplate: '',
            defaultSummaryViewVisible: false,
            triggerGlobalMessages: false,

            // Default function for getting the total amount of the order that the gift cards, if 
            // the total gift cards equals or exceeds, then the summary view will be the only
            // view that shows.
            extractOrderTotal: function (order) {
                return order.OrderTotalAmount;
            },

            validationOptions: {}
        },

        extendEvents: [
            ADDING_GIFT_CARD,
            ADDING_GIFT_CARD_FAILED,
            ADDED_GIFT_CARD,
            REMOVING_GIFT_CARD,
            REMOVING_GIFT_CARD_FAILED,
            REMOVED_GIFT_CARD
        ],


        // MVVM Support
        //

        viewModelBound: function () {
            var widget = this;
            var vm = widget.viewModel;

            vm.refreshViewVisibility();

            $.cv.css.bind($.cv.css.eventnames.giftCardsChanged
                , $.proxy(vm.refreshViewVisibility, vm));
        },


        // Private function
        //

        _getCurrentCards: function() {
            var widget = this;

            // Generates function to call when item is removed
            var generateRemoveGiftCard = function (cardNo) {
                return function () {
                    widget.viewModel.removeGiftCard(cardNo);
                };
            };
            
            var currentOrder = $.cv.css.localGetCurrentOrder(),
                cards = [];
                
            if (currentOrder) {
                var tempCards = currentOrder.GiftCards || [];

                // Add remove event to each card...
                // MAP to an array instead of leaving as an object also!
                $.each(tempCards, function(i, c) {
                    c.removeGiftCard = generateRemoveGiftCard(c.CardNumber);
                    cards.push(c);
                });
            }

            return cards;
        },

        _getViewModel: function () {
            var widget = this,
                $el = $(widget.element);

            // Determine the type of widget mode/views we have
            var notifyViewExists  = $el.find(".notify-view").length > 0;
            var entryViewExists   = $el.find(".entry-view").length > 0;
            var summaryViewExists = $el.find(".summary-view").length > 0;
            var cards = widget._getCurrentCards();

            var viewModel = kendo.observable({
                // Notify View
                //

                isNotifyViewVisible: notifyViewExists && true,
                doesNotifyViewExist: notifyViewExists,

                showEntryView: function () {
                    viewModel.clearEntryView();

                    viewModel.set('isNotifyViewVisible', false);
                    viewModel.set('isEntryViewVisible', true);
                },


                // Entry View
                //

                isEntryViewVisible: (entryViewExists && !notifyViewExists), // entry view should show by default if not notify view exists
                doesEntryViewExist: entryViewExists,

                // Gift Card Information
                giftCardNumber: '',
                giftCardPin:    '',
                useTotalAmount: false,
                amountToUse: 0,
                isProcessing: false,


                // Event Handlers/Methods
                addGiftCardKeyUp: function (event) {
                    if (event.which == 13) {
                        // stops the form from submitting when using the widget on a page that has form submit buttons
                        event.preventDefault();
                        event.stopPropagation();
                        this.addGiftCard();
                    }
                },

                addGiftCard: function () {
                    var prom = $.Deferred();
                    
                    viewModel.validate();

                    viewModel.set("messages", []);

                    // Only progress if the form is valid.
                    if (viewModel.get('isValid') === true) {
                        widget.trigger(ADDING_GIFT_CARD, {cardNumber: viewModel.get('giftCardNumber')});

                        var cardInfo = {
                            cardNumber: viewModel.get('giftCardNumber'),
                            pinNumber: viewModel.get('giftCardPin'),
                            useAllAmount: viewModel.get('useTotalAmount'),
                            amountToUse: viewModel.get('amountToUse')
                        };
                        viewModel.set("isProcessing", true);
                        var addProm = $.cv.css.giftCard.addGiftCard(cardInfo);

                        addProm.done(function (response) {
                            viewModel.set("isProcessing", false);
                            if (response.data.Success === false) {
                                var messages = $.map(response.data.Messages, function (item) {
                                    return {
                                        type: 'error',
                                        message: item
                                    };
                                });

                                viewModel.set('messages', messages);
                                
                                widget.trigger(ADDING_GIFT_CARD_FAILED, {cardNumber: viewModel.get('giftCardNumber')});

                                prom.resolve();
                                return;
                            }

                            // Adjust view model.
                            viewModel.refreshViewVisibility().done(function() {
                                viewModel.clearEntryView();

                                // Trigger Events
                                widget.trigger(ADDED_GIFT_CARD, {cardNumber: viewModel.get('giftCardNumber')});

                                $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                                $.cv.css.trigger($.cv.css.eventnames.giftCardsChanged, {
                                    cardNumber: cardInfo.cardNumber,
                                    changeType: 'add'
                                });

                                // Messages
                                if (widget.options.triggerGlobalMessages) {
                                    $.cv.css.trigger($.cv.css.eventnames.message, {
                                        message: "Gift card added",
                                        type: 'info',
                                        source: 'giftCard',
                                        clearExisting: true
                                    });
                                }

                                viewModel.set('messages', [{ type: 'info', message: 'Gift card added' }]);             

                                prom.resolve();
                            });
                        });

                        addProm.fail(function () {
                            viewModel.set("isProcessing", false);
                            viewModel.set('messages', [{ type: 'error', value: 'Unknown error while adding gift card' }]);

                            if (widget.options.triggerGlobalMessages) {
                                $.cv.css.trigger($.cv.css.eventnames.message, {
                                    message: "Unknown error while adding gift card",
                                    type: 'error',
                                    source: 'giftCard',
                                    clearExisting: true
                                });
                            }

                            widget.trigger(ADDING_GIFT_CARD_FAILED, {cardNumber: viewModel.get('giftCardNumber')});

                            prom.reject();
                        });
                    } else {
                        // Validation messages should show as is.
                        prom.reject();
                    }

                    return prom.promise();
                },

                cancelEntry: function () {
                    viewModel.clearEntryView();

                    viewModel.set('isNotifyViewVisible', true);
                    viewModel.set('isEntryViewVisible', false);
                    viewModel.set('messages', []);
                },

                clearEntryView: function () {
                    viewModel.set('giftCardNumber', '');
                    viewModel.set('giftCardPin', '');
                    viewModel.set('useTotalAmount', false);
                    viewModel.set('amountToUse', 0);
                },


                // Summary View
                //

                isSummaryViewVisible: widget.options.defaultSummaryViewVisible,
                doesSummaryViewExist: summaryViewExists,

                removeGiftCard: function (cardNumber) {
                    var prom = $.Deferred();

                    widget.trigger(REMOVING_GIFT_CARD, {cardNumber: cardNumber});

                    var removeProm = $.cv.css.giftCard.removeGiftCard({cardNumber: cardNumber});

                    removeProm.done(function (response) {
                        if (response.data.Success === false) {
                            var messages = $.map(response.data.Messages, function (item) {
                                return {
                                    type: 'error',
                                    message: item
                                };
                            });

                            viewModel.set('messages', messages);

                            prom.resolve();

                            widget.trigger(REMOVING_GIFT_CARD_FAILED, {cardNumber: cardNumber});

                            return;
                        }

                        viewModel.refreshViewVisibility().done(function() {
                            viewModel.set('messages', [{ type: 'info', message: 'Gift card removed' }]);

                            widget.trigger(REMOVED_GIFT_CARD);

                            $.cv.css.trigger($.cv.css.eventnames.orderChanged);
                            $.cv.css.trigger($.cv.css.eventnames.giftCardsChanged, {
                                cardNumber: cardNumber,
                                changeType: 'remove'
                            });

                            if (widget.options.triggerGlobalMessages) {
                                $.cv.css.trigger($.cv.css.eventnames.message, {
                                    message: "Gift card removed",
                                    type: 'info',
                                    source: 'giftCard',
                                    clearExisting: true
                                });
                            }

                            prom.resolve();
                        });
                    });
                    
                    removeProm.fail(function () {
                        viewModel.set('messages', [{ type: 'error', message: 'Unknown error while removing the gift card' }]);

                        if (widget.options.triggerGlobalMessages) {
                            $.cv.css.trigger($.cv.css.eventnames.message, {
                                message: "Unknown error while adding gift card",
                                type: 'error',
                                source: 'giftCard',
                                clearExisting: true
                            });
                        }

                        widget.trigger(REMOVING_GIFT_CARD_FAILED, {cardNumber: cardNumber});

                        prom.reject();
                    });

                    return prom.promise();
                },

                giftCards: cards,

                orderTotal: 0,
                orderTotalAfterGiftCards: 0,


                // Validation
                //

                isValid: true,

                validate: function () {
                    // Ensure we have validation setup
                    var validator = $el.data("kendoValidator");
                    if (!validator) {
                        $el.kendoValidator(widget.options.validationOptions);
                        validator = $el.data("kendoValidator");

                        // Turn off validation of hidden fields on rules
                        $.cv.util.preventHiddenFieldValidation(validator);
                    }

                    // Validate
                    var isValid = validator.validate();
                    viewModel.set('isValid', isValid);

                    return isValid;
                },


                // General
                //

                messages: [],

                ensureOrder: function(reload) {
                    var vm = this;
                    var currentOrder = $.cv.css.localGetCurrentOrder();

                    // Reload Order if:
                    // 1. We don't have one stored in Local Storage
                    // 2. If we are asked to (reload = true)
                    var orderExists = $.Deferred();
                    
                    if (!currentOrder || reload) {
                        $.cv.css.getCurrentOrder().done(function() {
                            orderExists.resolve();
                        });
                    } else {
                        orderExists.resolve();
                    }

                    orderExists.done(function() {
                        var currentOrder = $.cv.css.localGetCurrentOrder();
                        var gcs = widget._getCurrentCards();
                        var orderTotal = widget.options.extractOrderTotal(currentOrder);

                        vm.set('giftCards', gcs);
                        vm.set('orderTotal', orderTotal);

                        if (gcs.length > 0) {
                            vm.set('orderTotalAfterGiftCards', currentOrder.TotalPaymentBalanceAfterEnteredGiftCards);
                        } else {
                            vm.set('orderTotalAfterGiftCards', orderTotal);
                        }
                    });

                    return orderExists.promise();
                },

                refreshViewVisibility: function () {
                    var vm = this;
                    var prom = $.Deferred();
                    
                    vm.ensureOrder(true).done(function() {
                        var giftCards = widget._getCurrentCards();
                        var totalAfterGiftCards = viewModel.get('orderTotalAfterGiftCards');

                        var notifyViewExists  = $el.find(".notify-view").length > 0;
                        var entryViewExists   = $el.find(".entry-view").length > 0;
                        var summaryViewExists = $el.find(".summary-view").length > 0;

                        // Total may adjust visibility of notify or edit views
                        if (totalAfterGiftCards <= 0) {
                            viewModel.set('wasNotifyViewLastVisible',viewModel.get('isNotifyViewVisible'));
                            viewModel.set('isNotifyViewVisible', false);
                            viewModel.set('isEntryViewVisible', false);
                        } else {
                            var notify = viewModel.get('wasNotifyViewLastVisible');

                            if (notify != null) {
                                viewModel.set('isNotifyViewVisible', notify);
                                viewModel.set('isEntryViewVisible', !notify);

                                if (!notify) {
                                    viewModel.set("messages", []);
                                }
                            }
                        }

                        viewModel.set('isSummaryViewVisible', 
                            summaryViewExists && (widget.options.defaultSummaryViewVisible === true 
                                                  || (giftCards && giftCards.length > 0)));

                        prom.resolve();
                    });

                    return prom;
                }

            });

            viewModel.bind("change", function (e) {
                if (e.field === 'useTotalAmount') {
                    viewModel.set("amountToUse", 0);
                }
            });

            $.cv.css.bind($.cv.css.eventnames.orderChanged, function (order) {
                if (order === true) {
                    viewModel.ensureOrder(true);
                    viewModel.refreshViewVisibility();
                    return;
                }
                
                if (order == null) {
                    return;
                }

                var gcs = widget._getCurrentCards();
                if (gcs.length > 0) {
                    viewModel.set("orderTotalAfterGiftCards", order.TotalPaymentBalanceAfterEnteredGiftCards);
                } else {
                    viewModel.set("orderTotalAfterGiftCards", order.OrderTotalAmount);
                }
            });

            return viewModel;
        },

        _buildViewTemplate: function () {
            var widget = this;
            widget.viewTemplate = "";
        }

    };

    // register the widget
    $.cv.ui.widget(giftCardWidget);

})(jQuery);
;
/* Name: user maintenance
* Author: Aidan Thomas
* Created: 20131111
*
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/widgets/cv.ui.mvvmwidget.js
*           --- CSS - OPTIONAL IF YOU WANT TO USE THESE ELEMENTS ---
* Params:  
*/
;
(function ($, undefined) {

    var WIDGETDATAINTIALISED = "widgetDataInitialised",
        LISTUPDATED = "listUpdated";

    var userMaintenanceWidget = {

        // Standard Variables

        // widget name
        name: "userMaintenance",

        extend: "mvvmwidget",

        // default widget options
        options: {
            // viewModel defaults
            jsonFieldGroupName: "",
            successfulRedirectUrl: "",
            // viewModel flags
            autoBind: true,
            // events
            // view flags
            triggerMessages: false,
            clearWidgetMessages: true,
            updateOnChange: false,
            // view text defaults
            textMandatoryFieldsMissing: "The following fields are required: {0}",
            textErrorEnteredDateInWrongFormat: 'Date entered is in the wrong format',
            textErrorEnteredEmailInWrongFormat: 'Email entered is in the wrong format',
            textErrorEnteredDecimalInWrongFormat: 'Decimal entered in the wrong format',
            textErrorEnteredUrlInWrongFormat: 'Url entered in the wrong format',
            textMandatoryFieldNotComplete: "Mandatory field",
            textUpdateSuccessful: "Updated successfully",
            textUpdateErrorDefaultMessage: "There was an error updating your information at this time",
            // view Template
            viewTemplate: null,
            itemTemplate: ''
        },

        extendEvents: [WIDGETDATAINTIALISED, LISTUPDATED],

        // MVVM Support

        viewModelBound: function () {
            // called after the widget view is bound to the viewModel
            var widget = this, initDeferred = $.Deferred(), userDetailsDef = $.Deferred(), ds = [];
            widget.viewModel.set("isProcessing", true);

            userDetailsDef = $.cv.css.user.getUserMaintenanceData({ jsonFieldGroupName: widget.options.jsonFieldGroupName });

            $.when(userDetailsDef).done(function (msg) {
                if (!msg.errorMessage || msg.errorMessage.length == 0) {
                    if (msg.data && msg.data.length > 0 && msg.data[0].UserDetailsFieldData) {
                        ds = msg.data[0].UserDetailsFieldData;
                    } else {
                        ds = [];
                    }
                    widget.viewModel.set('itemList', widget._getDataView(ds));
                }
                initDeferred.resolve();
            });
            $.when(initDeferred).done(function () {
                widget.viewModel.set("isProcessing", false);
                widget.trigger(WIDGETDATAINTIALISED, { viewCount: widget.viewModel.get("itemList").length, dataCount: widget.viewModel.get("dataCount") });
            });
        },

        // private functions
        _getDataView: function (data) {
            var widget = this, array = [];
            $.each(data, function (idx, item) {
                // add standard commands
                item.index = idx;
                item.MandatoryMessage = widget.options.textMandatoryFieldNotComplete;
                var dataItem = $.cv.util.getFieldItemData(item);
                dataItem.dataChanged = function (e) {
                    var fieldItem = e.data["fieldItem"];
                    var updateOnChange = widget.viewModel.get("updateOnChange");
                    if (dataItem.fieldValid(e) && updateOnChange) {
                        widget.viewModel.updateUserField(fieldItem.fieldName);
                    }
                };
                array.push(dataItem);
            });
            return array;
        },

        _stringEmpty: function (data) {
            if ($.type(data) == "string")
                data = data.replace(/ /g, "");
            return !data;
        },

        _getViewModel: function () {
            var widget = this;
            return widget._getDefaultViewModel();
        },

        _getDefaultViewModel: function () {
            var widget = this;

            var _getMandatoryFieldList = function () {
                var itemList = widget.viewModel.get("itemList"), mandatoryArray = [];
                $.each(itemList, function (idx, item) {
                    if (item.fieldItem.mandatory)
                        mandatoryArray.push($.extend(item, { emptyMessage: item.prompt }));
                });

                return mandatoryArray;
            };

            var viewModel = kendo.observable($.extend(widget.options, {

                // Properties for UI elements
                isProcessing: false,

                updateOnChange: widget.options.updateOnChange,

                currentUser: null,

                itemList: [],

                message: '',

                clearWidgetMessages: widget.options.clearWidgetMessages,

                dataCount: 0,

                updateUserField: function (fieldname) {
                    // create payload
                    var _this = this, list = {}, valid = true;
                    $.cv.util.clearMessage.apply(widget.viewModel);
                    $.each(this.get('itemList'), function (index, item) {
                        // if a fieldname triggered it, just validate and send that
                        if (fieldname != null && item.fieldItem.fieldName === fieldname) {
                            var val = this.get(item.fieldItem.fieldName);
                            if ($.cv.util.validateField(val, item.fieldItem.FieldType)) {
                                list[item.fieldItem.fieldName] = val;
                            } else
                                valid = false;
                        }
                    });

                    var currentUser = _this.getCurrentUser();
                    $.when(currentUser).done(function (currentUser) {
                        list["_objectKey"] = currentUser;
                        var d1 = $.cv.css.user.setCurrentUserDetails({ updateData: list, jsonFieldGroup: widget.options.jsonFieldGroupName });
                        $.when(d1).done(function (msg) {
                            var data = msg.data;
                            var params = {};
                            if (!data.sessionHasTimedOut) {
                                _this.set("isProcessing", false);
                            }
                        });
                    });
                },

                checkAllRequiredFields: function () {
                    var allFieldsPopulated = true, emptyFields = "", mandatoryFieldList = _getMandatoryFieldList();
                    $.each(mandatoryFieldList, function (idx, item) {
                        if (!item.fieldItem.parent().fieldValid({ data: item.fieldItem.parent() }, true)) {
                            allFieldsPopulated = false;
                            emptyFields += "," + item.emptyMessage;
                        }
                    });

                    if (emptyFields != "") {
                        emptyFields = widget.options.textMandatoryFieldsMissing.format(emptyFields.substring(1));
                        $.cv.util.setMessage.apply(widget.viewModel, [emptyFields, $.cv.css.messageTypes.error]);
                    }
                    return allFieldsPopulated;
                },

                getCurrentUser: function () {
                    var _this = this, currentUser = this.get("currentUser"), currentUserDeferred = $.Deferred();
                    if (currentUser == null) {
                        userToEdit = $.cv.css.localGetUser();
                    } else {
                        userToEdit["_objectKey"] = currentUser;
                    }
                    if (userToEdit && userToEdit != null) {
                        var d = $.Deferred();
                        d.resolve({ data: [userToEdit] });
                    }
                    else {
                        var d = $.cv.css.getCurrentUser();
                    }
                    $.when(d).done(function (usr) {
                        if (usr && usr.data && usr.data.length > 0) {
                            _this.set("currentUser", usr.data[0]._objectKey);
                        }
                        currentUserDeferred.resolve(usr.data[0]._objectKey);
                    });
                    return currentUserDeferred.promise();
                },

                getUserDetails: function() {
                    var _this = this, userDetails = $.Deferred(), userUpdateData = {};
                    var currentUser = _this.getCurrentUser();

                    $.each(_this.get("itemList"), function (idx, item) {
                        var val = item.get(item.fieldItem.fieldName);
                        if (val == null) {
                            val = "";
                        }
                        if (val.toString() != "true" && val.toString() != "false") {
                            userUpdateData[item.fieldItem.fieldName] = val.toString();
                        } else {
                            userUpdateData[item.fieldItem.fieldName] = val;
                        }
                    });

                    currentUser.done(function (currentUser) {
                        userUpdateData["_objectKey"] = currentUser;
                        userDetails.resolve(userUpdateData);
                    });
                    return userDetails.promise();
                },

                saveDetails: function () {
                    var _this = this;
                    $.cv.util.clearMessage.apply(widget.viewModel);
                    if (this.checkAllRequiredFields()) {
                        _this.set("isProcessing", true);
                        var u = _this.getUserDetails();
                        $.when(u).done(function (userDetails) {
                            var d1 = $.cv.css.user.setCurrentUserDetails({ updateData: userDetails, jsonFieldGroup: widget.options.jsonFieldGroupName });
                            $.when(d1).done(function (msg) {
                                var data = msg.data;
                                if (!data.sessionHasTimedOut) {
                                    if (widget.options.successfulRedirectUrl.length == 0) {
                                        _this.set("isProcessing", false);
                                        $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textUpdateSuccessful, $.cv.css.messageTypes.success]);
                                    } else {
                                        $.cv.util.redirect(widget.options.successfulRedirectUrl, {}, false);
                                    }
                                }
                            }).fail(function (msg) {
                                _this.set("isProcessing", false);
                                $.cv.util.setMessage.apply(widget.viewModel, [widget.options.textUpdateErrorDefaultMessage, $.cv.css.messageTypes.error]);
                            });
                        });                        
                    }
                }

            }));

            return viewModel;
        },

        _buildViewTemplate: function () {
            var widget = this;
            widget._buildDefaultViewTemplate();
        },

        _buildDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            widget._buildDefaultContentTemplate();
        },

        _buildDefaultContentTemplate: function () {
            var widget = this;
            var html = "<div class='cv-ui-element-content-area'>";
            html += "</div>";
            widget.viewTemplate += html;
        },

        _buildItemTemplateBody: function () {
            var widget = this;
            var html = "<div></div>";
            widget.itemTemplate += html;
        }

    };

    // register the widget
    $.cv.ui.widget(userMaintenanceWidget);

})(jQuery);
;
(function (e, t) { var n = "beforeShowNearest"; var r = "afterShowNearest"; var i = "beforeUpdateLatLng"; var s = "afterUpdateLatLng"; var o = { name: "storeLocator", extend: "mvvmwidget", events: [n, r, i, s], options: { addressIds: [], addressAutocompleteId: null, addressRegion: "au", latitudeId: null, longitudeId: null, mapCanvasId: null, mapZoom: 12, mapMarkerTitle: "You are here", mapIconPath: "/images/", mapIconPrefix: null, mapIconSuffix: ".png", mapIconWidth: 50, mapIconHeight: 50, spinnerId: null, spinnerOptions: {}, clearStoresOnSearch: true, errorMessageAddressNotFound: "Address not found.", errorMessageAddressLookupFailed: "Address lookup failed.", errorMessageUnableToShowMap: "Unable to show map." }, viewModelBound: function () { var e = this }, _getViewModel: function () { var t = this; var o = kendo.observable({ storeLocations: [], isProcessing: false, updateLatLongFromAddress: function () { t.trigger(i); o._clearMessages(); var n = o._getAddress(); var r = e("#" + t.options.latitudeId); var u = e("#" + t.options.longitudeId); if (r.length > 0 && u.length > 0 && n.length > 0) { var a = new google.maps.Geocoder; o.set("isProcessing", true); o._showSpinner(); a.geocode({ address: n, region: t.options.addressRegion }, function (e, n) { o.set("isProcessing", false); o._hideSpinner(); if (n == google.maps.GeocoderStatus.OK) { r.val(e[0].geometry.location.lat()); u.val(e[0].geometry.location.lng()) } else { r.val(0); u.val(0); o._showError(t.options.errorMessageAddressNotFound) } t.trigger(s) }) } else { o._showError(t.options.errorMessageAddressLookupFailed); t.trigger(s) } }, showMap: function () { o._clearMessages(); var n = e("#" + t.options.latitudeId); var r = e("#" + t.options.longitudeId); if (t.options.mapCanvasId.length > 0 && n.length > 0 && r.length > 0) { o._showMap(n.val(), r.val()) } else { o._showError(t.options.errorMessageUnableToShowMap) } }, showNearest: function () { t.trigger(n); o.set("isProcessing", true); if (t.options.clearStoresOnSearch) { o.set("storeLocations", []); o._hideMap() } o._clearMessages(); o._showSpinner(); var i = o._getAddress(); var s = new google.maps.Geocoder; s.geocode({ address: i, region: t.options.addressRegion }, function (n, i) { if (i == google.maps.GeocoderStatus.OK) { var s = e.cv.css.storeLocator.findNearestStore({ latitude: n[0].geometry.location.lat(), longitude: n[0].geometry.location.lng() }); e.when(s).done(function (i) { var s = new google.maps.LatLngBounds; s.extend(new google.maps.LatLng(n[0].geometry.location.lat(), n[0].geometry.location.lng())); e.each(i.data, function (e, t) { t.position = new google.maps.LatLng(t.Latitude, t.Longitude); s.extend(t.position) }); var u = o._showMap(n[0].geometry.location.lat(), n[0].geometry.location.lng(), s); e.each(i.data, function (e, t) { t.Index = e + 1; o._addMarker(u, t.position, t.StoreName, t.Index) }); o.set("storeLocations", i.data); o._hideSpinner(); o.set("isProcessing", false); t.trigger(r) }) } else { o._showError(t.options.errorMessageAddressNotFound); t.trigger(r) } }) }, searchInputKeyUp: function (e) { if (e.which == 13) { e.preventDefault(); e.stopPropagation(); o.showNearest() } }, _getAddress: function () { var n = ""; e.each(t.options.addressIds, function (t, r) { if (t > 0) { n += " " } n += e("#" + r).val() }); return n }, _hideMap: function () { e("#" + t.options.mapCanvasId).empty() }, _showMap: function (n, r, i) { var s = { center: new google.maps.LatLng(n, r), zoom: t.options.mapZoom, mapTypeId: google.maps.MapTypeId.ROADMAP }; var o = new google.maps.Map(e("#" + t.options.mapCanvasId).get(0), s); var u = new google.maps.LatLng(n, r); var a = new google.maps.Marker({ map: o, position: u, title: t.options.mapMarkerTitle }); if (i) { o.fitBounds(i); o.panToBounds(i) } return o }, _addMarker: function (e, n, r, i) { var s = null; if (t.options.mapIconPrefix) { s = new google.maps.MarkerImage(t.options.mapIconPath + t.options.mapIconPrefix + i + t.options.mapIconSuffix, new google.maps.Size(t.options.mapIconWidth, t.options.mapIconHeight)) } var o = new google.maps.Marker({ map: e, position: n, title: r, icon: s }) }, _showError: function (e) { o._hideSpinner(); o.set("isProcessing", false); o._showMessage({ Type: "error", Message: e }) }, _showMessage: function (n, r) { var i = ""; e.each(e.cv.css.messageTypes, function (e, t) { if (e == n.Type) { i = t } }); var s = e.cv.css.trigger(e.cv.css.eventnames.message, { type: i, message: n.Message, source: t.name, clearExisting: r }); if (s.handlerCount == 0) { alert(n.Message) } }, _clearMessages: function () { e.cv.css.trigger(e.cv.css.eventnames.message, { message: "", type: "", source: t.name, clearExisting: true }) }, _showSpinner: function () { if (t.options.spinnerId) { e("#" + t.options.spinnerId).spin(t.options.spinnerOptions) } }, _hideSpinner: function () { if (t.options.spinnerId) { e("#" + t.options.spinnerId).spin(false) } } }); if (t.options.addressAutocompleteId) { var u = e("#" + t.options.addressAutocompleteId).get(0); var a = new google.maps.places.Autocomplete(u, { types: ["geocode"], componentRestrictions: { country: t.options.addressRegion } }); google.maps.event.addListener(a, "place_changed", function () { o.showNearest() }) } return o }, _buildViewTemplate: function () { var e = this } }; e.cv.ui.widget(o) })(jQuery);
/* Name: recently viewed product
* Author: Aidan Thomas
* Created: 20130220
* 
* Dependencies:    
*          --- Third Party ---
*          jquery.js 
*          kendo.web.js
*           --- CSS ---
*          /Scripts/cv.widget.kendo.js
*          /Scripts/cv.css.js
*          /Scripts/cv.util.js
* Params:  
*           dataSource: 
*           numberOfProducts:
*           autoBind: 
*           hideIfEmpty: 
*           listTitle: 
*           viewTemplate: kendo template id for the main view
*           itemViewTemplate: kendo template id for each item
*/
;
(function ($, undefined) {

    var DATABINDING = "dataBinding",
        DATABOUND = "dataBound",
        CHANGE = "change",
        LISTUPDATED = "listUpdated";


    var recentlyViewedProduct = {

        // Standard Variables

        // widget name
        name: "recentlyViewedProduct",

        // default widget options
        options: {
            // viewModel defaults
            dataSource: [],
            numberOfProducts: 4,
            // viewModel flags
            autoBind: true,
            // events
            // view flags
            hideIfEmpty: true,
            // view text defaults
            listTitle: "Items Recently Viewed",
            // view Template
            viewTemplate: null, // Treat these as IDs, remove the last one.
            itemViewTemplate: null
        },

        events: [DATABINDING, DATABOUND, LISTUPDATED],

        viewModel: null,

        view: null,

        // MVVM Support

        items: function () {
            return this.element.children();
        },

        // for supporting changing the datasource via MVVM
        setDataSource: function (dataSource) {
            // set the internal datasource equal to the one passed in by MVVM
            this.options.dataSource = dataSource;
            // rebuild the datasource if necessary, or just reassign
            this._dataSource();
            // redraw the datasource items
            this.refresh();
        },

        // private property
        _viewAppended: false,
        _itemViewAppended: false,


        // Standard Methods
        initialise: function (el, o) {

            var widget = this;

            var internalView = $(el).children(":first");
            if (internalView.data("view")) {
                widget.view = internalView.html();
            } else {
                // setup view
                widget._viewAppended = true;
                if (!widget.options.itemViewTemplate) {
                    // generate an item template name and flag it to be created
                    widget.options.itemViewTemplate = widget.name + "-item-template-" + kendo.guid();
                    widget._itemViewAppended = true;
                }
                // get template text and parse it with the options
                var templateText = widget.options.viewTemplate ? $("#" + widget.options.viewTemplate).html() : widget._getDefaultViewTemplate();
                var viewTemplate = kendo.template(templateText);
                widget.view = viewTemplate(widget.options);
                // add the itemView (not parsed)
                if (!widget.options._itemViewAppended) {
                    widget.view += widget._getDefaultItemViewTemplate();
                }
                widget.element.html(widget.view);
            }
            widget.viewModel = widget._getViewModel();
            // bind view to viewModel
            var target = widget.element.children(":first");
            kendo.bind(target, widget.viewModel);
            // dataSource default should be []
            widget._dataSource();
            // if autobound, refresh is called when the DS is fetched
        },

        _dataSource: function () {
            var widget = this;
            // if the DataSource is defined and the _refreshHandler is wired up, unbind because
            // we need to rebuild the DataSource
            if (widget.dataSource && widget._refreshHandler) {
                widget.dataSource.unbind(CHANGE, widget._refreshHandler);
            }
            else {
                widget._refreshHandler = $.proxy(widget.refresh, widget);
            }

            // returns the datasource OR creates one if using array or configuration object
            widget.dataSource = kendo.data.DataSource.create(widget.options.dataSource);

            // bind to the change event to refresh the widget
            widget.dataSource.bind(CHANGE, widget._refreshHandler);

            if (widget.options.autoBind) {
                widget.dataSource.fetch();
            }
        },

        refresh: function () {
            var widget = this;
            widget.viewModel.dataSource = widget.options.dataSource;
            widget.viewModel.updateItemList();
        },

        destroy: function () {
            var widget = this;
            // remove the data element
            widget.element.removeData(widget.name);
            // clean up the DOM
            if (widget._viewAppended) {
                $.cv.util.destroyKendoWidgets(widget.element);
                widget.element.empty();
            }
        },

        // private function
        _getViewModel: function () {
            var widget = this;

            var getDataView = function () {
                // check if ds is initialised
                if (!widget.dataSource)
                    return [];
                $.each(widget.dataSource.view(), function (idx, item) {
                    item.Index = idx;
                });
                return widget.dataSource.view();
            }

            var initDS = function () {
                var products = $.cv.css.recentlyViewedProduct.localGetRecentlyViewedProducts();
                if (products == null)
                    products = [];
                if (typeof productViewProduct != 'undefined') {
                    findAndRemove(products, 'productCode', productViewProduct.productCode);
                    trimList(products);
                    if (products.length < 4) {
                        products = $.merge([productViewProduct], products);
                        widget.options.dataSource = products;
                        if (products.length == 0)
                            viewModel.isEmpty = true;
                        else
                            viewModel.isEmpty = false;
                    }
                    else {
                        widget.options.dataSource = products;
                        if (products.length == 0)
                            viewModel.isEmpty = true;
                        else
                            viewModel.isEmpty = false;
                        products = $.merge([productViewProduct], products);
                    }
                    $.cv.css.recentlyViewedProduct.localSetRecentlyViewedProducts(products);
                }
                else {
                    trimList(products);
                    widget.options.dataSource = products;
                    if (products.length == 0)
                        viewModel.isEmpty = true;
                    else
                        viewModel.isEmpty = false;
                }
            }

            var findAndRemove = function (array, property, value) {
                $.each(array, function (index, result) {
                    if (result[property] === value) {
                        //Remove from array
                        array.splice(index, 1);
                        return false;
                    }
                });
            }

            var trimList = function (products) {
                if (products.length > widget.options.numberOfProducts)
                    products.splice(widget.options.numberOfProducts);
            }

            var viewModel = kendo.observable({

                // Properties for UI elements
                dataSource: widget.options.dataSource,

                updateItemList: function () {
                    var dataView = getDataView();
                    this.set("itemList", dataView);
                    widget.trigger(LISTUPDATED, { count: dataView.length });
                },

                itemList: getDataView(),

                isEmpty: true /*function() {
                    console.log('is empty',this.get('dataSource'));
                    if(this.get('dataSource') != null) {
                        console.log('is empty',this.get('dataSource').length == 0);
                        console.log('is empty',this.get('dataSource').length);
                        return this.get('dataSource').length == 0;
                    }
                    else
                        return true;
                }*/

            });

            initDS();

            return viewModel;
        },


        _getDefaultViewTemplate: function () {
            var widget = this;
            // modify view template based on widget.options where applicable
            var html = ""
                + "# if(hideIfEmpty) { #"
                + "<section class='sidebar-toppurchases' data-bind='invisible: isEmpty'>"
                + "# } else { #"
                + "<section class='sidebar-toppurchases'>"
                + '# } #'
                //+ "<section class='sidebar-toppurchases'>"
                    + "<h4>" + widget.options.listTitle + "</h4>"
                    + "<ul class='toppurchases-grid clearfix' data-bind='source: itemList' data-template='" + widget.options.itemTemplateId + "'>"
                    + "</ul>"
                + "</section>"
            return html;
        },

        _getDefaultItemViewTemplate: function () {
            var widget = this;
            // return the template to be bound to the dataSource items
            var html = ""
                + "<script type='text/x-kendo-template' id='" + widget.options.itemTemplateId + "'>"
                    + "<li>"
                        + "# if (data.SEOUrl != '') { #"
                            + "<a href='/Product#= data.SEOUrl #'>"
                        + "# } else { #"
                            + "<a href='/ProductDisplay.aspx?Product=#= data.productCode #'>"
                        + '# } #'
                            + "# if (data.picture1 != '' && data.picture1 != 'NoImage.gif') { #"
                                + "<img src='/images/ProductImages/#= data.picture1 #' />"
                            + "# } else {#"
                                + "<img src='/images/TemplateImages/placeholder/placeholder-product-100x100.png' />"
                            + '# } #'
                            + "<span>#= description #</span>"
                        + "</a>"
                    + "</li>"
                + "</script>";
            return html;
        }

    }
    // register the widget

    $.cv.ui.widget(recentlyViewedProduct);

})(jQuery);;
;
(function ($, undefined) {
  $.cv = $.cv || {};
  $.cv.productCustom = $.cv.productCustom || {};
  
  $.cv.productCustom.updateProductModal = function(message) {
    if (!$.fancybox.isOpen) {
	  $("#atc-modal").find("h2:first").text(message);
	  $.fancybox.open({
		href: "#atc-modal",
		topRatio: '0.25'
	  });
	} else {
	  $("#atc-modal").find("h2:first").text(message);
	}
  };
  	
  $.cv.productCustom.reviewSubmitted = function() {
	$( "#reviewform" ).slideUp();
  }

})(jQuery);;
;
(function ($, undefined) {
    $.cv = $.cv || {};
    $.cv.checkoutCustom = $.cv.checkoutCustom || {};
  
    // common
  
    $.cv.checkoutCustom.validateInputFields = function(element,dataItem) {
        var widget;
        $(element).each(function () {
            widget = $(this).data(dataItem);
            if (widget) {
                if ($.isFunction(widget.validateInputFields))
                    widget.validateInputFields(true);
            }
        });
    };

    $.cv.checkoutCustom.hasValidationError = function(widget) {
        _.contains(_.keys($.cv.css.pageValidationErrors), widget)
    };
  
    $.cv.checkoutCustom.showStep = function(step) {
        $.cv.css.trigger($.cv.css.eventnames.message, { message: "", type: $.cv.css.messageTypes.error, source: 'paymentOptions', clearExisting: true });
        $("[id^='checkout-step']").hide();
        $("#checkout-step-" + step).show();
        $('html,body').animate({scrollTop: 0},'fast');
        $.cv.checkoutCustom.updateBreadCrumb(step);
    };
    
    $.cv.checkoutCustom.updateBreadCrumb = function(step) {
        $.each($(".breadcrumbs li"), function(idx, item) {
            if ((idx + 1) < parseInt(step)) {
                $(item).removeClass("active").addClass("completed");
                $(item).bind("click", function() {
                   $.cv.checkoutCustom.showStep((idx + 1)); 
                });
            } else {
                $(item).unbind("click");
                if ((idx + 1) == parseInt(step)) {
                    $(item).removeClass("completed").addClass("active");
                } else {
                    $(item).removeClass("completed").removeClass("active");
                }
            }
        });
    };
    
    $.cv.checkoutCustom.updateReviewAddressField = function(id,val) {
        var el = $(id);
        el.text(val);
        val.length > 0 ? el.show() : el.hide();
    };
  
    // delivery address / checkout step 1
  
    $.cv.checkoutCustom.setDeliveryAddressFunctions = function() {
        $('.shipping-address select').selectric();
        $("#continue-to-billing").click(function() {
           $.cv.checkoutCustom.continueToBilling(); 
        });
        $.cv.checkoutCustom.updateReviewShippingAddress();
        $.cv.css.bind($.cv.css.eventnames.addressChanged, $.cv.checkoutCustom.updateReviewShippingAddress);
    };
    
    $.cv.checkoutCustom.continueToBilling = function() {
        $.cv.checkoutCustom.validateInputFields(".grid.shipping-address","deliveryAddress");
        if (!_.contains(_.keys($.cv.css.pageValidationErrors), "deliveryAddress")) {
            $.cv.checkoutCustom.saveAddress();
            $.cv.checkoutCustom.showStep("2");
        }
    };
    
    $.cv.checkoutCustom.updateReviewShippingAddress = function() {
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-name-review",$("input[name='SoDelAddr1']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-address-1-review",$("input[name='SoDelAddr2']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-address-2-review",$("input[name='SoDelAddr3']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-postcode-review",$("input[name='SoDelPostcode']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-suburb-review",$("input[name='SoDelSuburb']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-state-review",$("select[name='SoDelState']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-country-review",$("select[name='SoDelCountry']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#shipping-phone-review",$("input[name='SoDelPhone']").val());
    };
    
    $.cv.checkoutCustom.saveAddress = function() {
		var d = $.Deferred(), currentUser = $.cv.css.localGetUser();
		if (currentUser == null) {
				d = $.cv.css.getCurrentUser();
		} else {
				d.resolve({ data: [currentUser] });
		}
		$.when(d).done(function (usr) {
				if (usr && usr.data && usr.data.length > 0) {
					var userUpdateData = {
						_objectKey: usr.data[0]._objectKey,
						ARUFullName: $("input[name='SoDelAddr1']").eq(0).val(),
                        ARUAddressLine1: $("input[name='SoDelAddr2']").eq(0).val(),
						ARUAddressLine2: $("input[name='SoDelAddr3']").eq(0).val(),
                        ARUPostCode: $("input[name='SoDelPostcode']").eq(0).val(),
						ARUSuburb: $("input[name='SoDelSuburb']").eq(0).val(),
						ARUState: $("select[name='SoDelState']").eq(0).val(),
						ARUCountry: $("select[name='SoDelCountry']").eq(0).val(),
                        ARUContactPhone: $("input[name='SoDelPhone']").eq(0).val()
					}
					$.cv.css.user.setCurrentUserDetails({ updateData: userUpdateData, jsonFieldGroup: "saveAddressFieldGroup" });
				}
		});
	};
    
    // billing address / checkout step 2
    
    $.cv.checkoutCustom.setBillingAddressFunctions = function() {
        $('.billing-address select').selectric();
        $("#continue-to-review-paypal,#continue-to-review-credit-card").click(function() {
            if (!$(this).hasClass("inactive-no-spinner")) {
                $.cv.checkoutCustom.continueToReview(); 
            }
        });
        $("#same-billing-address").change(function() {
            if($("#same-billing-address").is(':checked')) {
                $.cv.checkoutCustom.setBillingAddress();
            } else {
                $.cv.checkoutCustom.clearBillingAddress();
            }
        });
    };
    
    $.cv.checkoutCustom.updateReviewBillingAddress = function() {
        $.cv.checkoutCustom.updateReviewAddressField("#billing-name-review",$("input[name='CN_BillingName']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-address-1-review",$("input[name='CN_BillingDelAddr2']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-address-2-review",$("input[name='CN_BillingDelAddr3']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-postcode-review",$("input[name='CN_BillingPostcode']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-suburb-review",$("input[name='CN_BillingDelSuburb']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-state-review",$("select[name='CN_BillingDelState']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-country-review",$("select[name='CN_BillingCountry']").val());
        $.cv.checkoutCustom.updateReviewAddressField("#billing-phone-review",$("input[name='CN_BillingDelPhone']").val());
    };
    
    $.cv.checkoutCustom.disableBillingAddress = function() {
        $('.billing-address select, .billing-address input:not(#same-billing-address)').attr("disabled","disabled");
        $('.billing-address select').selectric('refresh');
    };
    
    $.cv.checkoutCustom.enableBillingAddress = function() {
        $('.billing-address select, .billing-address input:not(.cv-dropdown-country)').removeAttr("disabled");
        $('.billing-address select').selectric('refresh');
    };
    
    $.cv.checkoutCustom.setBillingAddress = function() {
        var obs = [];
        obs.push({obj: $("input[name='CN_BillingName']").eq(0), val: $("input[name='SoDelAddr1']").val()});
        obs.push({obj: $("input[name='CN_BillingDelAddr2']").eq(0), val: $("input[name='SoDelAddr2']").val()});
        obs.push({obj: $("input[name='CN_BillingDelAddr3']").eq(0), val: $("input[name='SoDelAddr3']").val()});
        obs.push({obj: $("input[name='CN_BillingDelSuburb']").eq(0), val: $("input[name='SoDelSuburb']").val()});
        obs.push({obj: $("select[name='CN_BillingDelState']").eq(0), val: $("select[name='SoDelState']").val()});
        obs.push({obj: $("input[name='CN_BillingPostcode']").eq(0), val: $("input[name='SoDelPostcode']").val()});
		obs.push({obj: $("select[name='CN_BillingCountry']").eq(0), val: $("select[name='SoDelCountry']").val()});
        obs.push({obj: $("input[name='CN_BillingDelPhone']").eq(0), val: $("input[name='SoDelPhone']").val()});
        $.cv.checkoutCustom.copyAddressFieldsToBillingAddress(obs);
        $.cv.checkoutCustom.disableBillingAddress();
    };
    
    $.cv.checkoutCustom.clearBillingAddress = function() {
        var obs = [];
        obs.push({obj: $("input[name='CN_BillingName']").eq(0), val: ""});
        obs.push({obj: $("input[name='CN_BillingDelAddr2']").eq(0), val: ""});
        obs.push({obj: $("input[name='CN_BillingDelAddr3']").eq(0), val: ""});
        obs.push({obj: $("input[name='CN_BillingDelSuburb']").eq(0), val: ""});
        obs.push({obj: $("select[name='CN_BillingDelState']").eq(0), val: ""});
        obs.push({obj: $("input[name='CN_BillingPostcode']").eq(0), val: ""});
		obs.push({obj: $("select[name='CN_BillingCountry']").eq(0), val: ""});
        obs.push({obj: $("input[name='CN_BillingDelPhone']").eq(0), val: ""});
        $.cv.checkoutCustom.copyAddressFieldsToBillingAddress(obs);
        $.cv.checkoutCustom.enableBillingAddress();
    };
    
    $.cv.checkoutCustom.copyAddressFieldsToBillingAddress = function(obs) {
		$.cv.checkoutCustom.changeBillingAddressUpdates(false);
		$.each(obs, function(idx, item) {
			$.cv.checkoutCustom.copyAddressFieldToBillingAddress(item.obj, item.val);
		});
		$.cv.checkoutCustom.bulkChangeBillingAddress();
		$.cv.checkoutCustom.changeBillingAddressUpdates(true);
	};
    
    $.cv.checkoutCustom.changeBillingAddressUpdates = function(updateOnChange) {
		$(".grid.billing-address[data-role='ordercompletefields']").each(function() {
			var ocWidget = $(this).data("orderCompleteFields");
			if (ocWidget) {
					ocWidget.setUpdateOnChange(updateOnChange);
			}
		});
	};
    
    $.cv.checkoutCustom.copyAddressFieldToBillingAddress  = function(obj, val) {
		obj.val(val).change();
	};
    
    $.cv.checkoutCustom.bulkChangeBillingAddress = function() {
		$(".grid.billing-address[data-role='ordercompletefields']").each(function() {
			var ocWidget = $(this).data("orderCompleteFields");
			if (ocWidget) {
					ocWidget.updateOrderCompleteFields();
			}
		});
	};
    
    $.cv.checkoutCustom.continueToReview = function() {
        $.cv.checkoutCustom.validateInputFields(".grid.billing-address","orderCompleteFields");
        if (!_.contains(_.keys($.cv.css.pageValidationErrors), "orderCompleteFields")) {
            $.cv.checkoutCustom.showStep("3");
        }
    };
    
    // payment options / checkout step 2
    
    $.cv.checkoutCustom.setPaymentOptionEvents = function() {
        $("input[name='payment-types']").change(function () {
            $(this).val() == "paypal" ? $("#payment-method-review").text("Paypal") : $("#payment-method-review").text("Credit Card");
        });
		$("#card-name").keyup(function(event) {
		  if (event.which == 13) {
			  // stops the form from submitting when using the widget on a page that has form submit buttons
			  event.preventDefault();
			  event.stopPropagation();
			  $("#continue-to-review-credit-card").click();
		  }
		});
        $.cv.checkoutCustom.setSubmitOrderEvent();
    };
    
    // submit order / checkout step 3
    
    $.cv.checkoutCustom.setSubmitOrderEvent = function() {
        $(".review-submit-order").click(function() {
            var widget;
            $(".payment-method[data-role='paymentoptions']").each(function () {
                widget = $(this).data("paymentOptions");
                if (widget) {
                    if ($.isFunction(widget.submitOrder))
                        widget.submitOrder();
                }
            });
        });
    };

})(jQuery);;
;
(function ($, undefined) {
  $.cv = $.cv || {};
  $.cv.storeLocatorCustom = $.cv.storeLocatorCustom || {};
  
  $.cv.storeLocatorCustom.setAddressEvents = function(productCode) {
    $("#location-search-postcode").change(function() {
      $("#location-search-suburb").val("");
      $("#location-search-state").val("");
    });
    
    $("#location-search-suburb,#location-search-state").change(function() {
      console.log($("#location-search-postcode").val());
      $("#location-search-postcode").val("");
    });
  }
  
})(jQuery);;
(function ($, undefined) {
  $.cv.css.isProcessingClass = "inactive";
  $.cv.css.messageTypes = { error: 'alert-box error cv-ico-general-minus2', warning: 'alert-box warning cv-ico-general-warning', info: 'alert-box info cv-ico-general-info2', success: 'alert-box success cv-ico-general-checkmark' };
  $.cv.css.userFavourites.defaults.returnMessageOnAddFavourite = true;
    $.cv.ajax.setup({timeoutRedirectUrl: "/login", timeoutRedirectMessage: "Your session has timed out, please log in again."});
})(jQuery);

function hidedialog () {
        $( "#dialog1" ).dialog("close");                          
};

$(document).ready(function() {
    /* widgets */
    kendo.init("body");

    /* blind to search event    */ 
    $("#textbox-topnav-search").keypress(function( event ) {
        if ( event.which == 13 ) {
            $("form").submit(function() {
              return false;
            });
            $("#button-topnav-search").click();
        }
    });
    
    $("#textbox-topnav-search").keyup(function( event ) {
        if ( event.which == 13 ) {
            $("form").submit(function() {
              return false;
            });
            $("#button-topnav-search").click();
        }
    });
        
    /* menu */                                        
    if($.isFunction($.fn.dlmenu)) {
        $('#dl-menu').dlmenu();
    }
    $(".dummy-button").bind("click",function(event){
        // stops the form from submitting on enter
        // require to stop the mobile menu opening when using the keyboard input submitting a value
        event.preventDefault();
        event.stopPropagation();
    })   


    /* -------------------------------------------------------------------- *\
        FITVIDS - Responsive Video
         + http://fitvidsjs.com/
    \* -------------------------------------------------------------------- */
    $(".video").fitVids();

    /* -------------------------------------------------------------------- *\
        Pop Up Light Box on Front Page         
    \* -------------------------------------------------------------------- */
    $( "#dialog1" ).dialog({ modal: true, 
                                height: 415,
                                width: 650,
                                draggable: false,
                                resizable: false,
                                dialogClass: "noTitleStuff",
                                hide: { effect: "explode", duration:1000 }
                            });

    $(".ui-dialog-titlebar").hide();


    /* -------------------------------------------------------------------- *\
        NIVO SLIDER
         + http://nivo.dev7studios.com/support/jquery-plugin-usage/
    \* -------------------------------------------------------------------- */
    $('#homepage-slider').nivoSlider({
        effect: 'fade', // Specify sets like: 'fold,fade,sliceDown'
        slices: 15, // For slice animations
        boxCols: 8, // For box animations
        boxRows: 4, // For box animations
        animSpeed: 500, // Slide transition speed
        pauseTime: 5000, // How long each slide will show
        startSlide: 0, // Set starting Slide (0 index)
        directionNav: false, // Next & Prev navigation
        controlNav: true, // 1,2,3... navigation
        controlNavThumbs: false, // Use thumbnails for Control Nav
        pauseOnHover: true, // Stop animation while hovering
        manualAdvance: false, // Force manual transitions
        prevText: 'Prev', // Prev directionNav text
        nextText: 'Next', // Next directionNav text
        randomStart: false // Start on a random slide
    });



    /* -------------------------------------------------------------------- *\
        PIKACHOOSE - Product Detail Image Gallery
         + http://www.pikachoose.com/
    \* -------------------------------------------------------------------- */
    $("#product-detail-gallery").PikaChoose({
        carousel: true,
        autoPlay: false,
        showCaption: false
    });


    /* -------------------------------------------------------------------- *\
        FANCYBOX 2 - Modal Windows
         + http://fancyapps.com/fancybox/
    \* -------------------------------------------------------------------- */
    $('.fancybox').fancybox({
        topRatio: '0.25'
    });
    $('.fancybox-noclose').fancybox({
        closeBtn: false,
        topRatio: '0.25'
    });
    $('.fancybox-processing').fancybox({
        closeBtn: false,
        topRatio: '0.25'
    });
    $('.fancybox-tryon').fancybox({
        closeBtn: true,
        autoSize: false,
        topRatio: '0.25',
        width: 970,
        height: 430
    });
    // Regions popup - Front Page
    $(".regions").click(function() {
        $.fancybox({
            'href' : '#regions',
            'type' : 'inline'
        });
    return false;
    }) 


    /* -------------------------------------------------------------------- *\
        jQUERY UI TABS
         + http://jqueryui.com/tabs/
    \* -------------------------------------------------------------------- */
    $("#tabs").tabs();

    

    /* -------------------------------------------------------------------- *\
        SELECTRIC - Custom Dropdowns
         + http://lcdsantos.github.io/jQuery-Selectric/index.html
    \* -------------------------------------------------------------------- */
    $(function(){
        $('.cv-dropdown').selectric();
    });


    /* -------------------------------------------------------------------- *\
        Promo banner
    \* -------------------------------------------------------------------- */
    $(".promo-holder .btn-close").click(function() {
      $(".promo-holder").slideUp();
    });
    /* -------------------------------------------------------------------- *\
        Nail Colours
    \* -------------------------------------------------------------------- */
    $("#show-more-nail-colours").click(function() {
      var _this = $(this);
      var oldText = _this.text();
      var newText = _this.data("toggleWording");
      _this.text(newText);
      _this.data("toggleWording",oldText);
      $(".hidden-nail").css("display",function(idx, oldDisplay) {
        return oldDisplay == "none" ? "inline-block" : "none";
      });
    });
    if ($("#colour-span").length > 0) {
      var originalColour = $("#colour-span").text();
      $(".nail-colours li").click(function() {
        $.cv.util.redirect($(this).find("a").attr("href"), null, false);
      });
      $(".nail-colours li").hover(
        function() {
          $("#colour-span").text($(this).find("a").text());
        },
        function() {
          $("#colour-span").text(originalColour);
        }
      );
    }
    
    /* -------------------------------------------------------------------- *\
        Share show/hide
    \* -------------------------------------------------------------------- */
    $("#share-list").hide();
    $( "#share-btn" ).click(function() {
      $( "#share-list" ).slideToggle();
    });


    /* -------------------------------------------------------------------- *\
        Product review show/hide
    \* -------------------------------------------------------------------- */
    $("#reviewform").hide();
    $( "#write-review, #leave-review" ).click(function() {
      $( "#reviewform" ).slideDown();
    });

    $( "#reviewform-close" ).click(function() {
      $( "#reviewform" ).slideUp();
    });
        
        if ($(".reviews .review").length <= 3) {
            $( ".more-reviews" ).hide();
        }
        $( ".more-reviews" ).click(function() {
            var reviews = $(".review.hidden-review").slice(0,3);
            if ($(".review.hidden-review").length <= 3) {
                $( ".more-reviews" ).hide();
            }
            reviews.slideDown("slow", function() {
                $(this).removeClass("hidden-review");
            });
    });
        
    /* -------------------------------------------------------------------- *\
        Product video
    \* -------------------------------------------------------------------- */
    var dataVideo = $("a[data-video]:first");
    if (dataVideo.length > 0) {
      var video = decodeURIComponent(dataVideo.data("video"));
      if (video.indexOf("autoplay") == -1) {
        video += "&autoplay=1";
      }
      dataVideo.addClass("fancybox.iframe");
      dataVideo.attr("href",video);
      dataVideo.fancybox({
          maxWidth  : 800,
          maxHeight : 600,
          fitToView : false,
          width     : '70%',
          height        : '70%',
          autoSize  : false,
          closeClick    : false,
          openEffect    : 'none',
          closeEffect   : 'none'
      });
      var galleryImage = $("#product-detail-gallery li:last img").clone();
      var newVid = dataVideo.clone();
      newVid.find("img").remove();
      var contents = $('<div>').append(newVid.append(galleryImage)).html(); 
      $("#product-detail-gallery").append("<li class='video'>" + contents + "</li>");
      var galleryItem = $("#product-detail-gallery .video");
      galleryItem.find("img").show();
      galleryItem.find("a").fancybox({
          maxWidth  : 800,
          maxHeight : 600,
          fitToView : false,
          width     : '70%',
          height        : '70%',
          autoSize  : false,
          closeClick    : false,
          openEffect    : 'none',
          closeEffect   : 'none'
      });
    }

    /* -------------------------------------------------------------------- *\
        INSTAGRAM
         + https://github.com/potomak/jquery-instagram
    \* -------------------------------------------------------------------- */
    function createPhotoElement(photo) {
      var innerHtml = $('<img>')
        .addClass('instagram-image')
        .attr('src', photo.images.thumbnail.url);

      innerHtml = $('<a>')
        .attr('target', '_blank')
        .attr('href', photo.link)
        .append(innerHtml);

      return $('<div>')
        .addClass('instagram-placeholder')
        .attr('id', photo.id)
        .append(innerHtml);
    }

    function didLoadInstagram(event, response) {
      var that = this;

      $.each(response.data, function(i, photo) {
        $(that).append(createPhotoElement(photo));
      });
    }

    $(document).ready(function() {
      var clientId = '79462827bc9549e1965bde599a4c7484';
      
      $('.instagram.tag').on('didLoadInstagram', didLoadInstagram);
      $('.instagram.tag').instagram({
        hash: 'imsensationail',
        count: 6,
        clientId: clientId
      });
    });
    
    /* -------------------------------------------------------------------- *\
        My Account
    \* -------------------------------------------------------------------- */
    
    $("#user-delivery-information").hide();
    $(".change-pwd").hide();
    $(".menu-my-info").click(function() {
      $("#user-delivery-information,.change-pwd").hide();
      $("#user-contact-information").show();
      $(".my-account-nav.no-mobile li").removeClass("active");
      $(this).parent().addClass("active");
    });
    $(".menu-address-book").click(function() {
      $("#user-contact-information,.change-pwd").hide();
      $("#user-delivery-information").show();
      $(".my-account-nav.no-mobile li").removeClass("active");
      $(this).parent().addClass("active");
    });
    $(".menu-change-password").click(function() {
      $("#user-contact-information,#user-delivery-information").hide();
      $(".change-pwd").show();
      $(".my-account-nav.no-mobile li").removeClass("active");
      $(this).parent().addClass("active");
    });
    $("#my-account-select").change(function() {
      if ($(this).val().indexOf("/") > -1) {
        $.cv.util.redirect($(this).val(), {}, false);
      } else {
        if ($(this).val().indexOf("logout") > -1) {
          $.cv.css.logout();
        } else {
          $("#user-contact-information,#user-delivery-information,.change-pwd,.vip").hide();
          $("." + $(this).val()).show();
        }
      }
    });
    
    /* -------------------------------------------------------------------- *\
        Order Tracking
    \* -------------------------------------------------------------------- */
    
    var trackOrders = function() {
      if($('#formOrderSearchL,#formOrderSearch').length > 0) {
        $('#formOrderSearchL,#formOrderSearch').submit(function() {
          if($('#cvfTrackPreviousOrderSelectionCriteriaTable').length == 0) {
            $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrder);
            $.cv.css.removeLocalStorage($.cv.css.localStorageKeys.currentOrderLines);
          }
        });
      }
    }
    trackOrders();
    
    /* -------------------------------------------------------------------- *\
        Store Locator
    \* -------------------------------------------------------------------- */
    $.cv.storeLocatorCustom.setAddressEvents();

});






;
/**
 * jquery.dlmenu.js v1.0.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2013, Codrops
 * http://www.codrops.com
 */
;( function( $, window, undefined ) {

	

	// global
	var Modernizr = window.Modernizr, $body = $( 'body' );

	$.DLMenu = function( options, element ) {
		this.$el = $( element );
		this._init( options );
	};

	// the options
	$.DLMenu.defaults = {
		// classes for the animation effects
		animationClasses : { in : 'dl-animate-in-1', out : 'dl-animate-out-1' }
	};

	$.DLMenu.prototype = {
		_init : function( options ) {

			// options
			this.options = $.extend( true, {}, $.DLMenu.defaults, options );
			// cache some elements and initialize some variables
			this._config();
			
			var animEndEventNames = {
					'WebkitAnimation' : 'webkitAnimationEnd',
					'OAnimation' : 'oAnimationEnd',
					'msAnimation' : 'MSAnimationEnd',
					'animation' : 'animationend'
				},
				transEndEventNames = {
					'WebkitTransition' : 'webkitTransitionEnd',
					'MozTransition' : 'transitionend',
					'OTransition' : 'oTransitionEnd',
					'msTransition' : 'MSTransitionEnd',
					'transition' : 'transitionend'
				};
			// animation end event name
			this.animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ] + '.dlmenu';
			// transition end event name
			this.transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ] + '.dlmenu',
			// support for css animations and css transitions
			this.supportAnimations = Modernizr.cssanimations,
			this.supportTransitions = Modernizr.csstransitions;

			this._initEvents();

		},
		_config : function() {
			this.open = false;
			this.$trigger = this.$el.children( 'button' );
			this.$menu = this.$el.children( 'ul.dl-menu' );
			this.$menuitems = this.$menu.find( 'li:not(.dl-back)' );
			this.$back = this.$menu.find( 'li.dl-back' );
		},
		_initEvents : function() {

			var self = this;

			this.$trigger.on( 'click.dlmenu', function() {
				
				if( self.open ) {
					self._closeMenu();
				} 
				else {
					self._openMenu();
					// clicking somewhere else makes the menu close
					$body.off( 'click' ).on( 'click.dlmenu', function() {
						self._closeMenu() ;
					} );
					
				}
				return false;

			} );

			this.$menuitems.on( 'click.dlmenu', function( event ) {
				
				event.stopPropagation();

				var $item = $(this),
					$submenu = $item.children( 'ul.dl-submenu' );

				if( $submenu.length > 0 ) {

					var $flyin = $submenu.clone().insertAfter( self.$menu ).addClass( self.options.animationClasses.in ),
						onAnimationEndFn = function() {
							self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.out ).addClass( 'dl-subview' );
							$item.addClass( 'dl-subviewopen' ).parents( '.dl-subviewopen:first' ).removeClass( 'dl-subviewopen' ).addClass( 'dl-subview' );
							$flyin.remove();
						};

					self.$menu.addClass( self.options.animationClasses.out );

					if( self.supportAnimations ) {
						self.$menu.on( self.animEndEventName, onAnimationEndFn );
					}
					else {
						onAnimationEndFn.call();
					}

					return false;

				}
			} );

			this.$back.on( 'click.dlmenu', function( event ) {
				
				var $this = $( this ),
					$submenu = $this.parents( 'ul.dl-submenu:first' ),
					$item = $submenu.parent(),


					$flyin = $submenu.clone().insertAfter( self.$menu ).addClass( self.options.animationClasses.out );

				var onAnimationEndFn = function() {
					self.$menu.off( self.animEndEventName ).removeClass( self.options.animationClasses.in );
					$flyin.remove();
				};

				self.$menu.addClass( self.options.animationClasses.in );

				if( self.supportAnimations ) {
					self.$menu.on( self.animEndEventName, onAnimationEndFn );
				}
				else {
					onAnimationEndFn.call();
				}

				$item.removeClass( 'dl-subviewopen' );
				
				var $subview = $this.parents( '.dl-subview:first' );
				if( $subview.is( 'li' ) ) {
					$subview.addClass( 'dl-subviewopen' );
				}
				$subview.removeClass( 'dl-subview' );

				return false;

			} );
			
		},
		_closeMenu : function() {
			var self = this,
				onTransitionEndFn = function() {
					self.$menu.off( self.transEndEventName );
					self._resetMenu();
				};
			
			this.$menu.removeClass( 'dl-menuopen' );
			this.$menu.addClass( 'dl-menu-toggle' );
			this.$trigger.removeClass( 'dl-active' );
			
			if( this.supportTransitions ) {
				this.$menu.on( this.transEndEventName, onTransitionEndFn );
			}
			else {
				onTransitionEndFn.call();
			}

			this.open = false;
		},
		_openMenu : function() {
			this.$menu.addClass( 'dl-menuopen dl-menu-toggle' ).on( this.transEndEventName, function() {
				$( this ).removeClass( 'dl-menu-toggle' );
			} );
			this.$trigger.addClass( 'dl-active' );
			this.open = true;
		},
		// resets the menu to its original state (first level of options)
		_resetMenu : function() {
			this.$menu.removeClass( 'dl-subview' );
			this.$menuitems.removeClass( 'dl-subview dl-subviewopen' );
		}
	};

	var logError = function( message ) {
		if ( window.console ) {
			window.console.error( message );
		}
	};

	$.fn.dlmenu = function( options ) {
		if ( typeof options === 'string' ) {
			var args = Array.prototype.slice.call( arguments, 1 );
			this.each(function() {
				var instance = $.data( this, 'dlmenu' );
				if ( !instance ) {
					logError( "cannot call methods on dlmenu prior to initialization; " +
					"attempted to call method '" + options + "'" );
					return;
				}
				if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
					logError( "no such method '" + options + "' for dlmenu instance" );
					return;
				}
				instance[ options ].apply( instance, args );
			});
		} 
		else {
			this.each(function() {	
				var instance = $.data( this, 'dlmenu' );
				if ( instance ) {
					instance._init();
				}
				else {
					instance = $.data( this, 'dlmenu', new $.DLMenu( options, this ) );
				}
			});
		}
		return this;
	};

} )( jQuery, window );;
