PageSourceSearch

https://wx.iwindsurf.com/ScriptResource.axd?d=44st3BFAY6Rk1ITKF_hN…3rup23hEsuVvL4FZkVUHWkz7l_0TqDz3KrFPoTD7Ng2

js iwindsurf.com collected 2026-09-24 09:34:25 UTC 171,373 bytes, 4,457 lines download raw bytes

1function isNumber(n) {
2    return !isNaN(parseFloat(n)) && isFinite(n);
3}
4
5function debouncer(func, timeout) {
6    var timeoutID,
7        timeout = timeout || 200;
8    return function () {
9        var scope = this,
10            args = arguments;
11        clearTimeout(timeoutID);
12        timeoutID = setTimeout(function () {
13            func.apply(scope, Array.prototype.slice.call(args));
14        }, timeout);
15    };
16}
17
18var JSON;JSON||(JSON={});
19(function(){function k(a){return a<10?"0"+a:a}function o(a){p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function(a){var c=r[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function l(a,j){var c,d,h,m,g=e,f,b=j[a];b&&typeof b==="object"&&typeof b.toJSON==="function"&&(b=b.toJSON(a));typeof i==="function"&&(b=i.call(j,a,b));switch(typeof b){case "string":return o(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if(!b)return"null";
20e+=n;f=[];if(Object.prototype.toString.apply(b)==="[object Array]"){m=b.length;for(c=0;c<m;c+=1)f[c]=l(c,b)||"null";h=f.length===0?"[]":e?"[\n"+e+f.join(",\n"+e)+"\n"+g+"]":"["+f.join(",")+"]";e=g;return h}if(i&&typeof i==="object"){m=i.length;for(c=0;c<m;c+=1)typeof i[c]==="string"&&(d=i[c],(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h))}else for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h);h=f.length===0?"{}":e?"{\n"+e+f.join(",\n"+e)+"\n"+g+"}":"{"+f.join(",")+
21"}";e=g;return h}}if(typeof Date.prototype.toJSON!=="function")Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+k(this.getUTCMonth()+1)+"-"+k(this.getUTCDate())+"T"+k(this.getUTCHours())+":"+k(this.getUTCMinutes())+":"+k(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()};var q=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
22p=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,e,n,r={"\u0008":"\\b","\t":"\\t","\n":"\\n","\u000c":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},i;if(typeof JSON.stringify!=="function")JSON.stringify=function(a,j,c){var d;n=e="";if(typeof c==="number")for(d=0;d<c;d+=1)n+=" ";
22else typeof c==="string"&&(n=c);if((i=j)&&typeof j!=="function"&&(typeof j!=="object"||typeof j.length!=="number"))throw Error("JSON.stringify");return l("",
23{"":a})};if(typeof JSON.parse!=="function")JSON.parse=function(a,e){function c(a,d){var g,f,b=a[d];if(b&&typeof b==="object")for(g in b)Object.prototype.hasOwnProperty.call(b,g)&&(f=c(b,g),f!==void 0?b[g]=f:delete b[g]);return e.call(a,d,b)}var d,a=String(a);q.lastIndex=0;q.test(a)&&(a=a.replace(q,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
24"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return d=eval("("+a+")"),typeof e==="function"?c({"":d},""):d;throw new SyntaxError("JSON.parse");}})();
25
26function loadModalManager() {    
27
28    var modalManagerObjectOptions = {
29        "token": token,
30        "tokenDomain": tokenDomain,        
31        "userSignedIn": userSignedIn,
32        "userSignInUrl": signInUrl,
33        "siteName": siteName,
34        "upsellMsgShown": function (featureId) {
35            if (typeof trackUpsellPageView !== "function") return;
36
37            trackUpsellPageView(featureId);
38        },
39        "isMobile": isMobile()
40    };
41
42    wfModalManager = new WFModalManager(modalManagerObjectOptions);
43
44    $("body").bind("profile_clicked", function (event, profileId) {
45        if (profileId != undefined && profileId > 0) {
46            window.location = "/profile/" + profileId;
47        } else {
48            window.location = "/profile";
49        }
50    });
51}
52
53function loadProfileManager() {
54    // Click event for add to profile
55    $(document).on("click", ".createProfileShow", function () {
56        if (wfModalManager.doesUserHaveAccessToFeature(WFProducts.ProfileFeature)) {
57            if (wfModalManager != null) {
58                wfModalManager.addProfile();
59            }
60        }
61
62        return false;
63    });
64
65    $("body").bind("profiles_loaded", function (event) {
66        // TODO: Update the profile list in the header
67        updateProfileMenuList();
68
69        // Handle fav stars
70        switch (currentPage) {
71            case "Favorites":
72                if (wfProfileManager.isProfileFavorited(currentProfileID)) {
73                    $("#profileFavButton").removeClass("favorite").addClass("favorite-yellow");
74                } else {
75                    $("#profileFavButton").removeClass("favorite-yellow").addClass("favorite");
76                }
77                break;
78            case "ManageFavorites":
79                updateProfileManagerList();
80                break;
81        }
82
83    });
84
85    // Create the profile manager object.  Singleton.   One instance per page
86    wfProfileManager = new WFProfileManager({ "activeProfileId": defaultProfileId, "token": token, "cad": showAds, "baseURL": apiurl, "userSignedIn": userSignedIn, "tokenDomain": tokenDomain });
87    wfProfileManager.init();      
88}
89
90function updateProfileMenuList() {    
91    // UL element in the header
92    var profileList = $(".profileList-profiles");
93    var profiles = wfProfileManager.getProfiles();
94    var profileClass = "profile-item";
95    
96    // Clear the list
97    $(".profile-item", profileList).remove();
98
99    for (var x = 0; x < profiles.length; x++) {
100        var liClass = profiles[x].master == "true" ? "immuteable rounded-corners " + profileClass : "rounded-corners " + profileClass;
101        var profileLink = "/profile";
102        if (profiles[x].profile_id > 0) {
103            profileLink += "/" + profiles[x].profile_id;
104        }
105        profileList.append("<li class='" + liClass + "' id='" + profiles[x].profile_id + "'><a href='" + profileLink + "' class='name'>" + wfProfileManager.getProfileDisplayName(profiles[x].name) + "</a></li>");
106    }
107
108    // Pro Forecasts
109    profileList.append("<li class='" + profileClass + "'><a href='/proforecasts'>Pro Forecasts</a></li>");
110    profileList.append("<li class='" + profileClass + "'><a href='/storm'>StormTrack</a></li>");
111
112    if (siteName == "iWindsurf") {
113        profileList.append("<li class='" + profileClass + "'><a href='http://www.iwindsurf.com/forums.iws'>iWindsurf Forums</a></li>");
114        profileList.append("<li class='" + profileClass + "'><a href='http://www.iwindsurf.com/classifieds.iws'>iWindsurf Classifieds</a></li>");
115    }
116
117    // Wind Meters
118    profileList.append("<li class='" + profileClass + "'><a href='http://weatherflow.com/windmeter/?utm_source=" + siteName + "&utm_medium=Web&utm_campaign=MyWeather-Menu' class='js-menu-link' target='_blank'>Shop: Wind Meters</a></li>");
119
120    if (primaryActivity == "Kite" && (siteName == "iKitesurf" || siteName == "WindAlert")) {
121        profileList.append("<li class='" + profileClass + "'><a href='https://www.amazon.com/iKitesurf-Rash-Guard-Lycra-Medium/dp/B01JGRFHGE/' class='js-menu-link' target='_blank'>Shop: iKitesurf Rash Guard</a></li>");
122        profileList.append("<li class='" + profileClass + "'><a href='http://www.zazzle.com/ikitesurf_merch/products' class='js-menu-link' target='_blank'>Shop: iKitesurf Apparel</a></li>");
123        profileList.append("<li class='" + profileClass + "'><a href='http://c.ikitesurf.com/schools' class='js-menu-link'>iKitesurf Schools</a></li>");
124    }
125
126    // Default items appended to the end of the sub menu    
127    profileList.append("<li class='" + profileClass + "'><a  href='#' class='js-menu-link' onclick='doAddProfile()'>Create Favorites Profile</a></li>");    
128    profileList.append("<li class='" + profileClass + "'><a href='/en-us/Favorites/ManageFavorites.aspx' class='js-menu-link'>Manage Favorites Profiles</a></li>");
129
130}
131
132function showLearnMore() {    
133    window.location = learnMoreUrl + "?ret=" + escape(location.pathname + location.search + location.hash);
134}
135
136function doAddProfile() {
137    if (wfModalManager.doesUserHaveAccessToFeature(WFProducts.ProfileFeature)) {
138        if (wfModalManager != null) {
139            wfModalManager.addProfile();
140        }
141    }
142
143    return false;
144}
145// The "Map" event is load-only: it fires once per page load, on the first
146// view_changed that resolves to a valid product label (the initial map load).
147// Later view_changed fires (layer selections, fx model-run / param / animation
148// tweaks, custom-layer changes, hash changes) are intentionally not reported.
149let mapEventFired = false;
150
151function trackViewEvent(viewId, zoomLevel) {
152    if (mapEventFired) {
153        return;
154    }
155
156    const label = getViewAnalyticsId(viewId);
157    const zoom = zoomLevel || 'N/A';
158
159    if (typeof label != 'undefined' && label != null && label != '') {
160        mapEventFired = true;
161
162        gtag('event', 'Map', {
163            Regional_Product: label,
164            Zoom: zoom,
165        });
166    }
167}
168
169function trackSpotListClickEvent(eventName, spot, upgradeAvailable) {
170  const spotId = spot[0];
171  const spotName = spot[1];
172  let formattedSpotName = `${spotName}-${spotId}`;
173
174  if (!spotName || spotName === '') {
175      formattedSpotName = `${spot[5]},${spot[6]}-${spotId}`;
176  }
177  
178  gtag("event", eventName, {
179    Spot_Name: `${formattedSpotName}`,
180  });
181}
182
183function getViewAnalyticsId(viewId) {
184  var label = viewId;
185
186  switch (viewId) {
187    case "obs":
188      label = "custom-obs";
189      break;
190    case "fx":
191      label = "custom-fx";
192      break;
193    default:
194      for (var x = 0; x < WFProducts.regionalProducts.length; x++) {
195        var product = WFProducts.regionalProducts[x];
196
197        if (product.regional_product_id == viewId) {
198          label = product.analytics_key;
199          break;
200        } else {
201          label = "Custom";
202        }
203      }
204      break;
205  }
206
207  return label;
208}
209
210/*
211 * jQuery MultiSelect UI Widget 1.13
212 * Copyright (c) 2012 Eric Hynds
213 *
214 * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/
215 *
216 * Depends:
217 *   - jQuery 1.4.2+
218 *   - jQuery UI 1.8 widget factory
219 *
220 * Optional:
221 *   - jQuery UI effects
222 *   - jQuery UI position utility
223 *
224 * Dual licensed under the MIT and GPL licenses:
225 *   http://www.opensource.org/licenses/mit-license.php
226 *   http://www.gnu.org/licenses/gpl.html
227 *
228 */
229(function(d){var k=0;d.widget("ech.multiselect",{options:{header:!0,height:175,minWidth:225,classes:"",checkAllText:"Check all",uncheckAllText:"Uncheck all",noneSelectedText:"Select options",selectedText:"# selected",selectedList:0,show:null,hide:null,autoOpen:!1,multiple:!0,position:{}},_create:function(){var a=this.element.hide(),b=this.options;this.speed=d.fx.speeds._default;this._isOpen=!1;a=(this.button=d('<button type="button"><span class="ui-icon ui-icon-triangle-2-n-s"></span></button>')).addClass("ui-multiselect ui-widget ui-state-default ui-corner-all").addClass(b.classes).attr({title:a.attr("title"),"aria-haspopup":!0,tabIndex:a.attr("tabIndex")}).insertAfter(a);
229(this.buttonlabel=d("<span />")).html(b.noneSelectedText).appendTo(a);var a=(this.menu=d("<div />")).addClass("ui-multiselect-menu ui-widget ui-widget-content ui-corner-all").addClass(b.classes).appendTo(document.body),c=(this.header=d("<div />")).addClass("ui-widget-header ui-corner-all ui-multiselect-header ui-helper-clearfix").appendTo(a);(this.headerLinkContainer=d("<ul />")).addClass("ui-helper-reset").html(function(){return!0===b.header?'<li><a class="ui-multiselect-all" href="#"><span class="ui-icon ui-icon-check"></span><span>'+b.checkAllText+'</span></a></li><li><a class="ui-multiselect-none" href="#"><span class="ui-icon ui-icon-closethick"></span><span>'+b.uncheckAllText+"</span></a></li>":"string"===typeof b.header?"<li>"+b.header+"</li>":""}).append('<li class="ui-multiselect-close"><a href="#" class="ui-multiselect-close"><span class="ui-icon ui-icon-circle-close"></span></a></li>').appendTo(c);(this.checkboxContainer=d("<ul />")).addClass("ui-multiselect-checkboxes ui-helper-reset").appendTo(a);this._bindEvents();this.refresh(!0);b.multiple||a.addClass("ui-multiselect-single")},_init:function(){!1===this.options.header&&this.header.hide();this.options.multiple||this.headerLinkContainer.find(".ui-multiselect-all, .ui-multiselect-none").hide();this.options.autoOpen&&this.open();this.element.is(":disabled")&&this.disable()},refresh:function(a){var b=this.element,c=this.options,f=this.menu,h=this.checkboxContainer,g=[],e="",i=b.attr("id")||k++;b.find("option").each(function(b){d(this);var a=this.parentNode,f=this.innerHTML,h=this.title,k=this.value,b="ui-multiselect-"+(this.id||i+"-option-"+b),l=this.disabled,n=this.selected,m=["ui-corner-all"],o=(l?"ui-multiselect-disabled ":" ")+this.className,j;"OPTGROUP"===a.tagName&&(j=a.getAttribute("label"),-1===d.inArray(j,g)&&(e+='<li class="ui-multiselect-optgroup-label '+a.className+'"><a href="#">'+j+"</a></li>",g.push(j)));l&&m.push("ui-state-disabled");n&&!c.multiple&&m.push("ui-state-active");e+='<li class="'+o+'">';e+='<label for="'+b+'" title="'+h+'" class="'+m.join(" ")+'">';e+='<input id="'+b+'" name="multiselect_'+i+'" type="'+(c.multiple?"checkbox":"radio")+'" value="'+k+'" title="'+f+'"';n&&(e+=' checked="checked"',e+=' aria-selected="true"');l&&(e+=' disabled="disabled"',e+=' aria-disabled="true"');e+=" /><span>"+f+"</span></label></li>"});h.html(e);this.labels=f.find("label");this.inputs=this.labels.children("input");this._setButtonWidth();this._setMenuWidth();this.button[0].defaultValue=this.update();a||this._trigger("refresh")},update:function(){var a=this.options,b=this.inputs,c=b.filter(":checked"),f=c.length,a=0===f?a.noneSelectedText:d.isFunction(a.selectedText)?a.selectedText.call(this,f,b.length,c.get()):/\d/.test(a.selectedList)&&0<a.selectedList&&f<=a.selectedList?c.map(function(){return d(this).next().html()}).get().join(", "):a.selectedText.replace("#",f).replace("#",b.length);this.buttonlabel.html(a);return a},_bindEvents:function(){function a(){b[b._isOpen? "close":"open"]();return!1}var b=this,c=this.button;c.find("span").bind("click.multiselect",a);c.bind({click:a,keypress:function(a){switch(a.which){case 27:case 38:case 37:b.close();break;case 39:case 40:b.open()}},mouseenter:function(){c.hasClass("ui-state-disabled")||d(this).addClass("ui-state-hover")},mouseleave:function(){d(this).removeClass("ui-state-hover")},focus:function(){c.hasClass("ui-state-disabled")||d(this).addClass("ui-state-focus")},blur:function(){d(this).removeClass("ui-state-focus")}});this.header.delegate("a","click.multiselect",function(a){if(d(this).hasClass("ui-multiselect-close"))b.close();else b[d(this).hasClass("ui-multiselect-all")?"checkAll":"uncheckAll"]();a.preventDefault()});this.menu.delegate("li.ui-multiselect-optgroup-label a","click.multiselect",function(a){a.preventDefault();var c=d(this),g=c.parent().nextUntil("li.ui-multiselect-optgroup-label").find("input:visible:not(:disabled)"),e=g.get(),c=c.parent().text();!1!==b._trigger("beforeoptgrouptoggle",a,{inputs:e,label:c})&&(b._toggleChecked(g.filter(":checked").length!==g.length,g),b._trigger("optgrouptoggle",a,{inputs:e,label:c,checked:e[0].checked}))}).delegate("label","mouseenter.multiselect",function(){d(this).hasClass("ui-state-disabled")||(b.labels.removeClass("ui-state-hover"),d(this).addClass("ui-state-hover").find("input").focus())}).delegate("label","keydown.multiselect",function(a){a.preventDefault();switch(a.which){case 9:case 27:b.close();break;case 38:case 40:case 37:case 39:b._traverse(a.which,this);break;case 13:d(this).find("input")[0].click()}}).delegate('input[type="checkbox"], input[type="radio"]',"click.multiselect",function(a){var c=d(this),g=this.value,e=this.checked,i=b.element.find("option");this.disabled||!1===b._trigger("click",a,{value:g,text:this.title,checked:e})?a.preventDefault():(c.focus(),c.attr("aria-selected",e),i.each(function(){this.value===g?this.selected=e:b.options.multiple||(this.selected=!1)}),b.options.multiple||(b.labels.removeClass("ui-state-active"),c.closest("label").toggleClass("ui-state-active",e),b.close()),b.element.trigger("change"),setTimeout(d.proxy(b.update,b),10))});d(document).bind("mousedown.multiselect",function(a){b._isOpen&&(!d.contains(b.menu[0],a.target)&&!d.contains(b.button[0],a.target)&&a.target!==b.button[0])&&b.close()});d(this.element[0].form).bind("reset.multiselect",function(){setTimeout(d.proxy(b.refresh,b),10)})},_setButtonWidth:function(){var a=this.element.outerWidth(),b=this.options;/\d/.test(b.minWidth)&&a<b.minWidth&&(a=b.minWidth);this.button.width(a)},_setMenuWidth:function(){var a=this.menu,b=this.button.outerWidth()-parseInt(a.css("padding-left"),10)-parseInt(a.css("padding-right"),10)-parseInt(a.css("border-right-width"),10)-parseInt(a.css("border-left-width"),10);a.width(b||this.button.outerWidth())},_traverse:function(a,b){var c=d(b),f=38===a||37===a,c=c.parent()[f?"prevAll":"nextAll"]("li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label)")[f?"last":"first"]();c.length?c.find("label").trigger("mouseover"):(c=this.menu.find("ul").last(),this.menu.find("label")[f? "last":"first"]().trigger("mouseover"),c.scrollTop(f?c.height():0))},_toggleState:function(a,b){return function(){this.disabled||(this[a]=b);b?this.setAttribute("aria-selected",!0):this.removeAttribute("aria-selected")}},_toggleChecked:function(a,b){var c=b&&b.length?b:this.inputs,f=this;c.each(this._toggleState("checked",a));c.eq(0).focus();this.update();var h=c.map(function(){return this.value}).get();this.element.find("option").each(function(){!this.disabled&&-1<d.inArray(this.value,h)&&f._toggleState("selected",a).call(this)});c.length&&this.element.trigger("change")},_toggleDisabled:function(a){this.button.attr({disabled:a,"aria-disabled":a})[a?"addClass":"removeClass"]("ui-state-disabled");
229var b=this.menu.find("input"),b=a?b.filter(":enabled").data("ech-multiselect-disabled",!0):b.filter(function(){return!0===d.data(this,"ech-multiselect-disabled")}).removeData("ech-multiselect-disabled");b.attr({disabled:a,"arial-disabled":a}).parent()[a?"addClass":"removeClass"]("ui-state-disabled");this.element.attr({disabled:a,"aria-disabled":a})},open:function(){var a=this.button,b=this.menu,c=this.speed,f=this.options,h=[];if(!(!1===this._trigger("beforeopen")||a.hasClass("ui-state-disabled")||this._isOpen)){var g=b.find("ul").last(),e=f.show,i=a.offset();d.isArray(f.show)&&(e=f.show[0],c=f.show[1]||this.speed);e&&(h=[e,c]);g.scrollTop(0).height(f.height);d.ui.position&&!d.isEmptyObject(f.position)?(f.position.of=f.position.of||a,b.show().position(f.position).hide()):b.css({top:i.top+a.outerHeight(),left:i.left});d.fn.show.apply(b,h);this.labels.eq(0).trigger("mouseover").trigger("mouseenter").find("input").trigger("focus");a.addClass("ui-state-active");this._isOpen=!0;this._trigger("open")}},close:function(){if(!1!==this._trigger("beforeclose")){var a=this.options,b=a.hide,c=this.speed,f=[];d.isArray(a.hide)&&(b=a.hide[0],c=a.hide[1]||this.speed);b&&(f=[b,c]);d.fn.hide.apply(this.menu,f);this.button.removeClass("ui-state-active").trigger("blur").trigger("mouseleave");this._isOpen=!1;this._trigger("close")}},enable:function(){this._toggleDisabled(!1)},disable:function(){this._toggleDisabled(!0)},checkAll:function(){this._toggleChecked(!0);this._trigger("checkAll")},uncheckAll:function(){this._toggleChecked(!1);this._trigger("uncheckAll")},getChecked:function(){return this.menu.find("input").filter(":checked")},destroy:function(){d.Widget.prototype.destroy.call(this);this.button.remove();this.menu.remove();this.element.show();return this},isOpen:function(){return this._isOpen},widget:function(){return this.menu},getButton:function(){return this.button},_setOption:function(a,b){var c=this.menu;switch(a){case "header":c.find("div.ui-multiselect-header")[b?"show":"hide"]();break;case "checkAllText":c.find("a.ui-multiselect-all span").eq(-1).text(b);break;case "uncheckAllText":c.find("a.ui-multiselect-none span").eq(-1).text(b);break;case "height":c.find("ul").last().height(parseInt(b,10));break;case "minWidth":this.options[a]=parseInt(b,10);this._setButtonWidth();this._setMenuWidth();break;case "selectedText":case "selectedList":case "noneSelectedText":this.options[a]=b;this.update();break;case "classes":c.add(this.button).removeClass(this.options.classes).addClass(b);break;case "multiple":c.toggleClass("ui-multiselect-single",!b),this.options.multiple=b,this.element[0].multiple=b,this.refresh()}d.Widget.prototype._setOption.apply(this,arguments)}})})(jQuery);
230
231function WFMessage(targetDiv, messages, tokenDomain, settings) {
232    this.targetDiv = targetDiv;
233    this.messages = JSON.parse(messages);
234    this.cookiePrefix = "msg-";
235    this.messageIdx = -1;
236    this.tokenDomain = tokenDomain;
237    this.defaultSettings = {
238        messageDismissed: null
239    };
240
241    this.settings = $.extend(true, {}, this.default_settings, settings);
242
243    if (this.messages) {
244        // Show the message to the user        
245        this.displayMessage();
246    }
247}
248
249WFMessage.prototype.displayMessage = function () {    
250    for (var i = 0; i < this.messages.length; i++) {
251        if (!this.messageDismissed(this.messages[i].Id)) {
252            // Display the message            
253            this.messageIdx = i;
254            this.createMessageBox(this.messages[i].Message);
255            break;
256        }
257    }
258}
259
260WFMessage.prototype.messageDismissed = function (msgId) {
261    if ($.cookie(this.cookiePrefix + msgId) != null) {        
262        return true;
263    } else {
264        return false;
265    }
266}
267
268WFMessage.prototype.dismissMessage = function () {
269    if (this.messageIdx > -1 && this.messageIdx < this.messages.length) {
270        var message = this.messages[this.messageIdx];
271        var expiration = 9999;
272
273        if (message.Expire_Time != null) {
274            var dt = message.Expire_Time;
275
276            expiration = new Date(
277		            parseInt(dt.slice(0, 4), 10),
278		            parseInt(dt.slice(5, 7), 10) - 1,
279		            parseInt(dt.slice(8, 10), 10),
280		            parseInt(dt.slice(11, 13), 10),
281		            parseInt(dt.slice(14, 16), 10),
282		            parseInt(dt.slice(17, 19), 10),
283		            0
284		            );
285        }
286
287        // Set the cookie        
288        $.cookie(this.cookiePrefix + message.Id, new Date().toUTCString(), { expires: expiration, path: "/", domain: this.tokenDomain });
289
290        // Hide the msg
291        $(this.targetDiv).hide();
292
293        if (typeof this.settings.messageDismissed === "function") {
294            this.settings.messageDismissed();
295        }
296    }
297}
298
299WFMessage.prototype.createMessageBox = function (message) {
300    var self = this;
301    
302    $(this.targetDiv).append("<div class='wfMessage'><a href='#' class='wfMessage-close' title='Dismiss message'></a><p>" + message + "</p></div>");
303
304    // Close event handler
305    $(document).on("click", ".wfMessage-close", function () {
306        self.dismissMessage();
307        return false;
308    });
309}
310function WFSpot() {
311
312}
313
314WFSpot.spot_id = 0;
315WFSpot.spot_name = 1;
316WFSpot.type = 2;
317WFSpot.spot_status = 3;
318WFSpot.distance = 4;
319WFSpot.lat = 5;
320WFSpot.lon = 6;
321WFSpot.provider = 7;
322WFSpot.region_id = 8;
323WFSpot.upgrade_available = 9;
324WFSpot.is_favorite = 10;
325WFSpot.fav_sort_order = 11;
326WFSpot.wind_alert_exists = 12;
327WFSpot.wind_alert_active = 13;
328WFSpot.spot_rank = 14;
329WFSpot.status_message = 15;
330WFSpot.spot_message = 16;
331WFSpot.source_message = 17;
332WFSpot.timestamp = 18;
333WFSpot.avg = 19;
334WFSpot.lull = 20;
335WFSpot.gust = 21;
336WFSpot.dir = 22;
337WFSpot.dir_text = 23;
338WFSpot.atemp = 24;
339WFSpot.wtemp = 25;
340WFSpot.forecast = 26;
341WFSpot.wave_height = 27;
342WFSpot.wave_period = 28;
343WFSpot.current_time_local = 29;
344WFSpot.pres = 30;
345WFSpot.fav_spot_id = 31;
346WFSpot.wind_desc = 32;
347
348WFSpot.station_is_down = 4;
349
350WFSpot.record_length = 33;
351
352/******************************************************************************
353*  Converts structure returned from getSpotDetailSetBy[x] to a the 
354*  format returned by getSpotSetBy[x]
355******************************************************************************/
356WFSpot.prototype.convert = function (spotDetailSet, frameIdx) {    
357    var station = spotDetailSet.stations[0];
358    var frame = station.data_values[frameIdx];
359    var spot = new Array(WFSpot.record_length);
360
361    spot[WFSpot.spot_id] = spotDetailSet.spot_id;
362    spot[WFSpot.spot_name] = spotDetailSet.name;
363    spot[WFSpot.type] = station.type;
364    spot[WFSpot.spot_status] = station.status_id;
365    spot[WFSpot.distance] = null;
366    spot[WFSpot.lat] = spotDetailSet.lat;
367    spot[WFSpot.lon] = spotDetailSet.lon;
368    spot[WFSpot.provider] = station.provider;
369    spot[WFSpot.region_id] = null;
370    spot[WFSpot.upgrade_available] = spotDetailSet.upgrade_available;
371    spot[WFSpot.is_favorite] = null;
372    spot[WFSpot.fav_sort_order] = null;
373    spot[WFSpot.wind_alert_exists] = null;
374    spot[WFSpot.wind_alert_active] = null;
375    spot[WFSpot.spot_rank] = null;
376    spot[WFSpot.status_message] = null;
377    spot[WFSpot.spot_message] = null;
378    spot[WFSpot.source_message] = null;
379    spot[WFSpot.timestamp] = frame[WFSpotDetail.timestamp];
380    spot[WFSpot.avg] = frame[WFSpotDetail.avg];
381    spot[WFSpot.lull] = frame[WFSpotDetail.lull];
382    spot[WFSpot.gust] = frame[WFSpotDetail.gust];
383    spot[WFSpot.dir] = frame[WFSpotDetail.dir];
384    spot[WFSpot.dir_text] = frame[WFSpotDetail.dir_text];
385    spot[WFSpot.atemp] = frame[WFSpotDetail.atemp];
386    spot[WFSpot.wtemp] = frame[WFSpotDetail.wtemp];
387    spot[WFSpot.forecast] = null;
388    spot[WFSpot.wave_height] = frame[WFSpotDetail.wave_height];
389    spot[WFSpot.wave_period] = frame[WFSpotDetail.wave_period];
390    spot[WFSpot.current_time_local] = spotDetailSet.current_time_local;
391    spot[WFSpot.pres] = frame[WFSpotDetail.pres];
392    spot[WFSpot.fav_spot_id] = spotDetailSet.fav_spot_id;
393    spot[WFSpot.wind_desc] = frame[WFSpotDetail.wind_desc];
394
395    return spot;
396
397}
398/////////////////////////////////////////////////////////////////////////////////////////
399/////////////////////////////////////////////////////////////////////////////////////////
400//  WFModalManager
401//      - Used to create single and multi-step modals
402//
403//      functions                                                                           Modals
404//      ----------------------------------------------------------------------              ---------------------------------------------
405//      addContainerToProfile(containerName, containerTypeId, containerOptions)             nameModal,selectProfileModal,addProfileModal
406//      addArbitrarySpotToProfile(lat, lon, spotName)                                       nameModal,selectProfileModal,addProfileModal
407//      addSpotToProfile(spotId, spotName)                                                  selectProfileModal,addProfileModal
408//      removeContainerFromProfile(containerId, targetDivId)
409//
410//      Modals
411//      -----------------
412//      nameModal
413//      selectProfileModal
414//      addProfileModal
415//      alertModal
416//      membershipModal
417//      manageSettingsModal
418//      downloadDataModal
419//
420//      - Uses WFProfileManager - must already be in scope and initialized.
421//      - All default_args in the constructor can be set by the client and passed using options when creating
422//      - Ability to forward to a signin page if user is not signed in and url is provided in constructor options
423/////////////////////////////////////////////////////////////////////////////////////////
424/////////////////////////////////////////////////////////////////////////////////////////
425
426/////////////////////////////////////////////////////////////////////////////////////////
427/////////////////////////////////////////////////////////////////////////////////////////
428//  WFModalManager - Constructor
429/////////////////////////////////////////////////////////////////////////////////////////
430/////////////////////////////////////////////////////////////////////////////////////////
431function WFModalManager(options) {
432    this.default_args = {
433        "userSignInUrl": "",            // redirect url for signin  WindAlert=/en-us/MyAccount/SignIn.aspx        
434        "learnMoreUrl": typeof learnMoreUrl !== "undefined" ? learnMoreUrl : "/en-us/Services/SignUp.aspx",
435        "userSignedIn": "false",                                    // if userSignedIn == "false" and userSignInUrl.length > 0 --> redirect to so userSignInUrl
436        "token": "",
437        "tokenDomain": null,
438        "baseURL": "https://api.weatherflow.com/wxengine/rest",       // Weather Engine baseurl - client might want to change to staging
439        "siteName": "WindAlert",
440        "upsellMsgShown": null,
441        "isMobile": false
442    };
443
444
445    this.options = options;
446    this.nameField = "";
447    this.containerId = "";
448    this.containerTypeIdField = "";
449    this.optionsField = "";
450    this.profileNameField = "";
451    this.currentActivity = "";                                      // addcontainertoprofile, addspottoprofile, addarbitraryspottoprofile
452    this.spotId;
453    this.spotType;                                                  // 1,100,101
454    this.spotLat;
455    this.spotLon;
456    this.favSpotId;
457    this.reloadPageOnClose = false;                                 // calls location.reload() on modal close if true
458    this.profileId;
459
460    this.memberLevel = -1;
461    this.showAds = true;
462    this.showAdsToggle = false;
463    this.defaultAdsToggleState = 1;
464    this.canAdsToggle = false;
465    this.trialEndDate = "";
466
467    // download data member vars
468
469    this.downloadDataUnitsWind = "mph";
470    this.downloadDataUnitsTemp = "F";
471    this.downloadDataStartDateStr = "";
472    this.downloadDataEndDateStr = "";
473    this.downloadDataSpotId = 0;
474    this.downloadDataFormat = "csv";
475
476
477    // set options - use default_args if not passed in options var
478    for (var index in this.default_args) {
479        if (typeof this.options[index] == "undefined") {
480            this.options[index] = this.default_args[index];
481        }
482    }
483
484    this.scrollTop = null;
485    this.openModalId = null;
486    this.nameModalId = "wfmodal-name-item";
487    this.selectProfileModalId = "wfmodal-select-profile";
488    this.alertModalId = "wfmodal-alert";
489    this.addProfileModalId = "wfmodal-add-profile";
490    this.toggleAdsModalId = "wfmodal-ad-toggle";
491    this.loadingModalId = "wfmodal-loading";
492    this.membershipModalId = "wfmodal-membership";
493    this.downloadDataModalId = "wfmodal-download-data";
494    this.replicateProfileModalId = "wfmodal-replicate-profile";
495    this.renameProfileModalId = "wfmodal-rename-profile";
496
497    ///////////////////////////////////////////////////////////////////////
498    //  Add each container to the DOM and define as a modal
499    //////////////////////////////////////////////////////////////////////
500    //  Add Container to Profile - Step 1 - nameItem Modal
501    $("body").append(this.createNameItemModal());
502    this.nameModal = $("#" + this.nameModalId);
503
504    //  Add Container to Profile - Step 2 - Select Profiles
505    $("body").append(this.createSelectProfileModal());
506    this.selectProfileModal = $("#" + this.selectProfileModalId);
507
508    //  Add Profile
509    $("body").append(this.createAddProfileModal());
510    this.addProfileModal = $("#" + this.addProfileModalId);
511
512    // Rename Profile
513    $("body").append(this.createRenameProfileModal());
514    this.renameProfileModal = $("#" + this.renameProfileModalId);
515
516    //  Alert Modal
517    $("body").append(this.createAlertModal());
518    this.alertModal = $("#" + this.alertModalId);
519
520    //  Membership Modal
521    $("body").append(this.createMembershipModal());
522    this.membershipModal = $("#" + this.membershipModalId);
523
524    //  Download Data Modal
525    $("body").append(this.createDownloadDataModal());
526    this.downloadDataModal = $("#" + this.downloadDataModalId);
527
528    // Loading Modal
529    $("body").append(this.createLoadingModal());
530    this.loadingModal = $("#" + this.loadingModalId);
531
532    // Replicate Profile
533    $("body").append(this.createReplicateProfileModal());
534    this.replicateProfileModal = $("#" + this.replicateProfileModalId);
535
536    // Info Window
537    this.createInfoWindow();
538
539    this._attachEventHandlers();
540
541    // set user member vars
542    //    default values
543    //    ------------------------------
544    //    this.member_level = -1;
545    //    this.show_ads = true;
546    //    this.show_ads_toggle = false;
547    //    this.can_ads_toggle = false;
548    //    this.default_ads_toggle_state = 1;
549    //    this.trial_end_date = "";
550
551
552    //  option 1 - check to see if already in scope (ie: set by server side)
553    if (typeof memberLevel != "undefined" && typeof showAdsToggle != "undefined" && typeof canToggleAds != "undefined" && typeof showAds != "undefined" && typeof defaultAdsToggleState != "undefined") {
554        this.memberLevel = memberLevel;
555        this.defaultAdsToggleState = defaultAdsToggleState;
556
557        if (showAds == "false") {
558            this.showAds = false;
559        }
560        if (showAdsToggle == "true") {
561            this.showAdsToggle = true;
562        }
563        if (canToggleAds == "true") {
564            this.canAdsToggle = true;
565        }
566    }
567    else {
568        //  option 2 - use api checkToken
569        var url = this.options.baseURL + "/session/checkToken?callback=?";
570        var urldata = new Object();
571        urldata.wf_token = this.options.token;;
572
573        var myself = this;
574        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
575            if (data.status.status_code == 0) {
576                // success
577                myself.memberLevel = data.wf_user.member_level;
578                myself.defaultAdsToggleState = data.wf_user.default_ads_toggle_state;
579                myself.trialEndDate = data.wf_user.trial_end_date;
580
581                if (data.wf_user.show_ads == "false") {
582                    myself.showAds = false;
583                }
584                if (data.wf_user.show_ads_toggle == "true") {
585                    myself.showAdsToggle = true;
586                }
587                if (data.wf_user.can_ads_toggle == "true") {
588                    myself.canAdsToggle = true;
589                }
590            }
591            else {
592                // fail
593            }
594        }, "json");
595    }
596}
597
598
599//////////////////////////////////////////////////////////////////////////////////////////////////////////
600//////////////////////////////////////////////////////////////////////////////////////////////////////////
601//////////////////////////////////////////////////////////////////////////////////////////////////////////
602// Event Listeners
603//////////////////////////////////////////////////////////////////////////////////////////////////////////
604//////////////////////////////////////////////////////////////////////////////////////////////////////////
605//////////////////////////////////////////////////////////////////////////////////////////////////////////
606
607// listen for profiles loaded and change the profile list
608$("body").bind("profiles_loaded", function (event) {
609    $("#wfmodal-profile-list").empty();
610    $("#wfmodal-profile-list").append(wfModalManager.createSelectProfileList());
611});
612
613$(document).on("click", "a.profileName", function () {
614    var profileId = $(this).attr("pid");
615
616    $("body").trigger("profile_clicked", profileId);
617
618    return false;
619});
620
621
622//////////////////////////////////////////////////////////////////////////////////////////////////////////
623//////////////////////////////////////////////////////////////////////////////////////////////////////////
624//////////////////////////////////////////////////////////////////////////////////////////////////////////
625// Controllers
626//////////////////////////////////////////////////////////////////////////////////////////////////////////
627//////////////////////////////////////////////////////////////////////////////////////////////////////////
628//////////////////////////////////////////////////////////////////////////////////////////////////////////
629
630//////////////////////////////////////////////////////////////////////////////////////////////////////////
631// General Entry Controller for a Spot
632//    - spot is defined in WFSpot.js
633//////////////////////////////////////////////////////////////////////////////////////////////////////////
634WFModalManager.prototype.addRemoveSpot = function (spot) {
635    var spotId = spot[WFSpot.spot_id];
636    var favSpotId = spot[WFSpot.fav_spot_id];
637    var spotType = spot[WFSpot.type];
638    if (spot[WFSpot.type] == 101) {
639        var spotName = "Nowcast Spot (" + spot[WFSpot.lat].toFixed(2) + ", " + spot[WFSpot.lon].toFixed(2) + ")";
640    } else {
641        var spotName = spot[WFSpot.spot_name];
642    }
643    var lat = spot[WFSpot.lat];
644    var lon = spot[WFSpot.lon];
645
646    if (typeof spotId != "undefined" && spotId > 0) {
647        // type 1 or 100 spot with a spot_id
648        this.addSpotToProfile(spotId, spotName, spotType);
649    }
650    else if (typeof lat != "undefined" && typeof lon != "undefined") {
651        // arbitrary spot, might have favSpotId and name but those will fall out later
652        this.addArbitrarySpotToProfile(lat, lon, spotName, favSpotId);
653    }
654}
655
656
657//////////////////////////////////////////////////////////////////////////////////////////////////////////
658// addContainerToProfile Controllers
659//////////////////////////////////////////////////////////////////////////////////////////////////////////
660// Add Container to Profile - Step 1
661WFModalManager.prototype.addContainerToProfile = function (containerName, containerTypeId, containerOptions) {
662
663    // display step 1 modal
664    this.currentActivity = "addcontainertoprofile";
665    this.nameField = containerName;
666    this.containerTypeIdField = containerTypeId;
667    this.optionsField = containerOptions;
668
669    this.setModalTitle(this.getSaveContainerTitle(containerTypeId), this.nameModalId);
670
671    // set input to container name
672    $("#wfmodal-spot-name").val(containerName);
673
674    this.open(this.nameModalId);
675
676}
677
678WFModalManager.prototype.getSaveContainerTitle = function (containerTypeId) {
679    var title = "Save";
680    var ctnName = null;
681
682    switch (containerTypeId) {
683        case "Bulletin":
684            ctnName = "bulletins";
685            break;
686        case "Tides":
687            ctnName = "nearby-tides";
688            break;
689        case "Archive":
690            ctnName = "data-archives"
691            break;
692        case "Current Conditions":
693            ctnName = "current-conditions";
694            break;
695        case "Spot Stats":
696            ctnName = "wind-stats";
697            break;
698        case "Onsite Report":
699            ctnName = "onsite-reports";
700            break;
701        case "Operational Forecast":
702            ctnName = "daily-briefing";
703            break;
704        case "Model Table":
705            ctnName = "forecast-table";
706            break;
707    }
708
709    if (ctnName != null) {
710        var ctn = new ContainerInfo(ctnName);
711        title += " " + ctn.title();
712    }
713
714    return title;
715}
716
717
718// Add Container to Profile - Step 2
719WFModalManager.prototype.addContainerToProfileStep2 = function () {
720    // set modal title
721    $(".wf-modal-title").empty();
722    $(".wf-modal-title").append(this.nameField);
723
724    // update the profile list in the modal
725    $("#wfmodal-profile-list").empty();
726    $("#wfmodal-profile-list").append(this.createSelectProfileList());
727
728    if (wfProfileManager.profileCount == 1) {
729        var profiles = wfProfileManager.getProfiles();
730
731        if (this.currentActivity == "addarbitraryspottoprofile") {
732            this.doAddArbitrarySpotToProfile(profiles[0].profile_id, profiles[0].name);
733        } else if (this.currentActivity == "addcontainertoprofile") {
734            this.doAddContainerToProfile(profiles[0].profile_id, profiles[0].name);
735        }
736    }
737
738    // display select profile modal       
739    this.open(this.selectProfileModalId);
740}
741
742//////////////////////////////////////////////////////////////////////////////////////////////////////////
743// addSpotToProfile Controllers
744//////////////////////////////////////////////////////////////////////////////////////////////////////////
745// Add Container to Profile - Step 1
746WFModalManager.prototype.addSpotToProfile = function (spotId, spotName, spotType) {
747    this.currentActivity = "addspottoprofile";
748    this.nameField = spotName;
749    this.spotId = spotId;
750
751    if (spotType != undefined && spotType > 0) {
752        this.spotType = spotType;
753    }
754    else {
755        this.spotType = 1;
756    }
757
758    // set modal title
759    $(".wf-modal-title").empty();
760    $(".wf-modal-title").append(this.nameField);
761
762    // update the profile list in the modal
763    $("#wfmodal-profile-list").empty();
764    $("#wfmodal-profile-list").append(this.createSelectProfileList());
765
766    // If the user only has one profile, automatically add/remove the spot to/from their profile       
767    if (wfProfileManager.profileCount == 1) {
768        var profiles = wfProfileManager.getProfiles();
769
770        if (wfProfileManager.isSpotIdInAnyProfile(spotId)) {
771            //spotId, favSpotId, profileId, profileName 
772            var favId = wfProfileManager.getFavSpotIdForSpotIdInProfile(spotId, profiles[0].profile_id);
773            this.doRemoveSpotFromProfile(spotId, favId, profiles[0].profile_id, profiles[0].name);
774        } else {
775            this.doAddSpotToProfile(spotId, profiles[0].profile_id, profiles[0].name);
776        }
777    } else {
778        $(".wfmodal-instructions").html("<br/>Click a favorite star to add or remove spot from your profile.").show();;
779    }
780
781    // display select profile modal
782    this.open(this.selectProfileModalId);
783}
784
785
786//////////////////////////////////////////////////////////////////////////////////////////////////////////
787// addArbitrarySpotToProfile Controllers
788//////////////////////////////////////////////////////////////////////////////////////////////////////////
789// Add Arbitrary Spot to Profile - Step 1
790WFModalManager.prototype.addArbitrarySpotToProfile = function (lat, lon, spotName, favSpotId) {
791
792    // display step 1 modal
793    this.currentActivity = "addarbitraryspottoprofile";
794    this.nameField = spotName;
795    this.spotType = 101;
796    this.spotLat = lat;
797    this.spotLon = lon;
798    this.favSpotId = favSpotId;
799
800    if (this.favSpotId != null || wfProfileManager.isLatLonInAnyProfile(this.spotLat, this.spotLon)) {
801        // User is working with a arb spot that is already favorited
802        // set modal title
803        $(".wf-modal-title").empty();
804        $(".wf-modal-title").append(this.nameField);
805
806        // update the profile list in the modal
807        $("#wfmodal-profile-list").empty();
808        $("#wfmodal-profile-list").append(this.createSelectProfileList());
809
810        if (wfProfileManager.profileCount == 1) {
811            profiles = wfProfileManager.getProfiles();
812
813            // favSpotId, profileId, profileName            
814            this.doRemoveArbitrarySpotFromProfile(favSpotId, profiles[0].profile_id, profiles[0].name);
815        }
816
817        // display select profile modal
818        this.open(this.selectProfileModalId);
819    } else {
820        if (typeof spotName == "undefined" || spotName.length < 1) {
821            this.nameField = "Location " + this.spotLat + "," + this.spotLon;
822        }
823        // set modal title
824        $(".wf-modal-title").empty();
825        $(".wf-modal-title").append("Save Location");
826
827        // set input to container name
828        $("#wfmodal-spot-name").val(this.nameField);
829
830        // display change name modal
831        this.open(this.nameModalId);
832    }
833}
834
835
836// Add Arbitrary Spot to Profile - Step 2
837WFModalManager.prototype.addArbitrarySpotToProfileStep2 = function () {
838    // set modal title
839    $(".wf-modal-title").empty();
840    $(".wf-modal-title").append(this.nameField);
841
842    // update the profile list in the modal
843    $("#wfmodal-profile-list").empty();
844    $("#wfmodal-profile-list").append(this.createSelectProfileList());
845
846    // display select profile modal
847    this.open(this.selectProfileModalId);
848}
849
850
851//////////////////////////////////////////////////////////////////////////////////////////////////////////
852// addProfile Controllers
853//////////////////////////////////////////////////////////////////////////////////////////////////////////
854WFModalManager.prototype.addProfile = function () {
855    // set modal title
856    $(".wf-modal-title").empty();
857    $(".wfmodal-instructions").hide();
858    $(".wfmodal-message").hide();
859    $(".wf-modal-title").append("Create Favorites Profile");
860    $("#wfmodal-profile-name").val("");
861
862
863    // display add profile
864    this.open(this.addProfileModalId);
865
866    // refresh profiles - leaving profile list display and the data could have changed
867    wfProfileManager.loadProfiles();
868}
869
870WFModalManager.prototype.renameProfile = function (profileId, profileName) {
871    $(".wf-modal-title").html("Rename " + profileName);
872    $(this.renameProfileModal).attr("data-profile-id", profileId);
873
874    this.open(this.renameProfileModalId);
875}
876
877
878//////////////////////////////////////////////////////////////////////////////////////////////////////////
879// removeContainerFromProfile Controllers
880//////////////////////////////////////////////////////////////////////////////////////////////////////////
881WFModalManager.prototype.removeContainerFromProfile = function (containerId, targetDivId) {
882    // set modal alert message
883    $(".wfmodal-alert-message").empty();
884
885    // set modal title
886    $(".wf-modal-title").empty();
887    $(".wf-modal-title").append("Remove Container");
888
889    if (this.options.userSignedIn == "false") {
890        // User not signed in.  Remove container from their local profile        
891        var profile = new WFLocalProfile(this.options.tokenDomain);
892        profile.removeContainer(containerId);
893
894        $(".wfmodal-alert-message").append("The container was successfully removed.");
895
896        // display add profile
897        this.open(this.alertModalId);
898
899        // remove container from DOM
900        $("#" + targetDivId).remove();
901    } else {
902
903        // use API to remove container from profile
904        var url = this.options.baseURL + "/profile/removeContainerFromProfile?callback=?";
905        var urldata = new Object();
906        urldata.container_id = containerId;
907        urldata.wf_token = this.options.token;;
908
909        var myself = this;
910        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
911            if (data.status_code == 0) {
912                // success
913                $(".wfmodal-alert-message").append("The container was successfully removed.");
914                // remove container from DOM
915                $("#" + targetDivId).remove();
916            }
917            else {
918                // fail
919                $(".wfmodal-alert-message").append("An error occurred while removing the container.");
920            }
921            // display add profile            
922            myself.open(myself.alertModalId);
923
924        }, "json");
925    }
926
927}
928
929WFModalManager.prototype.displayAlert = function (title, message) {
930    // set modal alert message
931    $(".wfmodal-alert-message").empty();
932
933    // set modal title
934    $(".wf-modal-title").empty();
935    $(".wf-modal-title").append(title);
936
937    // set the message
938    $(".wfmodal-alert-message").append(message);
939
940    // display add profile
941    this.open(this.alertModalId);
942}
943
944WFModalManager.prototype.showReplicateProfile = function (profileId, profilename) {
945    $("#replicateProfileName").val(unescape(profilename) + " copy").attr("data-profile-id", profileId);
946    $("#replicateProgress").html("Create a copy of " + unescape(profilename)).show();
947
948    this.open(this.replicateProfileModalId);
949}
950
951WFModalManager.prototype.showToast = function (title, message) {
952    var self = this;
953    this.displayAlert(title, message);
954
955    var t = setTimeout(function () { self.close() }, 2000)
956}
957
958WFModalManager.prototype.displayInfoWindow = function (content, title, width) {
959    // Set the content
960    $("#info-window-content").empty().append(content);
961    $("#info-window").dialog("option", "title", title);
962    $("#info-window").dialog("option", "width", "100%");
963
964    // Show the window
965    $("#info-window").dialog("open");
966}
967
968WFModalManager.prototype.displayLoading = function (title) {
969    $(".wf-modal-title").html(title);
970    this.show(this.loadingModalId);
971}
972
973WFModalManager.prototype.hideLoading = function () {
974    this.close();
975}
976
977//////////////////////////////////////////////////////////////////////////////////////////////////////////
978// displayMembershipModal Controllers
979//////////////////////////////////////////////////////////////////////////////////////////////////////////
980WFModalManager.prototype.doesUserHaveAccessToFeature = function (featureId, exceptionFailMemberIdsStr) {
981    var doesUserHaveAccessToFeature = WFProducts.doesUserHaveAccessToFeature(featureId, this.memberLevel, exceptionFailMemberIdsStr);
982
983    if (doesUserHaveAccessToFeature) {
984        // the user can use the feature, do not need to display a modal
985        return true;
986    }
987    else {
988        // hide sign in and upgrade buttons
989        $("#wfmodal-membership-upgrade").hide();
990        $("#wfmodal-membership-signin").hide();
991
992        // set modal title
993        $(".wf-modal-title").empty();
994        if (this.memberLevel == -1) {
995            // Guest - need to sign in
996            $(".wf-modal-title").append("Sign in required for this feature");
997            $("#wfmodal-membership-signin").show();
998        }
999        else {
1000            $(".wf-modal-title").append("Upgrade required for this feature");
1001            $("#wfmodal-membership-upgrade").show();
1002        }
1003
1004        // set feature title  
1005        $("#wfmodal-feature-title").empty();
1006        $("#wfmodal-feature-title").append(WFProducts.getFeatureTitleById(featureId));
1007
1008        // set feature membership string  
1009        $("#wfmodal-feature-membershipstr").empty();
1010        $("#wfmodal-feature-membershipstr").append(WFProducts.getFeatureMembershipStringById(featureId));
1011
1012        // set current membership level
1013        $("#wfmodal-current-member-level").empty();
1014        $("#wfmodal-current-member-level").append(WFProducts.getMembershipLevelNameById(this.memberLevel));
1015
1016        this.open(this.membershipModalId);
1017
1018        if (typeof this.options.upsellMsgShown === "function") {
1019            this.options.upsellMsgShown(featureId);
1020        }
1021
1022        return false;
1023    }
1024}
1025
1026WFModalManager.prototype.showSignInRequired = function (feature, msg) {
1027    $("#wfmodal-membership-upgrade").hide();
1028
1029    $(".wf-modal-title").text("Sign in required for this feature");
1030    $("#wfmodal-membership-signin").show();
1031
1032    // set feature title      
1033    $("#wfmodal-feature-title").text(feature);
1034
1035    // set feature membership string      
1036    $("#wfmodal-feature-membershipstr").html(msg);
1037
1038    // set membership level
1039    $("#wfmodal-current-member-level").append(WFProducts.getMembershipLevelNameById(this.memberLevel));
1040
1041    this.open(this.membershipModalId);
1042
1043    return false;
1044}
1045
1046
1047WFModalManager.prototype.showMembershipRequired = function (feature) {
1048    var featureId = feature.feature_id;
1049
1050    // overriding getProducts.js membership_str to allow for dynamically changing the string based on memberLevel
1051    var membershipString = "This feature is available to PLUS, PRO, and GOLD Members. Upgrade today."
1052
1053    // hide sign in and learn more buttons
1054    $("#wfmodal-learnmore").hide();
1055    $("#wfmodal-membership-signin").hide();
1056
1057    // set modal title
1058    $(".wf-modal-title").empty();
1059    if (this.memberLevel == -1) {
1060        // Guest - need to sign in
1061        membershipString = "This feature is available to PLUS, PRO, and GOLD Members. Sign In or Upgrade today."
1062
1063        $(".wf-modal-title").append("Sign In or Upgrade required for this feature");
1064        $("#wfmodal-membership-signin").show();
1065    } else {
1066        $(".wf-modal-title").append("Upgrade required for this feature");
1067    }
1068
1069    // set feature title  
1070    $("#wfmodal-feature-title").empty();
1071    $("#wfmodal-feature-title").append(WFProducts.getFeatureTitleById(featureId));
1072
1073    // set feature membership string  
1074    $("#wfmodal-feature-membershipstr").empty();
1075    $("#wfmodal-feature-membershipstr").append(membershipString);
1076
1077    // set current membership level
1078    $("#wfmodal-current-member-level").empty();
1079    $("#wfmodal-current-member-level").append(WFProducts.getMembershipLevelNameById(this.memberLevel));
1080
1081
1082    this.open(this.membershipModalId);
1083    return false;
1084}
1085
1086//////////////////////////////////////////////////////////////////////////////////////////////////////////
1087// downloadDataModal Controllers
1088//////////////////////////////////////////////////////////////////////////////////////////////////////////
1089WFModalManager.prototype.downloadData = function (startDateStr, endDateStr, spotId, unitsWind, unitsTemp, format) {
1090    // set member vars so they are available to the download click event
1091    if (typeof startDateStr == "undefined") {
1092        this.downloadDataStartDateStr = "";
1093    }
1094    else {
1095        this.downloadDataStartDateStr = startDateStr;
1096    }
1097
1098    if (typeof endDateStr == "undefined") {
1099        this.downloadDataEndDateStr = "";
1100    }
1101    else {
1102        this.downloadDataEndDateStr = endDateStr;
1103    }
1104
1105    if (typeof spotId == "undefined") {
1106        this.downloadDataSpotId = 0;
1107    }
1108    else {
1109        this.downloadDataSpotId = spotId;
1110    }
1111
1112    if (typeof unitsWind == "undefined") {
1113        this.downloadDataUnitsWind = "mph";
1114    }
1115    else {
1116        this.downloadDataUnitsWind = unitsWind;
1117    }
1118
1119    if (typeof unitsTemp == "undefined") {
1120        this.downloadDataUnitsTemp = "F";
1121    }
1122    else {
1123        this.downloadDataUnitsTemp = unitsTemp;
1124    }
1125
1126    if (typeof format == "undefined") {
1127        this.downloadDataFormat = "csv";
1128    }
1129    else {
1130        this.downloadDataFormat = format;
1131    }
1132
1133
1134    // set modal alert message
1135    $(".wfmodal-alert-message").empty();
1136
1137    // set modal title
1138    $(".wf-modal-title").empty();
1139    $(".wf-modal-title").append("Archive Data Download");
1140
1141    // set start date
1142    $("#wfmodal-download-data-start-date").empty();
1143    $("#wfmodal-download-data-start-date").append(this.downloadDataStartDateStr);
1144
1145    // set end date
1146    $("#wfmodal-download-data-end-date").empty();
1147    $("#wfmodal-download-data-end-date").append(this.downloadDataEndDateStr);
1148
1149    this.open(this.downloadDataModalId);
1150}
1151
1152WFModalManager.prototype.doDownloadData = function () {
1153    this.close();
1154
1155    // use API to updateContainer
1156    var downloadUrl = "https://api.weatherflow.com/wxengine/rest/spot/getSpotDetail?" +
1157        "wf_token=" + this.options.token +
1158        "&units_wind=" + this.downloadDataUnitsWind +
1159        "&units_temp=" + this.downloadDataUnitsTemp +
1160        "&format=" + this.downloadDataFormat +
1161        "&time_start=" + this.downloadDataStartDateStr +
1162        "&time_end=" + this.downloadDataEndDateStr +
1163        "&spot_id=" + this.downloadDataSpotId;
1164
1165    window.location = downloadUrl;
1166}
1167
1168//////////////////////////////////////////////////////////////////////////////////////
1169//  HTML Generation Controllers
1170//////////////////////////////////////////////////////////////////////////////////////
1171// Name Item Modal
1172WFModalManager.prototype.createNameItemModal = function () {
1173    var myself = this;
1174    var buttons = [];
1175    var nextButton = this.createButton("wfmodal-do-add-to-container-step-2", "", "#", "Next");
1176    var str = "<div class='field-wrap'>";
1177    str += "<label for='wfmodal-spot-name'>Name:</label>";
1178    str += "<input type='text' id='wfmodal-spot-name' />";
1179    str += "</div>";
1180
1181    buttons.push(nextButton);
1182    buttons.push(this.createButton("wf-modal-close-btn", "wf-modal-close", "#", "Cancel"));
1183
1184    $(nextButton).on("touchend click", function () {
1185        var newName = $("#wfmodal-spot-name").val();
1186        if (newName.length > 0) {
1187            document.activeElement.blur();
1188            myself.nameField = $("#wfmodal-spot-name").val()
1189            myself.close();
1190            myself.addContainerToProfileStep2();
1191        }
1192        return false;
1193    });
1194
1195    var settings = {
1196        id: this.nameModalId,
1197        title: "",
1198        body: str,
1199        buttons: buttons
1200    };
1201
1202    return this.createModal(settings);
1203}
1204
1205// Select Profile Modal
1206WFModalManager.prototype.createSelectProfileModal = function () {
1207    var str = "<div id='wfmodal-profile-list'></div>";
1208    var settings = {
1209        id: this.selectProfileModalId,
1210        title: "",
1211        body: str,
1212        buttons: [this.createButton("cancelSaveFavorite", "wf-modal-close", "#", "Done")],
1213        extras: this.getSelectProfileLinks()
1214    };
1215
1216    return this.createModal(settings);
1217}
1218// Add Profile Modal
1219WFModalManager.prototype.createAddProfileModal = function () {
1220    var myself = this;
1221    var buttons = [];
1222    var saveButton = this.createButton("wfmodal-add-profile", "", "#", "Save");
1223    var str = "<div class='wfmodal-message'>Invalid input parameter value - name required</div>" +
1224        "<div id='saveFx1'>" +
1225        "   <div class='wfmodal-input-field-container'>" +
1226        "       <div class='wfmodal-input-field-label'><span>Name:</span></div>" +
1227        "       <div class='wfmodal-textfield'><input type='text' id='wfmodal-profile-name'/></div>" +
1228        "   </div>" +
1229        "</div>";
1230
1231    buttons.push(saveButton);
1232    buttons.push(this.createButton("wf-add-profile-close", "wf-modal-close", "#", "Cancel"));
1233
1234    $(saveButton).on("touchend click", function () {
1235        var newName = $("#wfmodal-profile-name").val();
1236        if (newName.length > 0) {
1237            myself.doAddProfileAction();
1238        }
1239        else {
1240            $(".wfmodal-message").show();
1241        }
1242        return false;
1243    });
1244
1245    var settings = {
1246        id: this.addProfileModalId,
1247        title: "",
1248        body: str,
1249        buttons: buttons
1250    };
1251
1252    return this.createModal(settings);
1253}
1254
1255// Rename Profile Modal
1256WFModalManager.prototype.createRenameProfileModal = function () {
1257    var myself = this;
1258    var buttons = [];
1259    var saveButton = this.createButton("wfmodal-rename-profile", "", "#", "Rename");
1260    var str = "<div class='wfmodal-message'>Invalid input parameter value - name required</div>" +
1261        "<div id='saveFx1'>" +
1262        "   <div class='wfmodal-input-field-container'>" +
1263        "       <div class='wfmodal-input-field-label'><span>New Name:</span></div>" +
1264        "       <div class='wfmodal-textfield'><input type='text' id='wfmodal-profile-new-name'/></div>" +
1265        "   </div>" +
1266        "</div>";
1267
1268    buttons.push(saveButton);
1269    buttons.push(this.createButton("wf-rename-profile-close", "wf-modal-close", "#", "Cancel"));
1270
1271    $(saveButton).on("touchend click", function () {
1272        var newName = $("#wfmodal-profile-new-name").val();
1273        if (newName.length > 0) {
1274            myself.doRenameProfileAction();
1275        }
1276        else {
1277            $(".wfmodal-message").show();
1278        }
1279        return false;
1280    });
1281
1282    var settings = {
1283        id: this.renameProfileModalId,
1284        title: "",
1285        body: str,
1286        buttons: buttons
1287    };
1288
1289    return this.createModal(settings);
1290}
1291
1292// Alert Modal
1293WFModalManager.prototype.createAlertModal = function () {
1294    var settings = {
1295        id: this.alertModalId,
1296        title: "",
1297        body: "<div class='wfmodal-alert-message'></div>",
1298        buttons: [this.createButton("wf-alert-modal-close", "wf-modal-close", "#", "Close")]
1299    };
1300    return this.createModal(settings);
1301}
1302
1303// Loading Modal
1304WFModalManager.prototype.createLoadingModal = function () {
1305    var settings = {
1306        id: this.loadingModalId,
1307        title: "",
1308        body: "<div class='loading3B3B3B'></div>"
1309    };
1310
1311    return this.createModal(settings);
1312}
1313
1314// Replicate Profile Modal
1315WFModalManager.prototype.createReplicateProfileModal = function () {
1316    var myself = this;
1317    var saveBtn = this.createButton("wf-replicate-save", "", "#", "Save");
1318    var str = "<p id='replicateProgress'></p>"
1319    str += "<div class='wfmodal-input-field-container'>";
1320    str += "<div class='wfmodal-input-field-label'><span>Name:</span></div>";
1321    str += "<div class='wfmodal-textfield'><input type='text' data-profile-id='' id='replicateProfileName'/></div>";
1322    str += "</div>";
1323
1324    $(saveBtn).on("touchend click", function () {
1325        if ($("#replicateProfileName").val() != "") {
1326            wfProfileManager.replicateProfile($("#replicateProfileName").attr("data-profile-id"), $("#replicateProfileName").val(), primaryActivity, function (status) {
1327                if (status.status_code > 0) {
1328                    $("#replicateProgress").html(data.status_message);
1329                } else {
1330                    wfModalManager.close();
1331                }
1332            });
1333        } else {
1334            $("#replicateProgress").html("Invalid profile name").show();
1335        }
1336
1337        return false;
1338    });
1339
1340    var settings = {
1341        id: this.replicateProfileModalId,
1342        title: "Save Profile As",
1343        body: str,
1344        buttons: [saveBtn, this.createButton("wf-replicate-modal-close", "wf-modal-close", "#", "Close")]
1345    };
1346
1347    return this.createModal(settings);
1348}
1349
1350// Membership Modal
1351WFModalManager.prototype.createMembershipModal = function () {
1352    var myself = this;
1353    var str = "<div class='wfmodal-row'>Feature: <span class='wfmodal-value' id='wfmodal-feature-title'></span></div>" +
1354        "<div class='wfmodal-row' id='wfmodal-feature-membershipstr'></div>" +
1355        "<div class='wfmodal-row'>Your Current Membership Level: <span class='wfmodal-value' id='wfmodal-current-member-level'></span></div>";
1356
1357    var signInButton = this.createButton("wfmodal-membership-signin", "", "#", "Sign In");
1358    $(signInButton).on("touchend click", function () {
1359        window.location = myself.options.userSignInUrl;
1360        return false;
1361    });
1362
1363    var upgradeButton = this.createButton("wfmodal-membership-upgrade", "", "#", "Upgrade");
1364    $(upgradeButton).on("touchend click", function () {
1365        window.location = myself.options.learnMoreUrl;
1366        return false;
1367    });
1368
1369    var learnMoreButton = this.createButton("wfmodal-learnmore", "", "#", "Learn More");
1370    $(learnMoreButton).on("touchend click", function () {
1371        window.location = myself.options.learnMoreUrl;
1372        return false;
1373    });
1374
1375    var cancelButton = this.createButton("membership-close", "wf-modal-close", "#", "Cancel");
1376
1377    var settings = {
1378        id: this.membershipModalId,
1379        title: "",
1380        body: str,
1381        buttons: [signInButton, upgradeButton, learnMoreButton, cancelButton]
1382    };
1383
1384    return this.createModal(settings);
1385}
1386
1387// Membership Modal
1388WFModalManager.prototype.createDownloadDataModal = function () {
1389    var myself = this;
1390    var str = "<div class='wfmodal-row'>Download all archived observation records for the period of time listed below.</div>" +
1391        "<div class='wfmodal-row'><span class='wfmodal-label'>Start:</span><span class='wfmodal-value-text' id='wfmodal-download-data-start-date'></span></div>" +
1392        "<div class='wfmodal-row'><span class='wfmodal-label'>End:</span><span class='wfmodal-value-text' id='wfmodal-download-data-end-date'></span></div>" +
1393        "<div class='wfmodal-row'><span class='wfmodal-label'>Format:</span><span class='wfmodal-value-text' id='wfmodal-download-data-format'>csv (header row included)</span></div>" +
1394        "<div class='wfmodal-row'><span class='wfmodal-label'>Fields:</span><span class='wfmodal-value-text' id='wfmodal-download-data-fields'>All available fields.  Can include the following: station meta data, wind(avg,gust,lull,dir), air temperature, pressure, precipitation, water temperature, wave height, wave period</span></div>"
1395
1396    var downloadButton = this.createButton("wfmodal-download", "", "#", "Download");
1397    $(downloadButton).on("touchend click", function () {
1398        myself.doDownloadData();
1399        return false;
1400    });
1401
1402    var closeButton = this.createButton("wfmodal-cancel", "wf-modal-close", "#", "Cancel");
1403
1404    var settings = {
1405        id: this.downloadDataModalId,
1406        title: "",
1407        body: str,
1408        buttons: [downloadButton, closeButton]
1409    };
1410
1411    return this.createModal(settings);
1412}
1413
1414// Info Window
1415WFModalManager.prototype.createInfoWindow = function () {
1416    var str = "<div id='info-window' title='More Information' style='display:none;'>";
1417    str += "<div id='info-window-content'></div>";
1418    str += "</div>";
1419
1420    $("body").append(str);
1421
1422    $("#info-window").dialog({
1423        autoOpen: false,
1424        draggable: false,
1425        modal: true,
1426        resizable: false,
1427        width: "auto"
1428    });
1429}
1430
1431
1432//////////////////////////////////////////////////////////////////////////////////////
1433//  Common HTML Parts
1434//////////////////////////////////////////////////////////////////////////////////////
1435// Header
1436WFModalManager.prototype.createModalHeader = function (title) {
1437    var hdr = document.createElement("div");
1438    hdr.className = "wf-modal-title";
1439    hdr.innerHTML = "<h2 class='wf-modal-title jw-h1'>" + title + "</h2><p class='wfmodal-instructions' style='display: none;'></p>";
1440
1441    return hdr;
1442}
1443
1444// Footer
1445WFModalManager.prototype.createModalFooter = function () {
1446    var foot = document.createElement("div");
1447    foot.className = "wfmodal-footer";
1448    foot.innerHTML = "<span class='wfmodal-status-good'></span><span class='wfmodal-status-bad'></span>";
1449
1450    return foot;
1451}
1452
1453WFModalManager.prototype.getSelectProfileLinks = function () {
1454    var str = "";
1455    if (this.options.userSignedIn == "true") {
1456        str += "<ul class='wfmodal-links-ul'>";
1457        str += "<li><a onclick='javascript:wfModalManager.addProfile();return false;' href='#' class='createProfileShow' id='createProfileShow'>Create Profile</a></li>";
1458        str += "<li><a href='/en-us/Favorites/ManageFavorites.aspx'>Manage Favorites Profiles</a></li>";
1459        str += "</ul>";
1460    } else {
1461        // Working with a local profile
1462        str += "<p>You are using a local profile.  To save your profile <a href='#' class='clkSignIn underline'>Sign In</a> or <a href='#' class='clkLearnMore underline'>Create An Account</a>.</p>";
1463    }
1464
1465    return str;
1466}
1467
1468// Body - Profile List
1469WFModalManager.prototype.createSelectProfileList = function () {
1470    var sb = "";
1471
1472    sb += "<div class='wfmodal-body wfmodal-profile-scroller' style='height: auto;'>";
1473    sb += "<table width='95%' class='wfmodal-table' id='profileListTable'>";
1474    sb += "<thead><tr><th>
1474Your Profiles</th><th class='starColumn'>Add / Remove</th></tr></thead>";
1475
1476    var profiles = wfProfileManager.getProfiles();
1477    var myself = this;
1478    var rowClass = "alt";
1479
1480    $.each(profiles, function (i, profile) {
1481        if (profile.my_profile == "true") {
1482            var onClickStr = "";
1483            var starButtonClass = "";
1484            var hoverTitleStr = "";
1485
1486            if (myself.currentActivity == "addspottoprofile") {
1487                if (userSignedIn == "true") {
1488                    var favSpotIdInProfile = wfProfileManager.getFavSpotIdForSpotIdInProfile(myself.spotId, profile.profile_id);  // returns -1 if spot not in profile
1489                    if (favSpotIdInProfile > 0) {
1490                        // spot already in this profile
1491                        onClickStr = "javascript:wfModalManager.doRemoveSpotFromProfile(\"" + myself.spotId + "\",\"" + favSpotIdInProfile + "\",\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1492                        starButtonClass = "wfmodal-favorite-yellow";
1493                        hoverTitleStr = "Remove from " + profile.name;
1494                    }
1495                    else {
1496                        // spot not in this profile
1497                        onClickStr = "javascript:wfModalManager.doAddSpotToProfile(\"" + myself.spotId + "\",\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1498                        starButtonClass = "wfmodal-favorite";
1499                        hoverTitleStr = "Add to " + profile.name;
1500                    }
1501                } else {
1502                    // Working with local profile
1503                    if (wfProfileManager.isSpotIdInProfile(myself.spotId, profile.profile_id)) {
1504                        // spot already in this profile
1505                        onClickStr = "javascript:wfModalManager.doRemoveSpotFromProfile(\"" + myself.spotId + "\",null,\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1506                        starButtonClass = "wfmodal-favorite-yellow";
1507                        hoverTitleStr = "Remove from " + profile.name;
1508                    } else {
1509                        // spot not in this profile
1510                        onClickStr = "javascript:wfModalManager.doAddSpotToProfile(\"" + myself.spotId + "\",\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1511                        starButtonClass = "wfmodal-favorite";
1512                        hoverTitleStr = "Add to " + profile.name;
1513                    }
1514                }
1515            }
1516            if (myself.currentActivity == "addarbitraryspottoprofile") {
1517                if (userSignedIn == "true") {
1518                    // do lookup by lat lon incase this arb spot is in multiple profiles (same lat lon with different fav spot ids)                                
1519                    var favSpotIdInProfile = wfProfileManager.getFavSpotIdForLatLonInProfile(myself.spotLat, myself.spotLon, profile.profile_id);
1520
1521                    if (favSpotIdInProfile > 0) {
1522                        // spot already in this profile                    
1523                        onClickStr = "javascript:wfModalManager.doRemoveArbitrarySpotFromProfile(\"" + favSpotIdInProfile + "\",\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1524                        starButtonClass = "wfmodal-favorite-yellow";
1525                        hoverTitleStr = "Remove from " + profile.name;
1526                        myself.favSpotId = "";
1527                    }
1528                    else {
1529                        // spot not in this profile
1530                        onClickStr = "javascript:wfModalManager.doAddArbitrarySpotToProfile(\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1531                        starButtonClass = "wfmodal-favorite";
1532                        hoverTitleStr = "Add to " + profile.name;
1533                    }
1534                } else {
1535                    // Local profile.  We don't have fav spot id's
1536                    if (wfProfileManager.isLatLonInProfile(myself.lat, myself.lon, profile.profile_id)) {
1537                        // spot already in this profile                    
1538                        onClickStr = "javascript:wfModalManager.doRemoveArbitrarySpotFromProfile(null,\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1539                        starButtonClass = "wfmodal-favorite-yellow";
1540                        hoverTitleStr = "Remove from " + profile.name;
1541                        myself.favSpotId = "";
1542                    } else {
1543                        // spot not in this profile
1544                        onClickStr = "javascript:wfModalManager.doAddArbitrarySpotToProfile(\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1545                        starButtonClass = "wfmodal-favorite";
1546                        hoverTitleStr = "Add to " + profile.name;
1547                    }
1548                }
1549
1550            }
1551            else if (myself.currentActivity == "addcontainertoprofile") {
1552                onClickStr = "javascript:wfModalManager.doAddContainerToProfile(\"" + profile.profile_id + "\",\"" + escape(profile.name) + "\")";
1553                starButtonClass = "wfmodal-favorite";
1554                hoverTitleStr = "Add to " + profile.name;
1555            }
1556
1557            sb += "<tr class='" + rowClass + "'><td>" + profile.name + "</td><td class='wfcontainer-center starColumn' id='wfmodal-td-" + profile.profile_id + "'><div onclick='" + onClickStr + "' class='" + starButtonClass + "' id='" + profile.profile_id + "' title='" + hoverTitleStr + "'></div></td></tr>";
1558        }
1559        else {
1560            sb += "<tr  class='" + rowClass + "'><td>" + profile.name + "</td><td class='notyours'>Owner: " + profile.created_by + " - <a href='/en-us/Favorites/ViewFavorites.aspx?pid=" + profile.profile_id + "'>View Profile</a></td></tr>";
1561        }
1562
1563        // Switch the row class
1564        rowClass = rowClass == "alt" ? "" : "alt";
1565    });
1566
1567    sb += "</table>";
1568    sb += "</div>";
1569
1570    return sb;
1571}
1572
1573////////////////////////////////////////////////////////////////////////////////
1574////////////////////////////////////////////////////////////////////////////////
1575////////////////////////////////////////////////////////////////////////////////
1576// Click Event Handlers
1577////////////////////////////////////////////////////////////////////////////////
1578////////////////////////////////////////////////////////////////////////////////
1579////////////////////////////////////////////////////////////////////////////////
1580
1581////////////////////////////////////////////////////////////////////////////////
1582// Add/Remove Container - Click Event Handlers
1583////////////////////////////////////////////////////////////////////////////////
1584// Add Container to Profile
1585WFModalManager.prototype.doAddContainerToProfile = function (profileId, profileName) {
1586    profileName = unescape(profileName);
1587
1588    if (this.options.userSignedIn == "false") {
1589        // User not signed in.  Add spot to the local profile        
1590        var profile = new WFLocalProfile(this.options.tokenDomain);
1591
1592        //containerId, containerTypeId, containerName, flowviewOptions, options
1593        var newContainerId = profile.newContainerId();
1594        var favCtn = new WFFavoriteContainer(newContainerId, this.containerTypeIdField, this.nameField, null, this.optionsField);
1595        profile.addContainer(favCtn);
1596
1597        $("#wfmodal-td-" + profileId).empty();
1598        $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveContainerFromProfile(\"" + newContainerId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1599
1600        // Update the page title
1601        $(".wf-modal-title").html(this.nameField + " added to " + profileName);
1602
1603    } else {
1604        var myself = this;
1605        var ps = new WFProfileService({ "baseURL": this.options.baseURL, "token": this.options.token });
1606
1607        profileName = unescape(profileName);
1608
1609        var urlData = {
1610            "container_type_id": this.containerTypeIdField,
1611            "name": this.nameField,
1612            "options": this.optionsField,
1613            "profile_id": profileId
1614        };
1615
1616        ps.addContainerToProfile(urlData, function (data) {
1617            if (data.status_code == 0) {
1618                var newContainerId = data.status_message;
1619                $("#wfmodal-td-" + profileId).empty();
1620                $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveContainerFromProfile(\"" + newContainerId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1621
1622                // Update the page title
1623                $(".wf-modal-title").html(myself.nameField + " added to " + profileName);
1624            }
1625        });
1626
1627    }
1628}
1629
1630// Remove Container From Profile
1631WFModalManager.prototype.doRemoveContainerFromProfile = function (containerId, profileId, profileName) {
1632    profileName = unescape(profileName);
1633    var url = this.options.baseURL + "/profile/removeContainerFromProfile?callback=?";
1634    var urldata = new Object();
1635    var self = this;
1636    urldata.wf_token = this.options.token;;
1637    urldata.container_id = containerId;;
1638
1639    this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
1640        if (data.status_code == 0) {
1641            $("#wfmodal-td-" + profileId).empty();
1642            $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doAddContainerToProfile(\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite' id='" + profileId + "' title='Add to " + profileName + "'></div>");
1643            // Update the page title
1644            $(".wf-modal-title").html(self.nameField + " removed from " + profileName);
1645        }
1646    }, "json");
1647}
1648
1649////////////////////////////////////////////////////////////////////////////////
1650// Add/Remove Arbitrary Spot - Click Event Handlers
1651////////////////////////////////////////////////////////////////////////////////
1652// Add Arbitrary Spot to Profile
1653WFModalManager.prototype.doAddArbitrarySpotToProfile = function (profileId, profileName) {
1654    if (this.options.userSignedIn == "false") {
1655        // User not signed in.  Add spot to the local profile        
1656        var profile = new WFLocalProfile(this.options.tokenDomain);
1657
1658        //typeId, favSpotId, spotId, lat, lon
1659        var favSpot = new WFFavoriteSpot("101", null, null, this.spotLat, this.spotLon);
1660        newFavSpotId = profile.addFavSpot(favSpot);
1661
1662        this.reloadPageOnClose = true;
1663
1664        $("#wfmodal-td-" + profileId).empty();
1665        $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveArbitrarySpotFromProfile(\"" + newFavSpotId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1666
1667        // Update the page title
1668        $(".wf-modal-title").html(this.nameField + " added to " + profileName);
1669    } else {
1670        var myself = this;
1671        var ps = new WFProfileService({ "baseURL": this.options.baseURL, "token": this.options.token });
1672
1673        profileName = unescape(profileName);
1674
1675        var urlData = {
1676            "lat": this.spotLat,
1677            "lon": this.spotLon,
1678            "name": this.nameField,
1679            "profile_id": profileId,
1680            "spot_types": this.spotType
1681        };
1682
1683        ps.addSpotToProfile(urlData, function (data) {
1684            if (data.status_code == 0) {
1685                var newFavSpotId = data.status_message;
1686
1687                myself.reloadPageOnClose = true;
1688
1689                $("#wfmodal-td-" + profileId).empty();
1690                $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveArbitrarySpotFromProfile(\"" + newFavSpotId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1691
1692                // Update the page title
1693                $(".wf-modal-title").html(myself.nameField + " added to " + profileName);
1694            }
1695        });
1696
1697    }
1698}
1699
1700// Remove Arbitrary Spot From Profile
1701WFModalManager.prototype.doRemoveArbitrarySpotFromProfile = function (favSpotId, profileId, profileName) {
1702
1703    if (this.options.userSignedIn == "false") {
1704        var profile = new WFLocalProfile(this.options.tokenDomain);
1705        var favSpot = profile.getSpotByLatLon(this.spotLat, this.spotLon);
1706
1707        if (profile.removeFavSpot(favSpot)) {
1708            this.reloadPageOnClose = true;
1709            $("#wfmodal-td-" + profileId).empty();
1710            $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doAddArbitrarySpotToProfile(\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite' id='" + profileId + "' title='Add to " + profileName + "'></div>");
1711            // Update the page title
1712            $(".wf-modal-title").html(this.nameField + " removed from " + profileName);
1713        }
1714    } else {
1715        profileName = unescape(profileName);
1716        var url = this.options.baseURL + "/profile/removeSpotFromProfile?callback=?";
1717        var urldata = new Object();
1718        urldata.wf_token = this.options.token;;
1719        urldata.fav_spot_id = favSpotId;
1720
1721        var myself = this;
1722        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
1723            if (data.status_code == 0) {
1724                myself.reloadPageOnClose = true;
1725                $("#wfmodal-td-" + profileId).empty();
1726                $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doAddArbitrarySpotToProfile(\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite' id='" + profileId + "' title='Add to " + profileName + "'></div>");
1727                // Update the page title
1728                $(".wf-modal-title").html(myself.nameField + " removed from " + profileName);
1729            }
1730        }, "json");
1731    }
1732}
1733
1734
1735////////////////////////////////////////////////////////////////////////////////
1736// Add/Remove Spot - Click Event Handlers
1737////////////////////////////////////////////////////////////////////////////////
1738// Add Spot to Profile
1739WFModalManager.prototype.doAddSpotToProfile = function (spotId, profileId, profileName) {
1740
1741    if (this.options.userSignedIn == "false") {
1742        // User not signed in.  Add spot to the local profile        
1743        var profile = new WFLocalProfile(this.options.tokenDomain);
1744
1745        //typeId, favSpotId, spotId, lat, lon
1746        var favSpot = new WFFavoriteSpot(this.spotType, null, spotId, null, null);
1747        profile.addFavSpot(favSpot);
1748
1749        this.reloadPageOnClose = true;
1750
1751        $("#wfmodal-td-" + profileId).empty();
1752        $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveSpotFromProfile(\"" + spotId + "\",null,\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1753
1754        // Update the page title
1755        $(".wf-modal-title").html(this.nameField + " added to " + profileName);
1756
1757    } else {
1758        var myself = this;
1759        var ps = new WFProfileService({ "baseURL": this.options.baseURL, "token": this.options.token });
1760
1761        profileName = unescape(profileName);
1762
1763        var urlData = {
1764            "profile_id": profileId,
1765            "spot_id": spotId,
1766            "spot_types": this.spotType
1767        };
1768
1769        ps.addSpotToProfile(urlData, function (data) {
1770            if (data.status_code == 0) {
1771                var newFavSpotId = data.status_message;
1772
1773                myself.reloadPageOnClose = true;
1774
1775                $("#wfmodal-td-" + profileId).empty();
1776                $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doRemoveSpotFromProfile(\"" + spotId + "\",\"" + newFavSpotId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite-yellow' id='" + profileId + "' title='Remove from " + profileName + "'></div>");
1777
1778                // Update the page title
1779                $(".wf-modal-title").html(myself.nameField + " added to " + profileName);
1780
1781                _gaq.push(['_trackEvent', 'Favorite', 'Add', myself.nameField + " " + spotId]);
1782            }
1783        });
1784    }
1785
1786}
1787
1788// Remove Spot From Profile
1789WFModalManager.prototype.doRemoveSpotFromProfile = function (spotId, favSpotId, profileId, profileName) {
1790    profileName = unescape(profileName);
1791
1792
1793    if (this.options.userSignedIn == "false") {
1794        // User not signed in.  Removespot from the local profile        
1795        var profile = new WFLocalProfile(this.options.tokenDomain);
1796
1797        //typeId, favSpotId, spotId, lat, lon
1798        //var favSpot = new WFFavoriteSpot(null, null, spotId, null, null);
1799        var favSpot = profile.getSpotById(spotId);
1800
1801        if (profile.removeFavSpot(favSpot)) {
1802            this.reloadPageOnClose = true;
1803            $("#wfmodal-td-" + profileId).empty();
1804            $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doAddSpotToProfile(\"" + spotId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite' id='" + profileId + "' title='Add to " + profileName + "'></div>");
1805            // Update the page title
1806            $(".wf-modal-title").html(this.nameField + " removed from " + profileName);
1807        }
1808    } else {
1809        var url = this.options.baseURL + "/profile/removeSpotFromProfile?callback=?";
1810        var urldata = new Object();
1811        urldata.wf_token = this.options.token;
1812        urldata.fav_spot_id = favSpotId;
1813        urldata.profile_id = profileId;
1814
1815        var myself = this;
1816        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
1817            if (data.status_code == 0) {
1818                myself.reloadPageOnClose = true;
1819                $("#wfmodal-td-" + profileId).empty();
1820                $("#wfmodal-td-" + profileId).append("<div onclick='javascript:wfModalManager.doAddSpotToProfile(\"" + spotId + "\",\"" + profileId + "\",\"" + escape(profileName) + "\")' class='wfmodal-favorite' id='" + profileId + "' title='Add to " + profileName + "'></div>");
1821                _gaq.push(['_trackEvent', 'Favorite', 'Remove', myself.nameField + " " + spotId]);
1822
1823                // Update the page title
1824                $(".wf-modal-title").html(myself.nameField + " removed from " + profileName);
1825            }
1826        }, "json");
1827    }
1828}
1829
1830
1831////////////////////////////////////////////////////////////////////////////////
1832// Add Profile - Click Event Handlers
1833////////////////////////////////////////////////////////////////////////////////
1834// Add Profile
1835WFModalManager.prototype.doAddProfileAction = function () {
1836    var newProfileName = $("#wfmodal-profile-name").val();
1837
1838    var myself = this;
1839    wfProfileManager.createNewProfile(newProfileName, function callback(createNewProfileStatusCode) {
1840        // Send to next step
1841        if (myself.currentActivity == "addcontainertoprofile") {
1842            myself.addContainerToProfileStep2();
1843        }
1844        else if (myself.currentActivity == "addspottoprofile") {
1845            myself.addSpotToProfile(myself.spotId, myself.nameField, myself.spotType);
1846        }
1847        else if (myself.currentActivity == "addarbitraryspottoprofile") {
1848            myself.addArbitrarySpotToProfileStep2();
1849        }
1850        else {
1851            if (createNewProfileStatusCode == 0) {
1852                myself.displayAlert("Create Profile", "The profile was created.");
1853            }
1854            else {
1855                myself.displayAlert("Create Profile", "There was an error while creating the profile.");
1856            }
1857        }
1858    });
1859}
1860
1861WFModalManager.prototype.doRenameProfileAction = function () {
1862    var profileId = $(this.renameProfileModal).attr("data-profile-id");
1863    var newProfileName = $("#wfmodal-profile-new-name").val();
1864    var myself = this;
1865
1866    wfProfileManager.renameProfile(profileId, newProfileName, function callback(createNewProfileStatusCode) {
1867        if (createNewProfileStatusCode == 0) {
1868            myself.displayAlert("Rename Profile", "The profile was renamed.");
1869        }
1870        else {
1871            myself.displayAlert("Rename Profile", "There was an error while renaming the profile.");
1872        }
1873    });
1874}
1875
1876// Cancel Add Profile
1877WFModalManager.prototype.doCancelAddProfileAction = function () {
1878    if (this.currentActivity == "addcontainertoprofile") {
1879        this.addContainerToProfileStep2();
1880    }
1881    else if (this.currentActivity == "addspottoprofile") {
1882        this.addSpotToProfile(this.spotId, this.nameField, this.spotType);
1883    }
1884    else if (this.currentActivity == "addarbitraryspottoprofile") {
1885        this.addArbitrarySpotToProfileStep2();
1886    }
1887}
1888
1889// saveContainerToNewProfile
1890WFModalManager.prototype.doSaveContainerToNewProfile = function () {
1891    this.addContainerToProfile(this.nameField, this.containerTypeIdField, this.optionsField);
1892}
1893
1894//////////////////////////////////////////////////////////////////////////////
1895// Other
1896//////////////////////////////////////////////////////////////////////////////
1897WFModalManager.prototype.createModal = function (settings) {
1898    var div = document.createElement("div");
1899    var wrapper = document.createElement("div");
1900    var body = document.createElement("div");
1901    var buttons = document.createElement("div");
1902    var header = this.createModalHeader(settings.title);
1903
1904    div.className = "wf-modal";
1905    div.setAttribute("id", settings.id);
1906
1907    buttons.className = "wf-modal-buttons clearfix";
1908    if (settings.hasOwnProperty("buttons") && settings.buttons) {
1909        for (var x = 0; x < settings.buttons.length; x++) {
1910            buttons.appendChild(settings.buttons[x]);
1911        }
1912    }
1913
1914    body.className = "wf-modal-body clearfix";
1915    body.innerHTML = settings.body;
1916
1917    wrapper.className = "wf-modal-content";
1918    wrapper.appendChild(header);
1919    wrapper.appendChild(body);
1920    wrapper.appendChild(buttons);
1921
1922    if (settings.hasOwnProperty("extras") && settings.extras) {
1923        var extras = document.createElement("div");
1924        extras.className = "wf-modal-extras";
1925        extras.innerHTML = settings.extras;
1926
1927        wrapper.appendChild(extras);
1928    }
1929
1930    if (settings.hasOwnProperty("footer") && settings.footer) {
1931        var footer = document.createElement("div");
1932        footer.className = "wf-modal-footer";
1933        footer.innerHTML = settings.footer;
1934
1935        wrapper.appendChild(footer);
1936    }
1937
1938    div.appendChild(wrapper);
1939
1940    return div;
1941}
1942
1943WFModalManager.prototype.createButton = function (id, classId, href, label) {
1944    var btn = document.createElement("button");
1945    if (id) {
1946        btn.id = id;
1947    }
1948    btn.className = classId;
1949    btn.innerHTML = label;
1950
1951    return btn;
1952}
1953
1954WFModalManager.prototype.setModalTitle = function (title, modalId) {
1955    $("#" + modalId + " .wf-modal-title").empty().append(title);
1956}
1957
1958WFModalManager.prototype._attachEventHandlers = function () {
1959    var self = this;
1960
1961    $(".wf-modal-content").on("touchend click", function (e) {
1962        e.stopPropagation();
1963
1964        return false;
1965    });
1966
1967    $(".wf-modal-body a, .wf-modal-extras a, .wf-modal-body input, .wf-modal #wfmodal-profile-list").on("touchend click", function (e) {
1968        e.stopPropagation();
1969    });
1970
1971    $(".wf-modal-close").on("touchend click", function (e) {
1972        self.close();
1973    });
1974
1975    $(".wf-modal").on("touchend click", function (e) {
1976        var modalOpen = $("html").hasClass("modal-open");
1977
1978        if (modalOpen) {
1979            self.close();
1980        }
1981    });
1982}
1983
1984WFModalManager.prototype.open = function (id) {
1985    this.scrollTop = $("body").scrollTop();
1986    this.openModalId = id;
1987
1988    // Close any open modals
1989    $(".wf-modal").hide();
1990
1991    $("html").addClass("modal-open");
1992    $("#" + id).show();
1993}
1994
1995WFModalManager.prototype.close = function () {
1996    if (this.openModalId) {
1997        $("html").removeClass("modal-open");
1998
1999        if (this.options.isMobile && this.scrollTop) {
2000            $("body").scrollTop(this.scrollTop);
2001        }
2002
2003        $("#" + this.openModalId).hide();
2004
2005        this.openModalId = null;
2006        this.scrollTop = null;
2007
2008        if (this.reloadPageOnClose) {
2009            location.reload();
2010        } else {
2011            if (typeof wfProfileManager === "undefined") return;
2012
2013            wfProfileManager.loadProfiles();
2014        }
2015    }
2016}
2017
2018//
2019WFModalManager.prototype.debug = function () {
2020    console.debug("this.member_level=" + this.memberLevel);
2021    console.debug("this.show_ads=" + this.showAds);
2022    console.debug("this.show_ads_toggle=" + this.showAdsToggle);
2023    console.debug("this.default_ads_toggle_state=" + this.defaultAdsToggleState);
2024    console.debug("this.trial_end_date=" + this.trialEndDate);
2025
2026    console.debug("this.nameField=" + this.nameField);
2027    console.debug("this.containerId=" + this.containerId);
2028    console.debug("this.containerTypeIdField=" + this.containerTypeIdField);
2029    console.debug("this.profileNameField=" + this.profileNameField);
2030    console.debug("this.currentActivity=" + this.currentActivity);
2031    //    console.debug("spotId="+spotId);
2032    console.debug("spotType=" + this.spotType);
2033    console.debug("this.spotLat=" + this.spotLat);
2034    console.debug("this.spotLon=" + this.spotLon);
2035    console.debug("this.favSpotId=" + this.favSpotId);
2036    console.debug("this.reloadPageOnClose=" + this.reloadPageOnClose);
2037    console.debug("this.profileId=" + this.profileId);
2038}
2039
2040
2041/////////////////////////////////////////////////////////////////////////////////////////
2042/////////////////////////////////////////////////////////////////////////////////////////
2043/////////////////////////////////////////////////////////////////////////////////////////
2044//  WFModalManager - END
2045/////////////////////////////////////////////////////////////////////////////////////////
2046/////////////////////////////////////////////////////////////////////////////////////////
2047/////////////////////////////////////////////////////////////////////////////////////////
2048
2049/////////////////////////////////////////////////////////////////////////////////////////
2050/////////////////////////////////////////////////////////////////////////////////////////
2051//  WFProfileManager
2052//      - Data store for getProfiles (profile set with containers and favorite spots)
2053//      - Useful functions for getting profile data
2054//      - Functions for adding things (spots, containers) to profiles
2055/////////////////////////////////////////////////////////////////////////////////////////
2056/////////////////////////////////////////////////////////////////////////////////////////
2057
2058/////////////////////////////////////////////////////////////////////////////////////////
2059/////////////////////////////////////////////////////////////////////////////////////////
2060//  profiles definition
2061//
2062//  profiles: [
2063//      {
2064//          activity: "Kite"
2065//          views: 2
2066//          alerts_enabled: "true"
2067//          created_by: "jforare"
2068//          master: "false"
2069//          my_profile: "true"
2070//          name: "Vacation Spots"
2071//          profile_id: 124097
2072//          containers: [
2073//              {
2074//                  container_id: 20543
2075//                  container_type_id: "Favorite Spots"
2076//                  container_name: "Favorite Spots"
2077//                  options: ""
2078//              }
2079//          ]
2080//          favorite_spots: [
2081//              {
2082//                  type_id: 1
2083//                  fav_spot_id: 287303
2084//                  spot_id: 53616
2085//              }
2086//          ]
2087//      }
2088//  ]
2089/////////////////////////////////////////////////////////////////////////////////////////
2090/////////////////////////////////////////////////////////////////////////////////////////
2091
2092
2093/////////////////////////////////////////////////////////////////////////////////////////
2094/////////////////////////////////////////////////////////////////////////////////////////
2095//  WFModelDataSet - Constructor
2096//    - Empty constructor so you can put into page scope early and initialize later
2097/////////////////////////////////////////////////////////////////////////////////////////
2098/////////////////////////////////////////////////////////////////////////////////////////
2099function WFProfileManager(options) {
2100    this.default_args = {
2101        "activeProfileId": 0,
2102        "baseURL": "https://api.weatherflow.com/wxengine/rest",       // Weather Engine baseurl - client might want to change to staging        
2103        "cad": null,
2104        "token": "",
2105        "tokenDomain": null,
2106        "userSignedIn": false
2107    };
2108
2109    this.options = options;
2110
2111    this.profiles;      // raw profiles array object from getProfiles();
2112    this.relevantProfiles; // row profiles array object from getRelevantProfiles();
2113    this.profilesLoaded = false;
2114    this.relevantProfilesLoaded = false;
2115    this.profileCount = -1;
2116    this.userLat = null;
2117    this.userLon = null;
2118    this.loadAllEventFired = false;
2119
2120    // set options - use default_args if not passed in options var
2121    for (var index in this.default_args) {
2122        if (typeof this.options[index] == "undefined") {
2123            this.options[index] = this.default_args[index];
2124        }
2125    }
2126
2127}
2128
2129
2130/////////////////////////////////////////////////////////////////////////////////////////
2131//  init
2132//    - load profiles
2133/////////////////////////////////////////////////////////////////////////////////////////
2134WFProfileManager.prototype.init = function () {
2135    this.loadProfiles();
2136}
2137
2138
2139// Allows client to ask if the profiles have loaded
2140WFProfileManager.prototype.profilesLoaded = function () {
2141    return this.profilesLoaded;
2142}
2143
2144
2145WFProfileManager.prototype.loadProfiles = function () {
2146
2147    if (this.options.userSignedIn == "true") {
2148        var url = this.options.baseURL + "/profile/getProfiles?callback=?";
2149        var urldata = new Object();
2150        urldata.wf_token = this.options.token;
2151        if (this.options.cad != null) {
2152            urldata.cad = this.options.cad;
2153        }
2154
2155        var myself = this;
2156        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2157            if (data.status.status_code == 0) {
2158                myself.profiles = data.profiles;
2159                myself.profilesLoaded = true;
2160
2161                myself.profileCount = data.profiles.length;
2162
2163                // Profiles are loaded.  Trigger the client event
2164                $("body").trigger("profiles_loaded");
2165
2166                // Trigger the all_profiles_loaded event if the relevant profiles have already loaded
2167                if (myself.relevantProfilesLoaded && !myself.loadAllEventFired) {
2168                    myself.loadAllEventFired = true;
2169                    $("body").trigger("all_profiles_loaded");
2170                }
2171            }
2172
2173            // Sync any local profiles
2174            myself.syncLocalProfile();
2175        }, "json");        
2176
2177    } else {
2178        // User not signed in.  Load or create their local profile
2179        //var data = undefined;
2180
2181        //data = this.getLocalProfile();
2182        var localProfile = new WFLocalProfile(this.options.tokenDomain);
2183
2184        if (localProfile != undefined) {
2185            this.profiles = localProfile.profiles;
2186            this.profilesLoaded = true;
2187            this.profileCount = 1;
2188
2189            // Profiles are loaded.  Trigger the client event
2190            $("body").trigger("profiles_loaded");
2191
2192            // Trigger the all_profiles_loaded event if the relevant profiles have already loaded
2193            if (this.relevantProfilesLoaded && !this.loadAllEventFired) {
2194                this.loadAllEventFired = true;
2195                $("body").trigger("all_profiles_loaded");
2196            }
2197        }
2198    }
2199}
2200
2201WFProfileManager.prototype.loadRelevantProfiles = function (bounds, primaryActivity) {
2202    var url = this.options.baseURL + "/profile/getRelevantProfiles?callback=?";
2203    var urldata = new Object();
2204    urldata.wf_token = this.options.token;
2205    urldata.num_per_page = 20;
2206    urldata.activity = primaryActivity;
2207    if (this.options.cad != null) {
2208        urldata.cad = this.options.cad;
2209    }
2210
2211    // TODO: Add geolocation hook if the bounds aren't set
2212
2213    // Add the bounds if provided by the client
2214    if (bounds) {
2215        urldata.lat_min = bounds.getSouthWest().lat();
2216        urldata.lon_min = bounds.getSouthWest().lng();
2217        urldata.lon_max = bounds.getNorthEast().lng();
2218        urldata.lat_max = bounds.getNorthEast().lat();
2219    }
2220
2221    var myself = this;
2222    this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2223
2224        myself.relevantProfiles = data.profiles;
2225        myself.relevantProfilesLoaded = true;
2226
2227        // Trigger the all_profiles_loaded event if the user's profiles have already loaded
2228        if (myself.profilesLoaded) {
2229            if (!myself.loadAllEventFired) {
2230                myself.loadAllEventFired = true;
2231                $("body").trigger("all_profiles_loaded");
2232            } else {
2233                $("body").trigger("relevant_profiles_loaded");
2234            }
2235        }
2236    }, "json");
2237}
2238
2239/////////////////////////////////////////////////////////////////////////////////////////
2240//  Setters/Getters
2241/////////////////////////////////////////////////////////////////////////////////////////
2242WFProfileManager.prototype.setProfiles = function (newProfilesObject) {
2243    this.profiles = newProfilesObject;
2244    this.profilesLoaded = true;
2245}
2246
2247WFProfileManager.prototype.getProfiles = function () {
2248    return this.profiles;
2249}
2250
2251
2252WFProfileManager.prototype.getRelevantProfiles = function () {
2253    return this.relevantProfiles;
2254}
2255
2256
2257WFProfileManager.prototype.getProfileCount = function () {
2258    if (this.profilesLoaded) {
2259        return this.profileCount;
2260    }
2261    else {
2262        return -1;
2263    }
2264}
2265
2266WFProfileManager.prototype.getProfileDisplayName = function (profileName) {
2267    if (profileName.length > 35) {
2268        return profileName.substring(0, 35) + "...";
2269    } else {
2270        return profileName;
2271    }
2272}
2273
2274WFProfileManager.prototype.isSpotIdInAnyProfile = function (spotIdToFind) {
2275    var spotFound = false;
2276        
2277    if (this.profilesLoaded) {
2278        $.each(this.profiles, function (i, profile) {
2279            $.each(profile.favorite_spots, function (j, favorite_spot) {
2280                if (favorite_spot.spot_id == spotIdToFind) {
2281                    spotFound = true;
2282                }
2283            });
2284        });
2285    }
2286    return spotFound;
2287}
2288
2289
2290WFProfileManager.prototype.isSpotIdInProfile = function (spotIdToFind, profileIdToLookIn) {
2291    var spotFound = false;
2292    $.each(this.profiles, function (i, profile) {
2293        if (profile.profile_id == profileIdToLookIn) {
2294            $.each(profile.favorite_spots, function (j, favorite_spot) {
2295                if (favorite_spot.spot_id == spotIdToFind) {
2296                    spotFound = true;
2297                }
2298            });
2299        }
2300    });
2301    return spotFound;
2302}
2303
2304
2305WFProfileManager.prototype.getFavSpotIdForSpotIdInProfile = function (spotIdToFind, profileIdToLookIn) {
2306    var returnFavSpotId = -1;
2307    $.each(this.profiles, function (i, profile) {
2308        if (profile.profile_id == profileIdToLookIn) {
2309            $.each(profile.favorite_spots, function (j, favorite_spot) {
2310                if (favorite_spot.spot_id == spotIdToFind) {
2311                    returnFavSpotId = favorite_spot.fav_spot_id;
2312                }
2313            });
2314        }
2315    });
2316    return returnFavSpotId;
2317}
2318
2319WFProfileManager.prototype.getFavSpotIdForLatLonInProfile = function (lat, lon, profileIdToLookIn) {
2320    var returnFavSpotId = -1;
2321
2322    $.each(this.profiles, function (i, profile) {
2323        if (profile.profile_id == profileIdToLookIn) {
2324            $.each(profile.favorite_spots, function (j, favorite_spot) {
2325                if (favorite_spot.lat == lat && favorite_spot.lon == lon) {
2326                    returnFavSpotId = favorite_spot.fav_spot_id;
2327                    return false; // break the loop
2328                }
2329            });
2330        }
2331        if (returnFavSpotId > -1) {
2332            return false; // break the loop
2333        }
2334    });
2335    return returnFavSpotId;
2336}
2337
2338WFProfileManager.prototype.isFavSpotIdInAnyProfile = function (favSpotIdToFind) {
2339    var spotFound = false;
2340    $.each(this.profiles, function (i, profile) {
2341        $.each(profile.favorite_spots, function (j, favorite_spot) {
2342            if (favorite_spot.fav_spot_id == favSpotIdToFind) {
2343                spotFound = true;
2344            }
2345        });
2346    });
2347    return spotFound;
2348}
2349
2350
2351WFProfileManager.prototype.isFavSpotIdInProfile = function (favSpotIdToFind, profileIdToLookIn) {
2352    var spotFound = false;
2353
2354    $.each(this.profiles, function (i, profile) {
2355        if (profile.profile_id == profileIdToLookIn) {
2356            $.each(profile.favorite_spots, function (j, favorite_spot) {
2357                if (favorite_spot.fav_spot_id == favSpotIdToFind) {
2358                    spotFound = true;                    
2359                }
2360            });
2361        }
2362    });
2363    return spotFound;
2364}
2365
2366WFProfileManager.prototype.isLatLonInAnyProfile = function (lat, lon) {
2367    var spotFound = false;
2368    
2369    $.each(this.profiles, function (i, profile) {
2370        $.each(profile.favorite_spots, function (j, favorite_spot) {
2371            if (favorite_spot.lat == lat && favorite_spot.lon == lon) {
2372                spotFound = true;
2373                return false; // break the loop
2374            }
2375        });
2376        if (spotFound) {
2377            return false; // break the loop
2378        }
2379    });
2380    
2381    return spotFound;
2382}
2383
2384WFProfileManager.prototype.isLatLonInProfile = function (lat, lon, profileIdToLookIn) {
2385    var spotFound = false;
2386    $.each(this.profiles, function (i, profile) {
2387        if (profile.profile_id == profileIdToLookIn) {
2388            $.each(profile.favorite_spots, function (j, favorite_spot) {
2389                if (favorite_spot.lat == lat && favorite_spot.lon == lon) {
2390                    spotFound = true;
2391                    return false;
2392                }
2393            });
2394        }
2395    });
2396    return spotFound;
2397}
2398
2399WFProfileManager.prototype.isProfileFavorited = function (profileIdToFind) {
2400    if (this.profilesLoaded) {
2401        var profileFound = false;
2402        $.each(this.profiles, function (i, profile) {
2403            if (profile.profile_id == profileIdToFind) {
2404                profileFound = true;
2405                return true;  // break out of the loop
2406            }
2407        });
2408        return profileFound;
2409    } else {        
2410        // Profiles have not loaded yet
2411        return false;
2412    }
2413}
2414
2415
2416
2417WFProfileManager.prototype.createNewProfile = function (newProfileName, callback) {
2418    var url = this.options.baseURL + "/profile/addProfile?callback=?";
2419    var urldata = new Object();
2420    urldata.wf_token = this.options.token;
2421    urldata.name = newProfileName;
2422    if (this.options.cad != null) {
2423        urldata.cad = this.options.cad;
2424    }
2425
2426    var myself = this;
2427    this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2428        // Step 1 - addProfile
2429        if (data.status_code == 0) {
2430            myself.loadProfiles();
2431
2432            if (callback) {
2433                callback(data.status_code);
2434            }
2435        }
2436        else {
2437            if (callback) {
2438                callback(data.status_code);
2439            };
2440        }
2441    }, "json");  // end Step 1 - addProfile
2442}
2443
2444WFProfileManager.prototype.deleteProfile = function (profileId, callback) {
2445    if (profileId) {
2446        var url = this.options.baseURL + "/profile/deleteProfile?callback=?";
2447        var urldata = new Object();
2448        urldata.wf_token = this.options.token;
2449        urldata.profile_id = profileId;        
2450
2451        var myself = this;
2452        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {            
2453            if (data.status_code == 0) {
2454                // Update the profile list
2455                myself.loadProfiles();
2456            }
2457            if (callback) {
2458                callback(data.status_code);
2459            }
2460        }, "json");
2461    } else {
2462        // Profile Id not passed
2463        if (callback) {
2464            callback(-1);
2465        }
2466    }
2467}
2468
2469WFProfileManager.prototype.addFavoriteProfile = function (profileId, primaryActivity, callback) {
2470
2471    if (profileId > 0) {
2472        var url = this.options.baseURL + "/profile/addFavoriteProfile?callback=?";
2473        var urldata = new Object();
2474        urldata.wf_token = this.options.token;
2475        urldata.profile_id = profileId;
2476        urldata.activity = primaryActivity;
2477        if (this.options.cad != null) {
2478            urldata.cad = this.options.cad;
2479        }
2480
2481        var myself = this;
2482        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2483            if (callback) {
2484                if (data.status_code == 0) {
2485                    // Update the profile list
2486                    myself.loadProfiles();
2487                }
2488                if (callback) {
2489                    callback(data.status_code);
2490                }
2491            }
2492        }, "json");
2493    } else {
2494        // Invalid profileId supplied
2495        if (callback) {
2496            callback(-1);
2497        }
2498    }
2499
2500}
2501
2502WFProfileManager.prototype.removeFavoriteProfile = function (profileId, primaryActivity, callback) {
2503
2504    if (profileId > 0) {
2505        var url = this.options.baseURL + "/profile/removeFavoriteProfile?callback=?";
2506        var urldata = new Object();
2507        urldata.wf_token = this.options.token;
2508        urldata.profile_id = profileId;
2509        urldata.activity = primaryActivity;
2510        if (this.options.cad != null) {
2511            urldata.cad = this.options.cad;
2512        }
2513
2514        var myself = this;
2515        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {            
2516            if (data.status_code == 0) {
2517                // Update the profile list
2518                myself.loadProfiles();
2519            }
2520            if (callback) {                
2521                callback(data.status_code);
2522            }
2523        }, "json");
2524    } else {
2525        // Invalid profileId supplied
2526        if (callback) {
2527            callback(-1);
2528        }
2529    }
2530
2531}
2532
2533WFProfileManager.prototype.replicateProfile = function (profileId, newName, primaryActivity, callback) {
2534    if (profileId) {
2535        var url = this.options.baseURL + "/profile/copyProfile?callback=?";
2536        var urldata = new Object();
2537        urldata.wf_token = this.options.token;
2538        urldata.profile_id = profileId;
2539        urldata.name = newName;
2540        urldata.activity = primaryActivity;
2541        if (this.options.cad != null) {
2542            urldata.cad = this.options.cad;
2543        }
2544
2545        var myself = this;
2546        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2547            if (data.status_code == 0) {
2548                // Update the profile list
2549                myself.loadProfiles();
2550            }
2551            if (callback) {                
2552                callback(data);
2553            }
2554        }, "json");
2555
2556    } else {
2557        if (callback) {
2558            callback(-1);
2559        }
2560    }
2561}
2562
2563WFProfileManager.prototype.renameProfile = function (profileId, newName, callback) {    
2564    if (profileId) {
2565        var url = this.options.baseURL + "/profile/updateProfile?callback=?";
2566        var urldata = new Object();
2567        urldata.wf_token = this.options.token;
2568        urldata.profile_id = profileId;
2569        urldata.name = newName;
2570
2571        if (this.options.cad != null) {
2572            urldata.cad = this.options.cad;
2573        }
2574
2575        var myself = this;
2576        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2577            if (data.status_code == 0) {
2578                // Update the profile list
2579                myself.loadProfiles();
2580            }
2581            if (callback) {
2582                callback(data.status_code);
2583            }
2584        }, "json");
2585    } else {
2586        if (callback) {
2587            callback(-1);
2588        }
2589    }     
2590}
2591
2592WFProfileManager.prototype.sortProfiles = function (profileIdList, callback) {    
2593    if (profileIdList) {
2594        var url = this.options.baseURL + "/profile/sortProfiles?callback=?";
2595        var urldata = new Object();
2596        urldata.wf_token = this.options.token;
2597        urldata.profile_id_list = profileIdList.toString();
2598
2599        var myself = this;
2600        this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2601            // Update the profile list
2602            myself.loadProfiles();
2603
2604            if (callback) {
2605                callback();
2606            }
2607
2608        }, "json");
2609
2610    } else {
2611        // profileIdList is invalid
2612        if (callback) {
2613            callback(-1);
2614        }
2615    }
2616}
2617
2618WFProfileManager.prototype.syncLocalProfile = function () {
2619    var profile = new WFLocalProfile(this.options.tokenDomain);
2620
2621    if (this.options.activeProfileId != undefined && this.options.activeProfileId > 0) {
2622        this.syncLocalSpots(this.options.activeProfileId, profile);
2623        this.syncLocalContainers(this.options.activeProfileId, profile);
2624    }
2625}
2626
2627WFProfileManager.prototype.syncLocalContainers = function (activeProfileId, profile) {    
2628    var localContainers = profile.profiles[0].containers;
2629
2630    for (var x = 0; x < localContainers.length; x++) {
2631        if (localContainers[x].container_type_id != "Favorite Spots") {
2632            this.addLocalContainerToProfile(localContainers[x], activeProfileId, function (data, ctn) {
2633                profile.removeContainer(ctn.container_id);
2634            });
2635        }
2636    }
2637}
2638
2639WFProfileManager.prototype.syncLocalSpots = function (activeProfileId, profile) {    
2640    var localSpots = profile.profiles[0].favorite_spots;
2641
2642    for (var x = 0; x < localSpots.length; x++) {
2643        this.addLocalSpotToProfile(localSpots[x], activeProfileId, function (data, spot) {
2644            profile.removeFavSpot(spot);
2645        });
2646    }
2647}
2648
2649WFProfileManager.prototype.addLocalContainerToProfile = function (ctn, profileId, callback) {
2650    var url = this.options.baseURL + "/profile/addContainerToProfile?callback=?";
2651    var urldata = new Object();
2652    urldata.wf_token = this.options.token;
2653    urldata.options = ctn.options;
2654    urldata.name = ctn.container_name;
2655    urldata.profile_id = profileId;
2656    urldata.container_type_id = ctn.container_type_id;
2657
2658    var myself = this;
2659    var profile = new WFLocalProfile(this.options.tokenDomain);
2660
2661    this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2662        if (callback) {
2663            callback(data, ctn);
2664        }
2665    }, "json");
2666    
2667}
2668
2669WFProfileManager.prototype.addLocalSpotToProfile = function (spot, profileId, callback) {
2670    var url = this.options.baseURL + "/profile/addSpotToProfile?callback=?";
2671    var urldata = new Object();
2672    urldata.wf_token = this.options.token;
2673    urldata.profile_id = profileId;
2674    urldata.spot_types = spot.type_id;
2675    if (spot.type_id == 101) {
2676        urldata.lat = spot.lat;
2677        urldata.lon = spot.lon;
2678        urldata.name = "Virtual Station (" + spot.lat.toFixed(2) + ", " + spot.lon.toFixed(2) + ")";        
2679    } else {
2680        urldata.spot_id = spot.spot_id;
2681    }    
2682
2683    var myself = this;
2684    this.jqRequest = $.getJSON(url, urldata, function (data, textStatus, jqXHR) {
2685        if (callback) {
2686            callback(data, spot);
2687        }
2688    }, "json");
2689
2690}
2691
2692WFProfileManager.prototype.isLocalProfileEmpty = function () {
2693    var localProfile = new WFLocalProfile(this.options.tokenDomain);
2694
2695    return localProfile.isEmpty();
2696}
2697
2698/////////////////////////////////////////////////////////////////////////////////////////
2699/////////////////////////////////////////////////////////////////////////////////////////
2700/////////////////////////////////////////////////////////////////////////////////////////
2701//  WFProfileManager - END
2702/////////////////////////////////////////////////////////////////////////////////////////
2703/////////////////////////////////////////////////////////////////////////////////////////
2704/////////////////////////////////////////////////////////////////////////////////////////
2705
2706function WFProfileService(options) {
2707    
2708    this.default_args = {
2709        "baseURL": "https://api.weatherflow.com/wxengine/rest",      // Weather Engine baseurl - client might want to change to staging
2710        "primaryActivity": "",
2711        "token": "",
2712        "unitsWind": "mph",
2713        "unitsTemp": "f",
2714        "unitsHeight": "ft"                
2715    };
2716
2717    this.options = options;
2718
2719    // set options - use default_args if not passed in options var
2720    for (var index in this.default_args) {
2721        if (typeof this.options[index] == "undefined") {
2722            this.options[index] = this.default_args[index];
2723        }
2724    }
2725}
2726
2727WFProfileService.prototype.addSpotToProfile = function (args, callback) {
2728    var url = this.options.baseURL + "/profile/addSpotToProfile?callback=?";
2729    var urlData = this.setArgs(args);
2730
2731    urlData.wf_token = this.options.token;
2732
2733    $.getJSON(url, urlData, function (data) {
2734        if (callback) {
2735            callback(data);
2736        }
2737    });
2738}
2739
2740WFProfileService.prototype.addContainerToProfile = function (args, callback) {
2741    var url = this.options.baseURL + "/profile/addContainerToProfile?callback=?";
2742    var urlData = this.setArgs(args);
2743
2744    urlData.wf_token = this.options.token;
2745
2746    $.getJSON(url, urlData, function (data) {
2747        if (callback) {
2748            callback(data);
2749        }
2750    });
2751}
2752
2753WFProfileService.prototype.setArgs = function (args) {
2754    var urlData = new Object();
2755
2756    for (var index in args) {
2757        urlData[index] = args[index];
2758    }
2759
2760    return urlData;
2761}
2762function WFLocalProfile(tokenDomain) {    
2763    this.status = { "status_code": 0, "status_message": "Success" };
2764    this.profiles = [];
2765    this.tokenDomain = tokenDomain;
2766
2767    this.getLocalProfile();
2768}
2769
2770WFLocalProfile.prototype.addFavSpot = function (spot) {
2771    // Create a local fav spot id
2772    spot.fav_spot_id = this.profiles[0].favorite_spots.length + 1;
2773
2774    this.profiles[0].favorite_spots.push(spot);
2775    this.saveLocalProfile();
2776
2777    return spot.fav_spot_id;
2778}
2779
2780WFLocalProfile.prototype.removeFavSpot = function (spot) {
2781    var profile = this.profiles[0];
2782    var retVal = false;
2783
2784    if (spot.spot_id != null) {
2785        for (var x = 0; x < profile.favorite_spots.length; x++) {
2786            var item = profile.favorite_spots[x];
2787
2788            if (spot.spot_id == item.spot_id) {
2789                profile.favorite_spots.splice(x, 1);
2790                retVal = true;
2791            }
2792        }
2793    } else {
2794        // Arb spot
2795        for (var x = 0; x < profile.favorite_spots.length; x++) {
2796            var item = profile.favorite_spots[x];
2797
2798            if (spot.lat == item.lat && spot.lon == item.lon) {
2799                profile.favorite_spots.splice(x, 1);
2800                retVal = true;
2801            }
2802        }
2803    }
2804    this.saveLocalProfile();
2805
2806    return retVal;
2807}
2808
2809WFLocalProfile.prototype.getSpotById = function (spotId) {
2810    var spot = null;
2811
2812    for (var x = 0; x < this.profiles[0].favorite_spots.length; x++) {
2813        if (this.profiles[0].favorite_spots[x].spot_id == spotId) {
2814            spot = this.profiles[0].favorite_spots[x];
2815            break;
2816        }
2817    }
2818
2819    return spot;
2820}
2821
2822WFLocalProfile.prototype.getSpotByFavId = function (favId) {
2823    var spot = null;
2824    
2825    for (var x = 0; x < this.profiles[0].favorite_spots.length; x++) {
2826        if (this.profiles[0].favorite_spots[x].fav_spot_id == favId) {
2827            spot = this.profiles[0].favorite_spots[x];
2828            break;
2829        }
2830    }
2831
2832    return spot;
2833}
2834
2835WFLocalProfile.prototype.getSpotByLatLon = function (lat, lon) {
2836    var spot = null;
2837    
2838    for (var x = 0; x < this.profiles[0].favorite_spots.length; x++) {
2839        if (this.profiles[0].favorite_spots[x].lat == lat && this.profiles[0].favorite_spots[x].lon == lon) {
2840            spot = this.profiles[0].favorite_spots[x];
2841            break;
2842        }
2843    }
2844
2845    return spot;
2846}
2847
2848
2849WFLocalProfile.prototype.getContainer = function (ctnId) {
2850    var index = this.getContainerIndex(ctnId);
2851    var ctn = null;
2852
2853    if (index > -1) {
2854        ctn = this.profiles[0].containers[index];
2855    }
2856
2857    return ctn;
2858}
2859
2860WFLocalProfile.prototype.addContainer = function (ctn) {
2861    this.profiles[0].containers.push(ctn);
2862    this.saveLocalProfile();
2863}
2864
2865WFLocalProfile.prototype.removeContainer = function (ctnId) {
2866    var index = this.getContainerIndex(ctnId);
2867    var retVal = false;
2868
2869    if (index > -1) {
2870        this.profiles[0].containers.splice(index, 1);
2871        this.saveLocalProfile();
2872    }
2873
2874    return retVal;
2875}
2876
2877WFLocalProfile.prototype.updateContainer = function (newCtn, ctnId) {
2878    var index = this.getContainerIndex(ctnId);
2879    var retVal = false;
2880
2881    if (index > -1) {
2882        this.profiles[0].containers[index] = newCtn;
2883        this.saveLocalProfile();
2884        retVal = true
2885    }
2886
2887    return retVal;
2888}
2889
2890WFLocalProfile.prototype.profileId = function () {
2891    return this.profiles[0].profile_id;
2892}
2893
2894WFLocalProfile.prototype.profileName = function () {
2895    return this.profiles[0].name;
2896}
2897
2898WFLocalProfile.prototype.getLocalProfile = function () {
2899
2900    if ($.cookie("Profile") != null) {        
2901        this.profiles = JSON.parse(unescape($.cookie("Profile")));
2902    } else {
2903        // Create a local profile        
2904        this.createLocalProfile();
2905    }
2906}
2907
2908WFLocalProfile.prototype.createLocalProfile = function () {
2909    this.profiles.push(new WFDefaultLocalProfile());
2910
2911    // Create the favorite spot list container    
2912    this.profiles[0].containers.push(new WFFavoriteContainer(1, "Favorite Spots", "Favorite Spots", null, ""));
2913
2914
2915    this.saveLocalProfile();
2916}
2917
2918WFLocalProfile.prototype.saveLocalProfile = function () {
2919    $.cookie('Profile', JSON.stringify(this.profiles), { expires: 9999, path: "/", domain: this.tokenDomain });
2920
2921    if (this.isEmpty()) {
2922        $("body").trigger("local_profile_empty");
2923    }
2924}
2925
2926WFLocalProfile.prototype.newContainerId = function () {
2927    return this.profiles[0].containers.length + 1;
2928}
2929
2930WFLocalProfile.prototype.getContainerIndex = function (ctnId) {
2931    var index = -1;
2932
2933    for (var x = 0; x < this.profiles[0].containers.length; x++) {
2934        if (this.profiles[0].containers[x].container_id == ctnId) {
2935            index = x;
2936            break;
2937        }
2938    }
2939
2940    return index;
2941}
2942
2943WFLocalProfile.prototype.isEmpty = function () {
2944    var retval = false;    
2945
2946    if (this.profiles[0].containers.length == 1 && this.profiles[0].favorite_spots.length == 0) {
2947        retval = true;
2948    }
2949
2950    return retval;
2951}
2952
2953/******************************************************************************
2954**  WFDefaultLocalProfile                                                    **
2955******************************************************************************/
2956function WFDefaultLocalProfile() {
2957    this.name = "Favorites";
2958    this.profile_id = 0;
2959    this.my_profile = "true";
2960    this.created_by = "local";
2961    this.favorite_spots = [];
2962    this.containers = [];
2963}
2964
2965/******************************************************************************
2966**  WFFavoriteSpot                                                           **
2967******************************************************************************/
2968function WFFavoriteSpot(typeId, favSpotId, spotId, lat, lon) {
2969    this.type_id = typeId;
2970    this.fav_spot_id = favSpotId;
2971    this.spot_id = spotId;
2972    this.lat = lat;
2973    this.lon = lon;
2974}
2975
2976/******************************************************************************
2977**  WFFavoriteContainer                                                      **
2978******************************************************************************/
2979function WFFavoriteContainer(containerId, containerTypeId, containerName, flowviewOptions, options) {
2980    this.container_id = containerId;
2981    this.container_type_id = containerTypeId;
2982    this.container_name = containerName;
2983    this.flowview_options = flowviewOptions;
2984    this.options = options;
2985}
2986function WFProducts() {}
2987
2988// changes from api
2989// 1. made feature.exceptions and array
2990// 2. added guest member_levels to features
2991// 3. added guest member_level_kit
2992
2993//////////////////////////////////////////////////////////////////////
2994//  Public Interface
2995/////////////////////////////////////////////////////////////////////
2996WFProducts.getFeatureTitleById = function (featureId) {
2997    for (i = 0; i < WFProducts.products.features.length; i++) {
2998        var feature = WFProducts.products.features[i];
2999        if (feature.feature_id == featureId) {
3000            return feature.title;
3001        }
3002    }
3003}
3004
3005WFProducts.getFeatureMembershipStringById = function (featureId) {
3006    for (i = 0; i < WFProducts.products.features.length; i++) {
3007        var feature = WFProducts.products.features[i];
3008        if (feature.feature_id == featureId) {
3009            return feature.memberships_str;
3010        }
3011    }
3012}
3013
3014WFProducts.getMembershipLevelNameById = function (memberLevel) {
3015    for (i = 0; i < WFProducts.products.member_level_kits.length; i++) {
3016        var memberLevelKit = WFProducts.products.member_level_kits[i];
3017        if (memberLevelKit.member_level == memberLevel) {
3018            return memberLevelKit.name;
3019        }
3020    }
3021    return "Guest";
3022}
3023
3024WFProducts.getAnalyticsKeyById = function (featureId) {
3025    var analyticsKey = null;
3026
3027    for (i = 0; i < WFProducts.products.features.length; i++) {
3028        var feature = WFProducts.products.features[i];
3029        if (feature.feature_id == featureId) {
3030            analyticsKey = feature.analytics_key;
3031        }
3032    }
3033
3034    return analyticsKey;
3035}
3036
3037
3038/*
3039    doesUserHaveAccessToFeature
3040
3041        featureId                   int     Required
3042        memberLevelId               int     Required
3043        exceptionFailMemberIdsStr   str     Optional    "2,49"
3044
3045        returns true if a featureId is available for the given memberLevel
3046
3047        if exceptionFailMemberIdsStr is given it will also check the feature exceptions
3048        if an exception is found the matches the exceptionFailMemberLevelIds and memberLevel false is returned
3049*/
3050WFProducts.doesUserHaveAccessToFeature = function (featureId, memberLevel, exceptionFailMemberLevelIdsStr) {
3051    for (i = 0; i < WFProducts.products.features.length; i++) {
3052        var feature = WFProducts.products.features[i];
3053        if (feature.feature_id == featureId) {
3054            for (j = 0; j < feature.member_levels.length; j++) {
3055                if (feature.member_levels[j] == memberLevel) {
3056                    // match found - the feature contains the user's memberLevel
3057
3058                    // now look to see if there is an exception for a memberLevel in exceptionFailMemberIdsStr
3059                    if (typeof exceptionFailMemberLevelIdsStr != "undefined" && exceptionFailMemberLevelIdsStr.length > 0) {
3060                        // there is a list of memberIds to check for exceptions
3061                        if (typeof feature.exceptions != "undefined") {
3062                            // the feature does have exceptions 
3063                            // return false if we find a match
3064                            var exceptionMemberLevelIds = exceptionFailMemberLevelIdsStr.split(",");
3065                            for (k = 0; k < exceptionMemberLevelIds.length; k++) {
3066                                exceptionMemberLevelId = exceptionMemberLevelIds[k];
3067                                for (l = 0; l < feature.exceptions.length; l++) {
3068                                    var exception = feature.exceptions[l];
3069                                    if (exception.member_level == exceptionMemberLevelId && exceptionMemberLevelId == memberLevel) {
3070                                        // the feature does have an exception for a failMemberLevelId and user's memberLevel
3071                                        return false;
3072                                    }
3073                                }
3074                            }
3075                        }
3076                    }
3077
3078                    // no exceptions found - return true
3079                    return true;
3080                }
3081            }
3082        }
3083    }
3084    return false;
3085}
3086
3087WFProducts.getExcludedMapOverlays = function (memberLevel) {
3088    var excludedOverlays = [];
3089
3090    for (var x = 0; x < WFProducts.MapOverlays.length; x++) {
3091        if (!WFProducts.doesUserHaveAccessToFeature(WFProducts.MapOverlays[x].featureId, memberLevel)) {
3092            excludedOverlays.push(WFProducts.MapOverlays[x].overlayName);
3093        }
3094    }
3095        
3096    return excludedOverlays;
3097}
3098
3099WFProducts.getExcludedMarkerData = function (memberLevel) {
3100    var excludedMarkerData = [];
3101
3102    if (!WFProducts.doesUserHaveAccessToFeature(7, memberLevel)) {
3103        excludedMarkerData.push(100);
3104        excludedMarkerData.push(101);
3105    }
3106
3107    return excludedMarkerData;
3108}
3109
3110WFProducts.test = function () {
3111    for (i = 0; i < WFProducts.products.features.length; i++) {
3112        var feature = WFProducts.products.features[i];
3113        console.debug(i + " - " + feature.title);
3114    }
3115}
3116
3117WFProducts.MapOverlays = [{ featureId: 6, overlayName: "nautical" }, { featureId: 18, overlayName: "sst" }, { featureId: 25, overlayName: "currents" }, {featureId: 7, overlayName: "nowcast"}];
3118WFProducts.ProfileFeature = 3;
3119WFProducts.AlertFeature = 4;
3120WFProducts.NauticalChartFeature = 6;
3121WFProducts.NoAdsFeature = 16;
3122
3123/* View Descriptions returned from api */
3124WFProducts.regionalProducts = [
3125    {
3126        "analytics_key": "Wind_Observations",
3127        "regional_product_id": 1,
3128        "title": "Wind Observations"
3129    },
3130    {
3131        "analytics_key": "Wind_Forecast",
3132        "regional_product_id": 2,
3133        "title": "Wind Forecast"
3134    },
3135    {
3136        "analytics_key": "Wind_Nowcast",
3137        "regional_product_id": 3,
3138        "title": "Wind Nowcast"
3139    },
3140    {
3141        "analytics_key": "General_Weather",
3142        "regional_product_id": 4,
3143        "title": "General Weather"
3144    },
3145    {
3146        "analytics_key": "Radar_Precipitation",
3147        "regional_product_id": 5,
3148        "title": "Radar / Precipitation"
3149    },
3150    {
3151        "analytics_key": "Satellite_Clouds",
3152        "regional_product_id": 6,
3153        "title": "Satellite / Clouds"
3154    },
3155    {
3156        "analytics_key": "Sea_Surface_Temperatures",
3157        "regional_product_id": 7,
3158        "title": "Sea Surface Temperatures"
3159    },
3160    {
3161        "analytics_key": "Radar_Wind",
3162        "regional_product_id": 8,
3163        "title": "Radar + Wind"
3164    },
3165    {
3166        "analytics_key": "Satellite_Wind",
3167        "regional_product_id": 9,
3168        "title": "Satellite + Wind"
3169    },
3170    {
3171        "analytics_key": "NWS_Marine_Forecast_Zones",
3172        "regional_product_id": 10,
3173        "title": "NWS Marine Forecast Zones"
3174    },
3175    {
3176        "analytics_key": "Daily_Briefings",
3177        "regional_product_id": 11,
3178        "title": "Daily Briefings"
3179    },
3180    {
3181        "analytics_key": "OnSite_Reports",
3182        "regional_product_id": 12,
3183        "title": "OnSite Reports"
3184    },
3185    {
3186        "analytics_key": "Sea_Surface_Current",
3187        "regional_product_id": 13,
3188        "title": "Sea Surface Current"
3189    },    
3190    {
3191        "analytics_key": "Flow",
3192        "regional_product_id": 18,
3193        "title": "Flow"
3194    },    
3195    {
3196        "analytics_key": "Wind_Gust",
3197        "regional_product_id": 19,
3198        "title": "Wind + Gust"
3199    },    
3200    {
3201        "analytics_key": "Live_Wind_Flow",
3202        "regional_product_id": 19,
3203        "title": "Live Wind - Flow"
3204    },    
3205    {
3206        "analytics_key": "Radar_Satellite",
3207        "regional_product_id": 23,
3208        "title": "Radar + Satellite"
3209    }
3210];
3211function FVContainerOptions() {
3212    // Default values
3213    this.displayDuration = 15000;       // Container display time    
3214}
3215
3216function FVFavListOptions() {
3217    // Favorite spot options
3218    this.rotationSpeed = 8000;
3219    this.mapBackground = "High Contrast";
3220    this.mapLayers = new Array();
3221    this.showStationGraph = true;
3222    this.lockedStation = "";
3223}
3224var size = window.getComputedStyle(document.body, ':after').getPropertyValue('content');
3225console.log(size)
3226
3227$(document).ready(function () {
3228    loadModalManager();
3229    cleanSavedSettings();    
3230
3231    // Load the profile manager for specific pages
3232    loadProfileManager();
3233    loadProfiles();
3234
3235    showWelcomeMsg();
3236
3237    /*if (!(/(iPad|iPhone|iPod).*OS [6-7].*AppleWebKit.*Mobile.*Safari/.test(navigator.userAgent))) {
3238    $(function () {
3239    $.smartbanner()
3240    });
3241    }*/        
3242});
3243
3244function showWelcomeMsg() {
3245    // Welcome message is shown to users that have been to a legacy website, but have not been to new website.
3246    // CFTOKEN cookie exists: User has been to legacy website
3247    // uid cookie exists: User has been to new website
3248    
3249    if ($.cookie("CFTOKEN") != null && firstTimeUser && siteName.toLowerCase() != "windalert") {
3250        var detailPage = getWelcomeDetailPage();
3251        var tutorialPage = getWelcomeTutorialPage();
3252
3253        var newUser = "<ul class='welcome-msg'>";
3254        newUser += "<li>Welcome to the New " + siteName + "</li>";
3255        newUser += "<li>The same Username and Password will work between Old and New " + siteName + "</li>";
3256        newUser += "<li>Here is information on <a href='" + detailPage + "' target='_blank'>what's new</a>.</li>";
3257        if (tutorialPage != "") {
3258            newUser += "<li>For help with the transition, <a href='" + tutorialPage + "'>please read this short tutorial</a>.</li>";
3259        }
3260        newUser += "<li>We hope you enjoy all the new features!</li>";
3261        newUser += "</ul>";
3262        
3263        // "Welcome to the New " + siteName
3264        wfModalManager.displayAlert("<div class='logo " + siteName.toLowerCase() + "'></div>", newUser);
3265    }
3266}
3267
3268function getWelcomeDetailPage() {
3269    switch (siteName.toLowerCase()) {
3270        case "sailflow":
3271            return "http://www.weatherflow.com/consumer-products/sailflow/";
3272            break;
3273        case "fishweather":
3274            return "http://www.weatherflow.com/consumer-products/fishweather/";
3275            break;
3276        default:
3277            return "http://www.weatherflow.com/consumer-products/";
3278            break;
3279    }
3280}
3281
3282function getWelcomeTutorialPage() {
3283    switch (siteName.toLowerCase()) {
3284        case "sailflow":
3285            return "http://www.weatherflow.com/whats-different-on-new-sailflow/";
3286            break;
3287        case "fishweather":
3288            return "http://www.weatherflow.com/whats-different-on-new-fishweather/";
3289            break;
3290        default:
3291            return "";
3292            break;
3293    }
3294}
3295
3296function isMobile() {
3297    var retval = false;
3298
3299    if (size.indexOf("mobile") > -1) {
3300        return true;
3301    }
3302
3303    return retval;
3304}
3305
3306function saveGraphModeSettings(mode) {
3307    mapSettings.gms = mode;
3308    saveMapSettings();
3309    trackGraphModeEvent(mode);
3310
3311    if (mode == "Advanced") {
3312        $("html").addClass("advanced-graph");
3313    } else {
3314        $("html").removeClass("advanced-graph");
3315    }
3316}
3317
3318function delete_cookie(name) {
3319    document.cookie = name + '=; Path=/; Domain=' + tokenDomain + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
3320}
3321
3322function cleanSavedSettings() {
3323    // Remove old Favorites sort order cookie
3324    delete_cookie('FavoritesSortOrder');
3325    delete_cookie('wfEventToken');
3326
3327    // Remove previous map graph mode
3328    if (mapSettings.hasOwnProperty("gm")) {
3329        delete mapSettings.gm;
3330        saveMapSettings();
3331        console.log(mapSettings);
3332    }
3333}
3334MapHash = function (savedMapSettings) {
3335    this.mapSettings = savedMapSettings;
3336    this.hash = this._getHash();
3337    this.params = this._split();    
3338}
3339
3340MapHash.LAT = 0;
3341MapHash.LON = 1;
3342MapHash.ZOOM = 2;
3343MapHash.VIEW = 3;
3344MapHash.SPOT_ID = 4;
3345MapHash.CTN_ID = 5;
3346MapHash.SEARCH = 0;
3347MapHash.VIEW_THRESHOLD = 1000;
3348
3349MapHash.LOCATION = {};
3350MapHash.LOCATION.TYPE = {};
3351MapHash.LOCATION.TYPE.HASH = "hash";
3352MapHash.LOCATION.TYPE.SPOT = "spot"
3353MapHash.LOCATION.TYPE.PREVIOUS = "previous";
3354MapHash.LOCATION.TYPE.API = "api";
3355MapHash.LOCATION.TYPE.DEFAULT = "default";
3356
3357MapHash.SPOT = {};
3358MapHash.SPOT.TYPE = {};
3359MapHash.SPOT.TYPE.SPOT = "spot";
3360MapHash.SPOT.TYPE.ONSITE_REPORT = "onsite";
3361MapHash.SPOT.TYPE.LATLNG = "latlng";
3362
3363
3364MapHash.prototype._getHash = function () {
3365    var hash = null;
3366    var urlParts = location.href.split("#");
3367
3368    if (urlParts.length > 1) {
3369        hash = urlParts[1].replace("#", "");
3370    }
3371        
3372    return hash;
3373}
3374
3375MapHash.prototype._split = function () {
3376    var params = [];
3377
3378    if (this.hash != null) {
3379        params = this.hash.split(",");
3380    }
3381    
3382    return params;
3383}
3384
3385MapHash.prototype.coordinates = function (callback) {
3386    if (typeof callback === "function") {
3387        var previousLat = this._previousLat();
3388        var previousLon = this._previousLon();
3389        var lastLocation = getLastLocation();
3390        var coords = {
3391            lat: null,
3392            lon: null,
3393            type: null,
3394            zoom: null
3395        };
3396
3397        if (this.params.length > MapHash.ZOOM && this.params[MapHash.LAT] != "" && this.params[MapHash.LON] != "" && this.params[MapHash.ZOOM] != "") {
3398            coords.lat = this.params[MapHash.LAT];
3399            coords.lon = this.params[MapHash.LON];
3400            coords.zoom = this.params[MapHash.ZOOM];
3401            coords.type = MapHash.LOCATION.TYPE.HASH;
3402
3403            callback(coords);
3404        } else if (this.params.length > MapHash.SPOT_ID && this.params[MapHash.LAT] == "" && this.params[MapHash.LON] == "" && this.params[MapHash.SPOT_ID] != "") {
3405            var self = this;
3406            var spotType = this.spotIdType();
3407            var spotId = this.params[MapHash.SPOT_ID].replace(/!/g, "");
3408
3409            // Set the zoom level and type
3410            coords.zoom = this.params[MapHash.ZOOM] == "" ? 9 : this.params[MapHash.ZOOM];
3411            coords.type = MapHash.LOCATION.TYPE.SPOT;
3412
3413            switch (spotType) {
3414                case MapHash.SPOT.TYPE.SPOT:
3415                    var spotService = new jweather.Data.SpotService({ include_obs: false, spot_types: "1,100" });
3416
3417                    spotService.getSpotDetailSetById(spotId, function (data) {
3418                        if (data.status.status_code == 0 && data.spot_count >= 1) {
3419                            var spot = data.spots[0];
3420
3421                            coords.lat = spot.lat;
3422                            coords.lon = spot.lon;
3423
3424                            callback(coords);
3425                        }
3426                    });
3427
3428                    break;
3429                case MapHash.SPOT.TYPE.ONSITE_REPORT:
3430                    var onsiteReportId = spotId.replace(/o-/g, "")
3431                    var onsiteReportService = new jweather.Data.OnsiteReportService();
3432
3433                    onsiteReportService.getOnsiteReportById(onsiteReportId, function (data) {
3434                        if (data.status.status_code == 0 && data.count >= 1) {
3435                            var report = data.onsite_reports[0];
3436
3437                            coords.lat = report.lat;
3438                            coords.lon = report.lon;
3439
3440                            callback(coords);
3441                        }
3442                    });
3443
3444                    break;
3445                case MapHash.SPOT.TYPE.LATLNG:
3446                    var parts = spotId.split(":");
3447
3448                    if (parts.length >= 2) {
3449                        coords.lat = parts[0];
3450                        coords.lon = parts[1];
3451
3452                        callback(coords);
3453                    }
3454
3455                    break;
3456            }
3457        } else if (lastLocation != null && lastLocation.lat != undefined && lastLocation.lat != null && lastLocation.lon != undefined && lastLocation.lon != null) {
3458            coords.lat = lastLocation.lat;
3459            coords.lon = lastLocation.lon;
3460            coords.zoom = lastLocation.zoom != null ? lastLocation.zoom : 9;
3461
3462            callback(coords);
3463        } else if (!this.isSearch() && previousLat != null && previousLon != null) {
3464            coords.lat = previousLat;
3465            coords.lon = previousLon;
3466            coords.zoom = previousMapZoom;
3467            coords.type = MapHash.LOCATION.TYPE.PREVIOUS;
3468
3469            saveLastMapLocation(previousMapCenter, previousMapZoom);
3470
3471            callback(coords);
3472        } else if (!this.isSearch() && userLati != "" && userLong != "") {
3473            coords.lat = userLati;
3474            coords.lon = userLong;
3475            coords.zoom = 9;
3476            coords.type = MapHash.LOCATION.TYPE.API;
3477
3478            callback(coords);
3479        } else if (!this.isSearch()) {
3480            coords.lat = 0;
3481            coords.lon = 0;
3482            coords.zoom = 2;
3483            coords.type = MapHash.LOCATION.TYPE.DEFAULT;
3484
3485            callback(coords);
3486        }
3487    }
3488}
3489
3490MapHash.prototype.viewId = function () {
3491    var viewId = null;
3492
3493    if (this.params.length > MapHash.VIEW) {
3494        viewId = this.params[MapHash.VIEW];
3495
3496        if (viewId != "") {
3497            var localId = this.localCustomViewId(viewId);
3498
3499            if (localId != null) {
3500                viewId = localId
3501            }
3502        } else {
3503            // Set the default view based on the spot type
3504            var spotType = this.spotIdType();
3505
3506            switch (spotType) {
3507                case MapHash.SPOT.TYPE.SPOT = "spot":
3508                    viewId = "1";
3509                    break;
3510                case MapHash.SPOT.TYPE.ONSITE_REPORT = "onsite":
3511                    viewId = "12";
3512                    break;
3513                case MapHash.SPOT.TYPE.LATLNG:
3514                    viewId = "3";
3515                    break;
3516            }
3517        }
3518    } else {
3519        if (this.mapSettings.hasOwnProperty("v")) {
3520            viewId = this.mapSettings.v;
3521        } else {
3522            // default view
3523            viewId = "1";
3524        }
3525    }
3526
3527    return viewId;
3528}
3529
3530MapHash.prototype.spotId = function () {
3531    var spotId = null;
3532
3533    if (this.params.length > MapHash.SPOT_ID && this.params[MapHash.SPOT_ID].indexOf(":") == -1) {        
3534        spotId = this.params[MapHash.SPOT_ID].replace(/!/g, "");
3535    }
3536
3537    return spotId;
3538}
3539
3540MapHash.prototype.spotIdType = function () {
3541    var spotType = null;
3542
3543    if (this.params.length > MapHash.SPOT_ID && this.params[MapHash.SPOT_ID] != "") {
3544        if (this.params[MapHash.SPOT_ID].indexOf("o-") > -1) {
3545            spotType = MapHash.SPOT.TYPE.ONSITE_REPORT;
3546        } else if (this.params[MapHash.SPOT_ID].indexOf(":") > -1) {
3547            spotType = MapHash.SPOT.TYPE.LATLNG;
3548        } else {
3549            spotType = MapHash.SPOT.TYPE.SPOT;
3550        }
3551    }
3552
3553    return spotType;
3554}
3555
3556MapHash.prototype.hasInfoWindow = function () {
3557    var hasInfoWindow = false;
3558
3559    if (this.params.length > MapHash.SPOT_ID) {
3560        if (this.params[MapHash.SPOT_ID].indexOf("!") > -1) {
3561            hasInfoWindow = true;
3562        }        
3563    }
3564
3565    return hasInfoWindow;
3566}
3567
3568MapHash.prototype.spotLat = function () {
3569    var spotLat = null;
3570
3571    if (this.params.length > MapHash.SPOT_ID) {
3572        if (this.params[MapHash.SPOT_ID].indexOf(":") > -1) {
3573            var latLng = this.params[MapHash.SPOT_ID].split(":");
3574            spotLat = latLng[0].replace(/!/g, "");
3575        }
3576    }
3577
3578    return spotLat;
3579}
3580
3581MapHash.prototype.spotLon = function () {
3582    var spotLon = null;
3583
3584    if (this.params.length > MapHash.SPOT_ID) {
3585        if (this.params[MapHash.SPOT_ID].indexOf(":") > -1) {
3586            var latLng = this.params[MapHash.SPOT_ID].split(":");
3587            spotLon = latLng[1].replace(/!/g, "");
3588        }
3589    }
3590
3591    return spotLon;
3592}
3593
3594MapHash.prototype.ctnId = function () {
3595    var ctnId = null;
3596
3597    if (this.params.length > MapHash.CTN_ID) {
3598        if (!isNaN(this.params[MapHash.CTN_ID])) {
3599            ctnId = parseInt(this.params[MapHash.CTN_ID]);
3600        }
3601    }
3602
3603    return ctnId;
3604}
3605
3606MapHash.prototype.view = function (callback) {
3607    var id = this.viewId();
3608    var view = null;
3609
3610    if (id != null) {        
3611        // Lookup the view
3612        var ps = new jweather.Data.ProfileService({});
3613
3614        ps.getContainer(id, function (data) {            
3615            if (data.status.status_code == 0 && data.hasOwnProperty("options")) {
3616                view = jweather.Data.ProfileService.containerOptionsObject(data.options);
3617
3618                view.name = data.is_mine ? data.name : "Unsaved Map";
3619                view.description = data.is_mine ? data.name : "A map that was shared with you.";
3620            }
3621
3622            if (typeof callback === "function") {
3623                callback(view, data);
3624            }
3625        });
3626    }
3627
3628    return view;
3629}
3630
3631MapHash.prototype.searchString = function () {
3632    var searchString = null;
3633
3634    if (this.params.length > MapHash.SEARCH && this.params[MapHash.SEARCH] != "") {
3635        // Explicit search term in the hash
3636        searchString = decodeURIComponent(this.params[MapHash.SEARCH]);
3637    } else {
3638        // No search term in the hash (empty hash, or a view-only hash like
3639        // ",,,1"): fall back to the stored last location's search term.
3640        var lastLocation = getLastLocation();
3641
3642        if (lastLocation != null && lastLocation.searchTerm != undefined && lastLocation.searchTerm != null) {
3643            searchString = decodeURIComponent(lastLocation.searchTerm);
3644        }
3645    }
3646
3647    return searchString;
3648}
3649
3650MapHash.prototype.isSearch = function () {
3651    // A hash that specifies an explicit location (lat/lon/zoom or a spot id)
3652    // centers the map directly and is never a stored-location search.
3653    if (this._hashHasLocation()) {
3654        return false;
3655    }
3656
3657    var lastLocation = getLastLocation();
3658
3659    if (this.params.length == 1) {
3660        // A single hash param is an explicit search term. If we already have
3661        // coordinates stored for this exact term (e.g. saved by the wind list
3662        // before linking to the map), treat it as a restorable location instead
3663        // of a fresh search so coordinates() reuses those coords and
3664        // buildMapOptions doesn't clobber them via saveLastSearchLocation().
3665        var searchTerm = decodeURIComponent(this.params[MapHash.SEARCH]);
3666        var hasCoordsForTerm = lastLocation != null
3667            && lastLocation.searchTerm != undefined && lastLocation.searchTerm != null
3668            && decodeURIComponent(lastLocation.searchTerm) == searchTerm
3669            && lastLocation.lat != undefined && lastLocation.lat != null
3670            && lastLocation.lon != undefined && lastLocation.lon != null;
3671
3672        return !hasCoordsForTerm;
3673    }
3674
3675    // The hash carries no location (empty, or a view-only hash like ",,,1").
3676    // Defer to the stored last location: it is a search when that location is a
3677    // search term we still need to geocode (no coordinates saved for it). This
3678    // keeps a pending named search (e.g. from the wind list) from falling back
3679    // to the stale previous-map-center cookie in coordinates().
3680    if (lastLocation != null && lastLocation.type != undefined && lastLocation.type == "search"
3681        && lastLocation.searchTerm != undefined && lastLocation.searchTerm != null && lastLocation.searchTerm != ""
3682        && (lastLocation.lat == undefined || lastLocation.lat == null || lastLocation.lon == undefined || lastLocation.lon == null)) {
3683        return true;
3684    }
3685
3686    return false;
3687}
3688
3689MapHash.prototype._hashHasLocation = function () {
3690    // Explicit lat/lon/zoom in the hash
3691    if (this.params.length > MapHash.ZOOM
3692        && this.params[MapHash.LAT] != "" && this.params[MapHash.LON] != "" && this.params[MapHash.ZOOM] != "") {
3693        return true;
3694    }
3695
3696    // A spot id (spot / onsite report / lat:lng) in the hash
3697    if (this.params.length > MapHash.SPOT_ID && this.params[MapHash.SPOT_ID] != "") {
3698        return true;
3699    }
3700
3701    return false;
3702}
3703
3704MapHash.prototype.localCustomViewId = function (urlViewId) {    
3705    // Check to see if the view matches our local obs or fx settings
3706    var viewId = null;
3707    
3708    if (this.mapSettings.hasOwnProperty("cv")) {
3709        if (this.mapSettings.cv.hasOwnProperty("obs")) {
3710            if (this.mapSettings.cv.obs.hasOwnProperty("ctnId") && this.mapSettings.cv.obs.ctnId == urlViewId) {                
3711                viewId = "obs";
3712            }
3713        }
3714    }
3715
3716    if (this.mapSettings.hasOwnProperty("cv")) {
3717        if (this.mapSettings.cv.hasOwnProperty("fx")) {
3718            if (this.mapSettings.cv.fx.hasOwnProperty("ctnId") && this.mapSettings.cv.fx.ctnId == urlViewId) {
3719                viewId = "fx";
3720            }
3721        }
3722    }
3723    
3724    return viewId;
3725}
3726
3727MapHash.prototype.set = function (center, zoom, bounds, viewId, view, spot) {
3728    var hashParts = [];
3729    var spotInfo = "";
3730    var hashId = "";
3731    
3732    // Center
3733    hashParts.push(center);
3734
3735    // Zoom
3736    hashParts.push(zoom);
3737
3738    // View Id
3739    hashParts.push(this.buildViewId(viewId, view));
3740
3741    if (this.spotInfoSet(spot)) {
3742        // Spot Id - Optional
3743        hashParts.push(this.buildSpotId(spot));
3744
3745        // Ctn Id - Optional
3746        var ctnId = this.buildCtnId(spot)
3747
3748        if (ctnId != null) {
3749            hashParts.push(ctnId);
3750        }
3751    }
3752
3753    window.location.hash = hashParts.join(",");
3754}
3755
3756MapHash.prototype.buildViewId = function (viewId, view) {
3757    var hashId = viewId + "";
3758
3759    // Cleanup the view id
3760    hashId = hashId.replace("c-", "");
3761
3762    if (hashId == "obs" || hashId == "fx" || hashId == "unsaved" && view.hasOwnProperty("ctnId")) {
3763        hashId = view.ctnId;
3764    }
3765
3766    return hashId;
3767}
3768
3769MapHash.prototype.buildSpotId = function (spot) {
3770    var spotId = null;
3771
3772    if (this.spotInfoSet(spot)) {
3773        if (spot.id != null) {
3774            spotId = spot.id
3775
3776            if (spot.markerType == "OnsiteReportMarker") {
3777                spotId = "o-" + spotId;
3778            }
3779        } else {
3780            spot.lat = !isNaN(spot.lat) ? spot.lat.toFixed(4) : spot.lat;
3781            spot.lon = !isNaN(spot.lon) ? spot.lon.toFixed(4) : spot.lon;            
3782
3783            spotId = spot.lat + ":" + spot.lon;
3784        }
3785    }
3786
3787    if (spot.infoWindow) {
3788        spotId = "!" + spotId;
3789    }
3790
3791    return spotId;
3792}
3793
3794MapHash.prototype.buildCtnId = function (spot) {
3795    var ctnId = null;
3796
3797    if (this.spotInfoSet(spot) && spot.hasOwnProperty("tab") && spot.tab != null) {
3798        ctnId = spot.tab;
3799    }
3800
3801    return ctnId;
3802}
3803
3804MapHash.prototype.spotInfoSet = function (spot) {
3805    var retval = false;
3806
3807    if (!spot) return retval;
3808    
3809    if (spot.hasOwnProperty("id") && spot.id != null) {
3810        retval = true;
3811    } else if (spot.hasOwnProperty("lat") && spot.hasOwnProperty("lon") && spot.lat != null && spot.lon != null) {
3812        retval = true;
3813    }    
3814    
3815    return retval;
3816}
3817
3818MapHash.prototype._previousLat = function () {
3819    var previousLat = null;
3820
3821    if (previousMapCenter != "" && previousMapCenter.indexOf(",") > -1) {
3822        previousLat = previousMapCenter.split(",")[0];
3823    }
3824
3825    return previousLat;
3826}
3827
3828MapHash.prototype._previousLon = function () {
3829    var previousLon = null;
3830
3831    if (previousMapCenter != "" && previousMapCenter.indexOf(",") > -1) {
3832        previousLon = previousMapCenter.split(",")[1];
3833    }
3834
3835    return previousLon;
3836}
3837
3838MapHash.prototype.lat = function () {
3839    var lat = null;
3840
3841    if (this.params.length > MapHash.LAT) {
3842        lat = this.params[MapHash.LAT];
3843    }
3844
3845    return lat;
3846}
3847
3848MapHash.prototype.lon = function () {
3849    var lon = null;
3850
3851    if (this.params.length > MapHash.LON) {
3852        lon = this.params[MapHash.LON];
3853    }
3854
3855    return lon;
3856}
3857
3858MapHash.prototype.zoom = function () {
3859    var zoom = null;
3860
3861    if (this.params.length > MapHash.ZOOM) {
3862        zoom = this.params[MapHash.ZOOM];
3863    }
3864
3865    return zoom;
3866}
3867
3868MapHash.prototype.urlViewId = function () {
3869    var viewId = null;
3870
3871    if (this.params.length > MapHash.VIEW) {
3872        viewId = this.params[MapHash.VIEW];
3873    }
3874
3875    return viewId;
3876}
3877ContainerInfo = function (containerName) {
3878    this.containerName = containerName;
3879    this.container = null;
3880    this.info = {
3881        "bulletins": {
3882            title: "NWS Forecast",
3883            width: "600"
3884        },
3885        "current-conditions": {
3886            title: "Current Conditions",
3887            width: "742"
3888        },
3889        "data-archives": {
3890            title: "Data Archives",
3891            width: "600"
3892        },
3893        "daily-briefing": {
3894            title: "Professional Meteorologist Forecast",
3895            width: "600"
3896        },
3897        "forecast-table": {
3898            title: "Forecast Table",
3899            width: "600"
3900        },
3901        "nearby-tides": {
3902            title: "Nearby Tides &amp; Currents",
3903            width: "600"
3904        },
3905        "nowcast": {
3906            title: "Nowcast",
3907            width: "600"
3908        },
3909        "onsite-reports": {
3910            title: "Onsite Reports",
3911            width: "600"
3912        },
3913        "station-info": {
3914            title: "Station Info",
3915            width: "600"
3916        },
3917        "wind-stats": {
3918            title: "Wind Statistics",
3919            width: "600"
3920        }
3921    };
3922
3923    this._init();
3924};
3925
3926ContainerInfo.prototype.width = function () {
3927    var width = "600";
3928
3929    if (this.container != null) {
3930        width = this.container.width;
3931    }
3932
3933    return width;
3934}
3935
3936ContainerInfo.prototype.infoText = function () {
3937    var str = "";
3938
3939    switch (this.containerName) {
3940        case "bulletins":
3941            str = this._bulletinInfo();
3942            break;
3943        case "current-conditions":
3944            str = this._currentConditionsInfo();
3945            break;
3946        case "data-archives":
3947            str = this._dataArchivesInfo();
3948            break;
3949        case "forecast-table":
3950            str = this._forecastTableInfo();
3951            break;
3952        case "nearby-tides":
3953            str = this._nearbyTidesInfo();
3954            break;
3955        case "nowcast":
3956            str = this._nowcastInfo();
3957            break;
3958        case "onsite-reports":
3959            str = this._onsiteReportsInfo();
3960            break;
3961        case "daily-briefing":
3962            str = this._operationalForecastInfo();
3963            break;
3964        case "station-info":
3965            str = this._stationInfo();
3966            break;
3967        case "wind-stats":
3968            str = this._windStatsInfo();
3969            break;
3970    }
3971
3972    return str;
3973}
3974
3975ContainerInfo.prototype.title = function () {
3976    var title = "";
3977
3978    if (this.container != null) {
3979        title = this.container.title;
3980    }
3981
3982    return title;
3983}
3984
3985ContainerInfo.prototype._init = function () {
3986    if (this.info.hasOwnProperty(this.containerName)) {
3987        this.container = this.info[this.containerName];
3988    }
3989}
3990
3991ContainerInfo.prototype._dataArchivesInfo = function () {
3992    var str = "<p>Our historical wind archives include a wealth of wind graphs &amp; data, going back as far as 30 years in the case of some popular stations. Click the arrows to go backward or forward in time. You can also use the calendar button to select a specific date, or click View Month to see an entire month\'s worth of archived wind graphs all at once.</p>";
3993    str += "<p>Raw, numerical data:<br/>If you\'re looking for raw numerical data, <a href='https://help.tempest.earth/hc/en-us/requests/new' target='_blank'>click here</a> to inquire about our data download packages.</p>";
3994    str += "<p><img alt=\"Yesterday\'s Wind\" src=\"https://cdn-consumer.weatherflow.com/images/info/yesterdays_wind_info.jpg?v20171115\" width='572px'></p>";
3995
3996    return str;
3997}
3998
3999ContainerInfo.prototype._currentConditionsInfo = function () {
4000    var mode = mapSettings.hasOwnProperty("gms") ? mapSettings.gms : "Basic";
4001    var images = {
4002        "Advanced": {
4003            src: "/App_Themes/jWeather/images/current_conditions_info.png",
4004            width: "718px",
4005            height: "444px"
4006        },
4007        "Basic": {
4008            src: "/App_Themes/jWeather/images/current_conditions_info_basic.png",
4009            width: "718px",
4010            height: "424px"
4011        }
4012    };
4013    var img = images.hasOwnProperty(mode) ? images[mode] : null;
4014    
4015    if (img != null) {
4016        var str = "<p>
4016<img src='" + img.src + "' height='" + img.height + "' width='" + img.width + "'></p>";
4017    }
4018
4019    return str;
4020}
4021
4022ContainerInfo.prototype._nowcastInfo = function () {
4023    return "<p>There is no weather station here. Nowcast data at this spot is based on our Nowcast model, which is based on a formula that incorporates the best available observations and forecast data for this location. Nowcast is experimental.</p>";
4024}
4025
4026ContainerInfo.prototype._nearbyTidesInfo = function () {
4027    var str = "<p>This is a list of all weather stations within " + searchRadius + " " + distanceUnits + " of this location. The search radius can be changed in your settings. The graph is for the current day.</p>";
4028    str += "<p>We use Xtides program to generate our tidal graphs. </p>";
4029    str += "<p>A very good summary of how Tides are calculated and a great general FAQ can be found here: </p>";
4030    str += "<p><a class='extra-info-link' href='http://www.flaterco.com/xtide/faq.html#50' target=/'blank'>http://www.flaterco.com/xtide/faq.html#50</a></p>";
4031
4032    return str;
4033}
4034
4035ContainerInfo.prototype._windStatsInfo = function () {
4036    var str = "<p> The left side shows the number of days per month a specific weather station reported average winds greater than 15, 20, and 25 miles per hour. The Right side shows the percentage of time that the weather station reported a specific wind direction for the highlighted month. The data has not been error checked. </p>";
4037    str += "<h3>Options:</h3>";
4038    str += "<ul>";
4039    str += "<li><strong>All Time (default)</strong> - This data is the average over the years available in our database.</li>";
4040    str += "<li><strong>Specific Years</strong> - These are specific statistics for a given year. Hovering your mouse over a month will tell you how many days and how many days that station reported.</li>";
4041    str += "<li><strong>Gust vs. Average</strong> - WeatherFlow stations are positioned in very good spots to capture ambient wind conditions and averages often reflect actual winds experienced on the water. However, we also include stations from many other sources that may not be ideally placed and gusts can often be a better indicator of conditions.</li>";
4042    str += "</ul>";
4043    str += "<p><a class='extra-info-link' href='mailto:[email protected]' target='_blank'>For more detailed premium wind statistics click here</a></p>";
4044
4045    return str;
4046}
4047
4048ContainerInfo.prototype._onsiteReportsInfo = function () {
4049    var str = infoContent = "This list displays the ten closest OnSite Reports within a 72 hour period. OnSite Reports are crowdsourced (user-generated) wind and weather reports, contributed by real people via different applications.";
4050
4051    return str;
4052}
4053
4054ContainerInfo.prototype._operationalForecastInfo = function () {
4055    var str = "<p>Professional Meteorologist Forecasts include a detailed wind forecast, or briefing, by a WeatherFlow meteorologist.  Bullet points detail areas of interest or concerns that the meteorologist has that could impact the winds for the day.  In contrast to the Computer Forecast Table, which was produced solely by a computer, the Pro Forecast forecast table was handcrafted by the meteorologist and reflects his or her expertise and local knowledge. The Pro Forecasts also include an Extended Discussion which provides a forecast for the upcoming conditions for the region over the next three days.</p><p><em>NOTE:</em> Pro Forecasts and Extended Discussions are both seasonal and local.  Hence, they are not available for all regions.</p>";
4056
4057    return str;
4058}
4059
4060ContainerInfo.prototype._bulletinInfo = function () {
4061    var str = "<p>The NWS Forecast includes Marine Forecasts as well as Watches & Warnings from the National Weather Service (NWS).  These are produced by regional and national NWS offices.</p>";
4062
4063    return str;
4064}
4065
4066ContainerInfo.prototype._stationInfo = function () {
4067    var str = "<p>Station information is provided to give additional metadata information, including facts about station ownership and operation.</p>";
4068
4069    return str;
4070}
4071
4072ContainerInfo.prototype._forecastTableInfo = function () {
4073    var str = "<p>The forecast table shows you many parameters output by the forecast model going out up to 7 days ahead.</p>";
4074    str += "<p>Clicking the star button saves the table that you have selected into your current profile.</p>";
4075    str += "<p>Available Models:   Select what forecast model you want to see.  Available models will vary depending on the location that you have selected.</p>";
4076    str += "<p>For more detailed information see our help topic <a class='extra-info-link' href='https://help.tempest.earth/hc/en-us/articles/207913138#h_01GV695ZJH0G8RNCD27B2HDJJW' target='_blank'>Reading Forecast Tables</a></p>";
4077
4078    return str;
4079}
4080
4081(function ($, undefined) {
4082    var navData = null;
4083
4084    $(document).ready(function () {
4085        if (userLati && userLong) {
4086            setUIGeolocation(userLati, userLong);
4087
4088            $(".geolocation-btn").show();
4089
4090            $(".geolocation-btn").click(function (e) {
4091                e.preventDefault();
4092
4093                var page = "currentPage" in window ? currentPage : "Search";
4094
4095                if (page == "SearchResults") {
4096                    // Don't reload the page, just update the map                    
4097                    if (ui) {
4098                        var center = new google.maps.LatLng(userLati, userLong);
4099
4100                        ui.loadCenterZoom(center, 10);
4101                        refreshDFP(0);
4102                    }
4103                    closeMobileSearchMenu();
4104                } else if (page == "WindList") {
4105                    doWindListSearch(userLati + "," + userLong);
4106                    refreshDFP(0);
4107                    closeMobileSearchMenu();
4108                } else if (page == "SearchResultsPlus") {
4109                    doSearchResultsPlusSearch(userLati + "," + userLong);
4110                    closeMobileSearchMenu();
4111                } else {
4112                    // Only track event if geolocate is used on the homepage
4113                    if (page == "Search") {
4114                        _gaq.push(['_trackEvent', 'Homepage Search', 'Geolocate', (userLati + ',' + userLong)]);
4115                    }
4116
4117                    window.location = "/windlist/" + userLati + "/" + userLong;
4118                }
4119            });
4120        } else {
4121            $(".geolocation-btn").hide();
4122        }
4123
4124        $("#main-menu-btn").click(function () {
4125            $("#main-menu").toggle();
4126            mainMenuClosed();
4127            closeMobileSearchMenu();
4128
4129            return false;
4130        });
4131
4132        $("#main-menu a.upgrade-available").on("click touchend", function () {
4133            var featureId = $(this).attr("data-feature-id");            
4134            var retval = wfModalManager.doesUserHaveAccessToFeature(featureId);
4135            
4136            return retval;
4137        });
4138
4139        $("body").on("click", ".parent-menu", function () {
4140            var menuId = $(this).attr("data-sub-menu");
4141
4142            if (menuId != undefined && menuId != null && menuId != "") {
4143                $(this).toggleClass("sub-menu-show");
4144                $("#" + menuId).toggleClass("sub-menu-show");
4145            }
4146
4147            return false;
4148        });
4149
4150        $("html").click(function () {
4151            $("#main-menu").hide();
4152            $("#mobile-search-menu").hide();
4153            mainMenuClosed();
4154        });
4155
4156        $("#mobile-search-btn").on("click touchend", function () {
4157            $("#mobile-search-menu").toggle();
4158            $("#mobile-search-menu .mobile-searchInput").focus();
4159            $("#main-menu").hide();
4160            mainMenuClosed();
4161
4162            return false;
4163        });
4164
4165        $("#mobile-search-menu").click(function (e) {
4166            // Don't bubble up to html click handler
4167            e.stopPropagation();
4168        });
4169
4170        $("#cancel-mobile-search").on("click touchend", function () {
4171            closeMobileSearchMenu();
4172
4173            return false;
4174        });
4175
4176
4177        $(".mobile-searchInput").keypress(function (e) {
4178            if (e.which == 13) {
4179                searchInputHandler($(".mobile-searchInput").val());
4180                closeMobileSearchMenu();
4181
4182                return false;
4183            }            
4184        });
4185
4186        $("#do-mobile-search").on("click touchend", function () {            
4187            searchInputHandler($(".mobile-searchInput").val());
4188            closeMobileSearchMenu();
4189
4190            return false;
4191        });
4192
4193        loadRegionalNav();
4194    });
4195
4196    function closeMobileSearchMenu() {
4197        $("#mobile-search-menu .mobile-searchInput").val("").blur();
4198        $("#mobile-search-menu").hide();
4199    }
4200
4201    function setUIGeolocation(lat, lon) {
4202        var regions = [
4203            { min_lat: 16.961, min_lon: -162.895, max_lat: 23.917, max_lon: -152.132 },
4204            { min_lat: 9.788, min_lon: -71.525, max_lat: 19.538, max_lon: -58.313 },
4205            { min_lat: 22.743, min_lon: -129.557, max_lat: 51.104, max_lon: -66.125 }
4206        ];
4207
4208        for (var i = 0; i < regions.length; i++) {
4209            var r = regions[i];
4210
4211            if (r.min_lat <= lat && r.min_lon <= lon && r.max_lat >= lat && r.max_lon >= lon) {
4212                return true;
4213            }
4214        }
4215
4216        return false;
4217    }
4218} (jQuery));
4219
4220function mainMenuClosed() {
4221    if (isMobile()) {
4222        if ($("#main-menu").is(":visible")) {
4223            $("html").addClass("modal-open");
4224        } else {
4225            $("html").removeClass("modal-open");
4226        }
4227    }
4228}
4229
4230function lastLocationDefaultObj() {
4231    return {
4232        lat: null,
4233        lon: null,
4234        zoom: null,
4235        searchTerm: null,
4236        type: null
4237    };
4238}
4239
4240function saveLastMapLocation(centerPoint, zoom) {
4241    var coords = centerPoint.split(",");
4242    var loc = lastLocationDefaultObj();
4243
4244    if (coords.length > 1) {
4245        loc.lat = coords[0];
4246        loc.lon = coords[1];
4247    }
4248
4249    loc.searchTerm = centerPoint;
4250    loc.zoom = zoom;
4251    loc.type = "coords";
4252
4253    saveLastLocation(loc);
4254}
4255
4256function saveLastSearchLocation(searchTerm) {
4257    var loc = lastLocationDefaultObj();
4258
4259    loc.searchTerm = searchTerm;
4260    loc.type = "search";
4261
4262    saveLastLocation(loc);
4263}
4264
4265function getLastLocation() {
4266    var str = getLocalSetting("lastLocation");
4267    var obj = null;
4268
4269    if (str != undefined && str != null) {
4270        try {
4271            obj = JSON.parse(str);
4272        } catch (e) {
4273            // Invalid JSON
4274            obj = null;
4275        }
4276    }
4277
4278    return obj;
4279}
4280
4281function saveLastLocation(obj) {
4282    saveLocalSetting("lastLocation", JSON.stringify(obj));
4283}
4284
4285function saveLocalSetting(key, value) {
4286    if (('localStorage' in window) && window.localStorage !== null) {
4287        localStorage[key] = value;
4288    }
4289}
4290
4291function getLocalSetting(key) {
4292    var retval = null;
4293
4294    if (('localStorage' in window) && window.localStorage !== null) {
4295        retval = localStorage[key];
4296    }
4297
4298    return retval;
4299}
4300
4301function loadProfiles() {
4302    jweather.baseUrl = apiurl;
4303    jweather.token = token;
4304
4305    var ps = new jweather.Data.ProfileService();
4306
4307    ps.getProfiles(function (data) {
4308        if (data.status.status_code == 0) {
4309            // UL element in the header
4310            var str = "";
4311            var profileList = $(".profileList-profiles");
4312            var profiles = data.profiles;
4313            var profileClass = "profile-item";
4314
4315            // Clear the list
4316            $(".profile-item", profileList).remove();
4317
4318            for (var x = 0; x < profiles.length; x++) {
4319                if (profiles[x].profile_id > 0) {
4320                    var profileLink = "/profile/" + profiles[x].profile_id;
4321
4322                    str += "<li id='" + profiles[x].profile_id + "' class='my-profile'><a href='" + profileLink + "'>" + profiles[x].name + "</a></li>";
4323                }
4324            }
4325
4326            $("#main-menu #profile-list").prepend(str);
4327        }
4328    });
4329}
4330
4331function loadRegionalNav() {
4332    var url = "/navigation.js";
4333    var dataType = "json";
4334
4335    var request = $.ajax({
4336        async: true,
4337        url: url,
4338        dataType: dataType
4339    });
4340
4341    request.done(function (data) {
4342        navData = data;
4343        regionalNavCallback();
4344    });
4345}
4346
4347function regionalNavCallback() {
4348    if (navData.hasOwnProperty("continents")) {
4349        for (var x = 0; x < navData.continents.length; x++) {
4350            var c = navData.continents[x];
4351            var cname = c.name.replace("&", "").replace(".", "").replace(/ /g, "");
4352            
4353            loadCountries(cname, navData.continents[x]);
4354        }
4355    }
4356
4357    attachRegionalNavEventHandlers();
4358}
4359
4360function loadCountries(continent, continentData) {
4361    var str = "";
4362
4363    if (continentData.hasOwnProperty("countries")) {
4364        var countries = continentData.countries;
4365
4366        for (var i = 0; i < countries.length; i++) {
4367            var c = countries[i];
4368            var idx = c.name.indexOf("--") == -1 ? i : "";
4369            var hasRegions = c.regions != undefined ? true : false;
4370            var cssClass = hasRegions ? "class='parent-menu'" : "";
4371            var locationInfo = c.c.split(",");
4372            var link = hasRegions || idx == "" ? "#" : "/windlist/" + locationInfo[0] + "/" + locationInfo[1];
4373
4374            str += "<li><a href='" + link + "' " + cssClass + " data-country-idx='" + idx + "'>" + c.name + "</a></li>";
4375        }
4376    }
4377
4378    $("#" + continent).empty().append(str);
4379}
4380
4381function attachRegionalNavEventHandlers() {
4382    // $(".Sub-Menu").click(function (e) {
4383    //     e.preventDefault();
4384    //     return false;
4385    // });
4386
4387    $("a[data-country-idx]").on("click", function () {
4388        var count = $(this).siblings(".sub-menu").length;
4389
4390        // Don't draw the menu if it already exists
4391        if (count == 0) {
4392            var subMenu = $(this).closest("ul");
4393            var subMenuId = subMenu.attr("id");
4394            var countryIdx = $(this).attr("data-country-idx");
4395            var continentIdx = $("a[data-sub-menu='" + subMenuId + "']").attr("data-continent-idx");
4396
4397            if (countryIdx != "" && continentIdx != "") {
4398                if (navData.continents[continentIdx].countries[countryIdx].regions) {
4399                    var regions = navData.continents[continentIdx].countries[countryIdx].regions;
4400                    var countryName = $(this).html();
4401                    var subMenuName = countryName.replace("&", "").replace(".", "").replace(/ /g, "");
4402
4403                    var str = "<ul id='" + subMenuName + "' class='sub-menu'>";
4404
4405                    for (var x = 0; x < regions.length; x++) {
4406                        var hasSubRegions = regions[x].subregions != undefined ? true : false;
4407                        var cssClass = hasSubRegions ? "class='parent-menu'" : "";
4408                        var locationInfo = regions[x].c.split(",");
4409                        var link = hasSubRegions ? "#" : "/windlist/" + locationInfo[0] + "/" + locationInfo[1];
4410
4411                        str += "<li><a href='" + link + "' " + cssClass + " data-region-idx='" + x + "'>" + regions[x].name + "</a></li>";
4412                    }
4413
4414                    str += "</ul>";
4415
4416                    $(this).attr("data-sub-menu", subMenuName);
4417                    $(this).parent().append(str);
4418                }
4419            } else {
4420                return false;
4421            }
4422        }
4423    });
4424
4425    $("body").on("click", "a[data-region-idx]", function () {
4426        var count = $(this).siblings(".sub-menu").length;
4427
4428        // Don't draw the menu if it already exists
4429        if (count == 0) {
4430            var countryMenu = $(this).closest("ul");
4431            var countryMenuId = countryMenu.attr("id");
4432            var continentMenu = $("a[data-sub-menu='" + countryMenuId + "']").closest("ul");
4433            var continentMenuId = continentMenu.attr("id");
4434            var regionIdx = $(this).attr("data-region-idx");
4435            var countryIdx = $("a[data-sub-menu='" + countryMenuId + "']").attr("data-country-idx");
4436            var continentIdx = $("a[data-sub-menu='" + continentMenuId + "']").attr("data-continent-idx");
4437
4438            if (regionIdx != "") {
4439                if (navData.continents[continentIdx].countries[countryIdx].regions[regionIdx].subregions) {
4440                    var regionName = $(this).html();
4441                    var subMenuName = regionName.replace("&", "").replace(".", "").replace(/ /g, "");
4442                    var subregions = navData.continents[continentIdx].countries[countryIdx].regions[regionIdx].subregions;
4443                    var str = "<ul id='" + subMenuName + "' class='sub-menu sub-menu
4443-show'>";
4444
4445                    for (var x = 0; x < subregions.length; x++) {
4446                        var locationInfo = subregions[x].c.split(",");
4447
4448                        str += "<li><a href='windlist/" + locationInfo[0] + "/" + locationInfo[1] + "' data-sub-region-idx='" + x + "'>" + subregions[x].name + "</a></li>";
4449                    }
4450
4451                    $(this).attr("data-sub-menu", subMenuName);
4452                    $(this).parent().append(str);
4453                }
4454            }
4455        }
4456    });
4457}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.