Last active
November 12, 2015 06:29
-
-
Save Josh68/869d83ef4e46c8c49db4 to your computer and use it in GitHub Desktop.
FAQs viewmodel from myModa mobile application
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*jshint smarttabs:true, maxerr:1000, strict:false*/ | |
/*Sample FAQ code*/ | |
var loadViewModel = function(){ | |
//------------------------------------------------------------------------------ | |
// | |
// MenuItems : Object | |
// Data object for available menu items. | |
// | |
//------------------------------------------------------------------------------ | |
var MenuItems = function(data) { | |
var self = this; | |
var items = data ? data.split(",") : []; | |
console.log(items.indexOf("account") >= 0); | |
self.hasAccount = ko.observable(items.indexOf("account") >= 0); | |
self.hasBenefits = ko.observable(items.indexOf("benefits") >= 0); | |
self.hasClaims = ko.observable(items.indexOf("claims") >= 0); | |
self.hasEOBs = ko.observable(items.indexOf("eobs") >= 0); | |
self.hasFindCare = ko.observable(items.indexOf("findcare") >= 0); | |
self.hasIDCard = ko.observable(items.indexOf("idcard") >= 0); | |
self.hasODSWell = ko.observable(items.indexOf("odswell") >= 0); | |
}; | |
//------------------------------------------------------------------------------ | |
// | |
// ViewModel : Object | |
// The main view model controlling this section. | |
// | |
//------------------------------------------------------------------------------ | |
var ViewModel = function(){ | |
var self = this; | |
self.currentUser = null; | |
self.currentCategory = ""; | |
/** | |
Observables | |
**/ | |
self.menuItems = ko.observable(new MenuItems()); | |
/*For SSO to full site*/ | |
self.fullSiteRootUrl = ko.computed(function(){ | |
var host = window.location.host, | |
firstDelimiter = host.indexOf("."), | |
fourthLevelDomain = host.substring(0,firstDelimiter), | |
domainMinusFourthLevel = host.substring(firstDelimiter); | |
if (fourthLevelDomain === "mobile") { | |
host = "www" + domainMinusFourthLevel; | |
} | |
return window.location.protocol + "//" + host; | |
}, this); | |
self.fullSiteLink = ko.computed(function(){ | |
return self.fullSiteRootUrl() + "/" + GLOBALS.fullSiteAppName + "/?site_preference=normal"; | |
}); | |
self.userName = ko.observable(""); | |
self.userPassword = ko.observable(""); | |
// Title in the navigation bar | |
self.navTitle = ko.observable("Frequently asked questions"); | |
// Title in the back button | |
self.backTitle = ko.observable("Back"); | |
self.historyIsValid = ko.computed(function(){ | |
var force = self.navTitle(); | |
var validPrevious = getParameterByNameFromHash("previous") && getParameterByNameFromHash("previous") != "undefined"; | |
// var validReferrer = getParameterByNameFromHash("referrer") === document.referrer; | |
// return validPrevious && validReferrer; | |
return validPrevious; | |
}, this); | |
self.setBackTitle = function(fallback) { | |
self.backTitle(self.historyIsValid() ? getParameterByNameFromHash("previous") : fallback ? fallback : "Back"); | |
}; | |
// Query string to add to outbound URL | |
self.historyString = ko.computed({ | |
read: function(){ | |
if(document.referrer && document.referrer !== null && typeof document.referrer !== 'undefined'){ | |
return "?previous=" + encodeURIComponent(self.navTitle()) + "&" + "referrer=" + encodeURIComponent(document.referrer); | |
} else { | |
return "?previous=" + encodeURIComponent(self.navTitle()); | |
} | |
}, | |
deferEvaluation: true, | |
owner: this | |
}); | |
/** | |
FindCare link in footer | |
**/ | |
self.memberKey = ko.observable(""); | |
self.subscriberKey = ko.observable(""); | |
self.groupKey = ko.observable(""); | |
self.dispType = ko.observable(""); | |
self.findCareLink = ko.computed(function(){ | |
return GLOBALS.domain + GLOBALS.findCarePath + "?memberKey=" + self.memberKey() + "&subscriberKey=" + self.subscriberKey() + | |
"&groupKey=" + self.groupKey() + "&asOfDate=" + GLOBALS.linkDate + "&dispType=" + self.dispType(); | |
}); | |
/** | |
Browser detection | |
**/ | |
self.isMobile = ko.observable(false); | |
self.detectBrowser = function(){ | |
var ua = navigator.userAgent; | |
self.checker = { | |
android: (/Android/).test(ua), | |
blackberry: (/BlackBerry/).test(ua), | |
ios: (/(iPhone|iPod)/).test(ua), | |
iphone: (/iPhone/).test(ua) | |
}; | |
// If this isn't a phone, don't link phone numbers. //not working | |
/*if (!self.checker.android || !self.checker.blackberry || !self.checker.iphone) { | |
$(".phone-number").find("a").each(function(){var me = $(this); me.replaceWith(me.text());}); | |
}*/ | |
// If this isn't a phone, don't link phone numbers. | |
self.isMobile(self.checker.android || self.checker.blackberry || self.checker.iphone); | |
// If this is an android, hide the back button | |
/* | |
if(self.checker.android){ | |
$('#navbar a').addClass('ui-hidden'); | |
} | |
*/ | |
}; | |
/** | |
App Installation | |
**/ | |
self.isInstalled = function(){ | |
return (("standalone" in window.navigator) && window.navigator.standalone); | |
}; | |
/** Exception Handling | |
Three scenarios covered: 401 (session expired), 404 (service missing), and general errors | |
Params contains: | |
- kill : throw an error and stop execution | |
- back : go back to a previous page | |
- TODO: silent : don't say or do anything (do we need something like this?) | |
**/ | |
self.handleException = function(jqXHR,params){ | |
// 401 - Special error, our session has just expired | |
// 401 - Special error, our session has just expired | |
if(jqXHR.status === 401 || jqXHR.status === 500) { //eating unauthorized as session expired, if this ever occurs | |
// Take us back to login - does not work with Sammy | |
window.location.href = GLOBALS.domain + "/" + GLOBALS.mobileAppName; | |
} | |
// Ignore an abort, since those can happen often | |
else if(jqXHR.status != 0) { | |
if(jqXHR.status === 404){ | |
$("#dialog-header").html("Service unavailable"); | |
$("#dialog-message").html("Unfortunately, this service is currently unavailable. Please try again soon."); | |
} | |
else { | |
$("#dialog-header").html("Error"); | |
try{ | |
var json = JSON.parse(jqXHR.responseText); | |
$("#dialog-message").html(json.error); | |
} | |
catch(e){ | |
$("#dialog-message").html(jqXHR.responseText); | |
} | |
} | |
$("#dialog-action .ui-btn-text").text("OK"); | |
$("#dialog-action").unbind(); | |
$("#dialog-action").bind('mouseup touchup',function(event){ | |
$("#dialog-popup").popup("close"); | |
event.preventDefault(); | |
if(params.back){ | |
window.history.go(-2); | |
} | |
}); | |
$("#dialog-popup").popup("open"); | |
if(params.kill){ | |
$.mobile.hidePageLoadingMsg(); | |
throw "Error"; | |
} | |
} | |
}; | |
/** | |
* isOfficeHours : BOOL | |
* Is Moda Health Custormer Service currently open? | |
*/ | |
self.isOfficeHours = ko.observable(false); | |
self.show = function(element, effect, speed){ | |
// $(element).show(effect || "slide", {direction: "right"}, speed || "slow"); | |
var elementClass = element.slice(1) + "-page"; | |
$(".page").hide().parents("body").removeClass(function(i, c){ | |
var classes = c.split(' '), | |
remove = []; | |
$.each(classes, function(){ | |
if(this.match(/-page/)){ | |
remove.push(this); | |
} | |
}); | |
return remove.join(' '); | |
}); | |
$("body").addClass(elementClass).scrollTop(0); | |
if($(element).hasClass("hasnav")){ | |
$("#navbar").show(); | |
} | |
else { | |
$("#navbar").hide(); | |
} | |
$(element).show(0, function(){ | |
var category = element.slice(1); | |
if ($(element).is("[data-bind='attr:{id:faqType}']")){ //callback to re-show opened FAQ on back | |
showCollapsible(category); // global function to show a previously shown collapsible, stored in a cookie | |
} | |
$(this).trigger('pageshown'); | |
}); | |
}; | |
/** | |
* isLoggedIn : BOOL | |
* Is a myModa member currently logged in? | |
*/ | |
self.loginUpdate = ko.observable(); | |
self.isLoggedIn = ko.computed(function(){ | |
self.loginUpdate(); // triggers reevaluation | |
if(!self.dataModel){ | |
return false; | |
}else{ | |
console.log("Logged in = " + (self.dataModel.isExpired() ? false : true)); | |
return self.dataModel.isExpired() ? false : true; | |
} | |
}, this); | |
//-------------------------------------- | |
// FAQs | |
//-------------------------------------- | |
var Faq = function(question, answer) { | |
this.question = question; | |
this.answer = answer; | |
}; | |
self.faqTitle = ko.observable(""); | |
self.isFromAllFaqs = ko.computed(function(){ | |
var myWatcher = self.navTitle(); | |
if (getParameterByNameFromHash("link") === "fromAll") { | |
return true; | |
} else { | |
return false; | |
} | |
}, this); | |
self.faqType = ko.computed(function(){ | |
return self.faqTitle().toLowerCase(); | |
}, this); | |
self.faqsList = ko.mapping.fromJS([]); | |
self.faqCategoryList = ko.mapping.fromJS({ | |
"vision":"Yes", | |
"medical":"Yes", | |
"dental":"Yes", | |
"pharmacy":"Yes", | |
"customerservice":"Yes" | |
}); | |
self.hasMedical = ko.computed(function(){ | |
console.log("hasMedical: " + (self.faqCategoryList.medical() === "Yes")); | |
return (self.faqCategoryList.medical() === "Yes"); | |
}, this); | |
self.hasDental = ko.computed(function(){ | |
console.log("hasDental: " + (self.faqCategoryList.dental() === "Yes")); | |
return (self.faqCategoryList.dental() === "Yes"); | |
}, this); | |
self.hasVision = ko.computed(function(){ | |
console.log("hasVision: " + (self.faqCategoryList.vision() === "Yes")); | |
return (self.faqCategoryList.vision() === "Yes"); | |
}, this); | |
self.hasPharmacy = ko.computed(function(){ | |
console.log("hasDental: " + (self.faqCategoryList.pharmacy() === "Yes")); | |
return (self.faqCategoryList.pharmacy() === "Yes"); | |
}, this); | |
self.getFaqList = function(callback) { | |
$.ajax({ | |
type: "GET", | |
contentType: "application/json", | |
url: GLOBALS.domain + "/" + GLOBALS.appName + "/" + GLOBALS.servicePathName + "/contactUsLinks", | |
dataType: "json", | |
data: "", | |
success: function(data, textStatus, jqXHR){ | |
ko.mapping.fromJS(data, self.faqCategoryList); | |
callback(); | |
}, | |
error: function(jqXHR, textStatus, errorThrown){ | |
self.handleException(jqXHR,{kill:true, back:true}); | |
return false; | |
} | |
}); | |
}; | |
self.getFaqData = function(serviceSubpath, callback) { //called with the subPath for each type of FAQ (set) and a callback that handles utility tasks (setting titles, showing things, etc) | |
console.log("AJAX getFaqData"); | |
$.ajax({ | |
type: "GET", | |
contentType: "application/json", | |
url: GLOBALS.domain + "/" + GLOBALS.appName + "/" + GLOBALS.servicePathName + "/faq" + serviceSubpath, | |
dataType: "json", | |
data: "", | |
success: function(data, textStatus, jqXHR){ | |
var jsonString = JSON.stringify(data.faq); | |
var subscriberID = self.dataModel.getField({ table:'current_member', field:'subscriberId', encrypt:true }); | |
var memberKey = self.dataModel.getField({ table:'current_member', field:'memberKey', encrypt:true }); | |
var pathArray = window.location.pathname.split("/"); | |
pathArray = pathArray.slice(0, pathArray.length - 2); | |
var parentPath = pathArray.join("/"); | |
jsonString = jsonString.replace(/href='..\//g, "href='" + window.location.protocol + "//" + window.location.host + parentPath + "/"); | |
jsonString = jsonString.replace(/%%category%%/g, self.currentCategory); | |
jsonString = jsonString.replace(/%%memberKey%%/g, memberKey); | |
jsonString = jsonString.replace(/%%subscriberID%%/g, subscriberID); | |
jsonString = jsonString.replace(/%%historyString%%/g, self.historyString()); | |
jsonString = jsonString.replace(/%%fullSiteLink%%/g, self.fullSiteLink()); | |
// Fill up the FAQ array | |
ko.mapping.fromJS(JSON.parse(jsonString), self.faqsList); | |
callback(); //the utility tasks callback (see bottom of viewModel) | |
}, | |
error: function(jqXHR, textStatus, errorThrown){ | |
self.handleException(jqXHR,{kill:false, back:true}); | |
return false; | |
} | |
}); | |
}; | |
self.prepopulate = function(){ | |
if(!self.dataModel.isExpired()){ | |
$("[name=SubscriberId]").each(function(){ | |
$(this).val(self.dataModel.getField({table:'current_member',field:'subscriberId',encrypt:true})); | |
}); | |
$("[name=phone_number]").each(function(){ | |
$(this).val(self.dataModel.getField({table:'current_contact',field:'telephone',encrypt:true})); | |
}); | |
$("[name=eMail_address]").each(function(){ | |
$(this).val(self.dataModel.getField({table:'current_contact',field:'email',encrypt:true})); | |
}); | |
} | |
}; | |
self.router = Sammy("body", function(){ | |
this.disable_push_state = true; | |
this.use('GoogleAnalytics'); | |
this.after(function(){ //trigger a custom event available outside the viewmodel when any route is complete | |
$(document).trigger('routeComplete'); | |
}); | |
//-------------------------------------- | |
// The around filter. | |
//-------------------------------------- | |
function navEvent(callback){ | |
/* | |
* body section class | |
*/ | |
var firstSlash = self.router.getLocation().slice(1).indexOf('/') + 1, | |
partialPath = self.router.getLocation().slice(1).substring(firstSlash), | |
secondSlash = self.router.getLocation().slice(1).substring(firstSlash).indexOf('/'), | |
firstPathString = partialPath.substring(0, secondSlash); | |
sectionClass = firstPathString + '-section'; | |
$("body").removeClass(function(i, c){ | |
var classes = c.split(' '), | |
remove = []; | |
$.each(classes, function(){ | |
if(this.match(/-section/)){ | |
remove.push(this); | |
} | |
}); | |
return remove.join(' '); | |
}); | |
if(firstPathString !== '#'){ | |
$('body').addClass(sectionClass); | |
} | |
var state = this.path.split("#/")[1]; | |
var pageID = '#' + state; | |
if (state === "") { | |
$("#home-footer").removeClass("secondary"); | |
} else { | |
$("#home-footer").addClass("secondary"); | |
} | |
$("#dialog").dialog("close"); | |
// close flyout menu if it's been left visible | |
$('#expandable').trigger('close'); | |
/*SSO for myModa full site*/ | |
$('#menu-full-site, .full-site-link').unbind().off(); | |
$('body').on('vmousedown', '#menu-full-site, .full-site-link', function(event){ | |
event.preventDefault(); | |
event.stopImmediatePropagation(); | |
if(self.isLoggedIn()){ | |
GLOBALS.externalLink = false; | |
self.userName(self.dataModel.getField({table:"current_user",field:"login",encrypt:true})); | |
self.userPassword(self.dataModel.getField({table:"current_user",field:"password",encrypt:true})); | |
document.fullSiteForm.submit(); | |
if($('#expandable').is(':visible')){ | |
$('#expandable').trigger('close'); | |
} | |
} else { | |
window.location.href = self.fullSiteLink(); | |
} | |
}); | |
if(String(self.dataModel.isExpired()) === "false"){ //if logged in, ping myModa full site to keep session | |
var keepFullSiteSessionAlive = (function() { //Thanks, Matt | |
var now = new Date().getTime(); | |
$('img.session-keeper').remove(); | |
//hit the server and return a 1x1 transparent gif | |
//append a timestamp query string so each request is unique, | |
//i.e., so we always hit the server, not a cached gif | |
$('<img class="ui-hidden session-keeper" src="' + GLOBALS.fullSiteDomain + '/' + GLOBALS.appName + '/images/spacer.gif?time=' + | |
now + '" height="0" width="0" border="0">').appendTo('#indexPage'); | |
}()); | |
} | |
// Everything is noauth on this page, so I just trimmed the logic | |
console.log("Setting state to: " + state); | |
callback(); | |
} | |
this.around(navEvent); | |
//-------------------------------------- | |
// 404 | |
//-------------------------------------- | |
this.notFound = function notFound(verb, path) | |
{ | |
if (path === "/faq/" || path === "/" + GLOBALS.mobileAppName + "/faq/" || path === "/" + GLOBALS.appName + "/faq/") { | |
//history.back(); | |
self.router.setLocation("#/" + self.historyString()); | |
} | |
console.log("Uh oh. Sammy 404: " + verb + " " + path); | |
return; | |
}; | |
//-------------------------------------- | |
// Naked URL | |
//-------------------------------------- | |
this.get("#", function(context){ | |
context.redirect("#/all"); | |
}); | |
this.get("#/", function(context){ | |
var directory = this.path.split("#/")[0].split("/"); | |
directory = directory[directory.length-2]; | |
if (directory === "faq") { | |
context.redirect("#/all"); | |
} else { | |
self.router.unload(); | |
window.location.reload(true); | |
} | |
}); | |
this.get("#/all", function(){ | |
self.getFaqList(function(){ | |
self.show("#all"); | |
document.title = "FAQ | All FAQs | myModa Mobile"; | |
self.navTitle("Frequently asked questions"); | |
self.setBackTitle("Back"); | |
}); | |
}); | |
this.get("#/category/:name", function(context) { /*BEWARE - if you have path params (eg, :name, here) and that param is anything besides | |
what you have as a fixed, post-hash subpath ("all," above, is an actual div id), it will drop down into this function and get caught, | |
IF AND ONLY IF the subpath has a single level (eg, "#/:name"). This will create an infinite loop situation, where you can't get home. | |
For this reason, make your pages with params have multiple levels (eg, "#/page/:name"), with the first level being a fixed div id.*/ | |
var subpath = this.params['name'], | |
newSubpath = subpath; | |
if (subpath === "vision") { | |
newSubpath = "medical"; | |
} | |
if (subpath === "mymoda") { | |
newSubpath = "general"; | |
} | |
self.currentCategory = subpath === "mymoda" ? "general" : subpath; | |
switch (subpath) { | |
case "medical" : | |
self.faqTitle("Medical"); | |
break; | |
case "dental" : | |
self.faqTitle("Dental"); | |
break; | |
case "pharmacy" : | |
self.faqTitle("Pharmacy"); | |
break; | |
case "vision" : | |
self.faqTitle("Medical"); | |
break; | |
case "mymoda" : | |
self.faqTitle("myModa"); | |
break; | |
case "claims" : | |
self.faqTitle("Claims"); | |
break; | |
default : | |
self.currentCategory = ""; | |
context.redirect("#/"); | |
return; | |
} | |
self.getFaqData("/" + newSubpath, function(){ //vision does not return FAQs, instead using what's in "Medical" | |
$("div[data-role='collapsible-set']").trigger('create'); //Necessary to recreate the jQM accordion after KO magic, also necessary to use a template to insert the collapsibles into the set (see html) | |
self.show("#" + (newSubpath === "general" ? "mymoda" : newSubpath)); //vision does not return FAQs, instead using what's in "Medical" | |
document.title = "FAQ | " + toTitleCase(self.faqTitle()) + " FAQs | myModa Mobile"; | |
self.navTitle(self.faqTitle() + " FAQs"); | |
self.setBackTitle("Back"); | |
}); | |
$('#allFaqs').off(); | |
$(document).on('vmouseup', '#allFaqs', function(e){ | |
self.router.setLocation("#/all" + self.historyString()); | |
//window.location = '#/all' + self.historyString(); | |
}); | |
}); | |
}); | |
// Configure JQuery UI objects | |
self.configureUI = function(){ | |
self.detectBrowser(); | |
self.loginUpdate(true); | |
}; | |
// Run once initializations | |
self.init = function(){ | |
try { | |
self.menuItems(new MenuItems(self.dataModel.getField({table: "current_user", field: "menu_items", encrypt: false}))); | |
} catch (error) { | |
// Relax. | |
} | |
self.router.run("#/"); | |
self.configureUI(); | |
}; | |
// Instantiate database and start app | |
self.dataModel = DataModel.getInstance(); | |
self.dataModel.initDatabase({success: function(){ | |
self.init(); | |
}}); | |
}; // End ViewModel | |
ko.applyBindings(new ViewModel()); | |
}; | |
var DataModel = { | |
getInstance: function(){ | |
return true; | |
} | |
initDatabase: function(){ | |
$.get('https://www.modahealth.com/mymoda/rest/contactUs', function(data){ | |
console.log(data.officeHour); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment