-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathapp-quickstart.js
3276 lines (2847 loc) · 143 KB
/
app-quickstart.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* **************************************************************
Copyright 2011 Zoovy, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************** */
var quickstart = function(_app) {
var r = {
vars : {
//a list of the templates used by this extension.
//if this is a custom extension and you are loading system extensions (prodlist, etc), then load ALL templates you'll need here.
"templates" : [
//the list of templates that are commonly edited (same order as they appear in appTemplates
'homepageTemplate', 'categoryTemplate',
'categoryListTemplate',
'categoryListTemplateRootCats',
'productListTemplate',
'productListTemplateATC',
'productListTemplateBuyerList',
'productListTemplateResults',
'productTemplate',
'productTemplateQuickView',
'pageNotFoundTemplate',
//the list of templates that, in most cases, are left alone. Also in the same order as appTemplates
'breadcrumbTemplate',
'companyTemplate',
'customerTemplate',
'searchTemplate',
'mpControlSpec',
'cartTemplate',
'productListTemplateCart',
'productListTemplateChildren',
'productReviewsTemplateDetail',
'imageViewerTemplate',
'reviewFrmTemplate',
'orderLineItemTemplate',
'invoiceTemplate',
'buyerListTemplate',
'buyerListProdlistContainerTemplate',
'faqTopicTemplate',
'faqQnATemplate',
'billAddressTemplate',
'shipAddressTemplate'],
"sotw" : {}, //state of the world. set to most recent page info object.
"hotw" : new Array(15), //history of the world. contains 15 most recent sotw objects.
"session" : {
"recentSearches" : [],
"recentlyViewedItems" : [],
"recentCategories" : []
} //a list of other extensions (just the namespace) that are required for this one to load
},
//////////////////////////////////// CALLS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
calls : {
}, //calls
//////////////////////////////////// CALLBACKS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
callbacks : {
init : {
onSuccess : function() {
var r = true; //return false if extension won't load for some reason (account config, dependencies, etc).
return r;
},
onError : function() {
//errors will get reported for this callback as part of the extensions loading. This is here for extra error handling purposes.
//you may or may not need it.
// dump('BEGIN _app.ext.quickstart.callbacks.init.onError');
}
},
startMyProgram : {
onSuccess : function() {
// dump("BEGIN quickstart.callback.startMyProgram");
// dump(" -> window.onpopstate: "+typeof window.onpopstate);
// dump(" -> window.history.pushState: "+typeof window.history.pushState);
//handle the cart. It could be passed in via _app.vars.cartID, as a URI param, localStorage or may not exist yet.
var cartID;
if(_app.vars.cartID) {
dump(" -> cartID was passed in via _app.vars.cartID");
cartID = _app.vars.cartID;
delete _app.vars.cartID; //leaving this here will just add confusion.
}
else if(cartID = _app.u.getParameterByName('cartID')) {
dump(' -> cart id was specified on the URI');
}
else if(cartID = _app.model.fetchCartID()) {
dump(" -> cartID obtained from fetchCartID. cartid: "+cartID);
//no need to add this cartID to the session/vars.carts, because that's where fetch gets it from.
}
else if(!$.support.localStorage) {
cartID = _app.model.readCookie('_cart'); //support browsers w/out localstorage
}
else {}
if(cartID) {
dump(" -> cartID is set, validate.");
// the addCart2CM uses the 'appCartExists' datapointer. if it changes here, update the callback.
_app.model.addDispatchToQ({"_cmd":"appCartExists",_cartid:cartID,"_tag":{"datapointer":"appCartExists","cartid":cartID,"callback":"addCart2CM","extension":"quickstart"}},"mutable");
//do not set cart ID in session until it validates.
}
else {
dump(" -> no cart found. create a new one");
_app.calls.appCartCreate.init({'callback':"addCart2CM","extension":'quickstart'},'mutable');
}
//technically, a session lasts until the browser is closed. if fresh data is desired on refresh, uncomment the following few lines.
//if($.support.sessionStorage) {
// window.sessionStorage.clear();
// }
_app.u.addEventDelegation($(document.body)); //if perfomance issues are noticed from adding this to the body instead of to each template, please report them.
var hotw = _app.model.dpsGet('quickstart','hotw');
if(!$.isEmptyObject(hotw)) {
_app.ext.quickstart.vars.hotw = hotw;
}
//if ?debug=anything is on URI, show all elements with a class of debug.
if(_app.u.getParameterByName('debug')) {
$('.debug').show().append("<div class='clearfix'>Model Version: "+_app.model.version+" and release: "+_app.vars.release+"</div>");
$('.debugQuickLinks','.debug').menu().css({'width':'150px'});
$('button','.debug').button();
$('body').css('padding-bottom',$('.debug').last().height());
}
document.write = function(v){
dump("document.write was executed. That's bad mojo. Rewritten to $('body').append();",'warn');
$("body").append(v);
}
window.showContent = _app.ext.quickstart.a.showContent; //a shortcut for easy execution.
window.quickView = _app.ext.quickstart.a.quickView; //a shortcut for easy execution.
//The request for appCategoryList is needed early for both the homepage list of cats and tier1.
//piggyback a few other necessary requests here to reduce # of requests
_app.ext.store_navcats.calls.appCategoryList.init(zGlobals.appSettings.rootcat,{"callback":"showRootCategories","extension":"quickstart"},'mutable');
_app.model.dispatchThis('mutable');
}
}, //startMyProgram
addCart2CM : {
onSuccess : function(_rtag){
var cartID = false;
// _app.u.dump("BEGIN quickstart.callbacks.addCart2CM.onSuccess");
if(_rtag.datapointer == 'appCartExists' && _app.data[_rtag.datapointer].exists) {
// _app.u.dump(" -> existing cart is valid. add to cart manager");
// dump(" -> _rtag:"); dump(_rtag);
cartID = _rtag.cartid;
_app.model.addCart2Session(cartID);
// dump(" -> cart id is valid. added the cart to the session is "+_app.model.addCart2Session(cartID)); //this function updates _app.vars.carts
if($('#cartMessenger').length) {
_app.ext.cart_message.u.initCartMessenger(cartID,$('#cartMessenger')); //starts the cart message polling
$('#cartMessenger').tlc({'verb':'translate','dataset':_app.data['cartDetail|'+cartID]}).attr('data-cartid',cartID);
$("textarea[name='message']",'#cartmessenger').on('keypress',function(event){
if (event.keyCode == 13) {
$("[data-app-role='messageSubmitButton']",$(this).closest('form')).trigger('click');
return false;
}
return true;
});
}
else {
dump("#cartMessenger does NOT exist. That means the cart messaging extension won't work right.","warn");
}
}
else if(_rtag.datapointer == 'appCartExists') {
_app.u.dump(" -> existing cart was NOT valid. Fetch a new cartid");
_app.model.removeCartFromSession(_rtag.cartid); //this will ensure the cart isn't used again.
_app.calls.appCartCreate.init({'callback':"addCart2CM","extension":'quickstart'},'mutable');//The cart that was passed was exired in invalid.
_app.model.dispatchThis('mutable');
}
else {
_app.u.dump(" -> cart has been created.");
//this was a appCartCreate.
if($('#cartMessenger').length) {
cartID = _app.model.fetchCartID();
_app.ext.cart_message.u.initCartMessenger(cartID,$('#cartMessenger')); //starts the cart message polling
}
else {
dump("#cartMessenger does NOT exist. That means the cart messaging extension won't work right.","warn");
}
}
if(cartID) {
_app.model.addDispatchToQ({"_cmd":"whoAmI",_cartid : cartID, "_tag":{"datapointer":"whoAmI",callback:function(rd){
myApp.router.init();//instantiates the router.
_app.ext.quickstart.u.handleAppInit(); //finishes loading RQ. handles some authentication based features.
_app.calls.refreshCart.init({'callback':'updateMCLineItems','extension':'quickstart'},'mutable');
_app.model.dispatchThis('mutable');
_app.ext.quickstart.u.bindAppNav(); //adds click handlers for the next/previous buttons (product/category feature).
// if(typeof _app.u.appInitComplete == 'function'){_app.u.appInitComplete()}; //gets run after app has been init
_app.ext.quickstart.thirdParty.init();
}}},"mutable"); //used to determine if user is logged in or not.
_app.model.dispatchThis('mutable');
if(!$.support.localStorage) {
_app.model.writeCookie('_cart',cartID); //support browsers w/ localstorage disabled.
}
}
}
},
//optional callback for appCategoryList in app init which will display the root level categories in element w/ id: tier1categories
showRootCategories : {
onSuccess : function() {
// dump('BEGIN _app.ext.quickstart.callbacks.showCategories.onSuccess');
var tagObj = {};
//we always get the tier 1 cats so they're handy, but we only do something with them out of the get if necessary (tier1categories is defined)
if($('#tier1categories').length) {
// dump("#tier1categories is set. fetch tier1 cat data.");
_app.ext.store_navcats.u.getChildDataOf(zGlobals.appSettings.rootcat,{'parentID':'tier1categories','callback':'addCatToDom','templateID':'categoryListTemplateRootCats','extension':'store_navcats'},'max'); //generate nav for 'browse'. doing a 'max' because the page will use that anway.
_app.model.dispatchThis();
}
}
}, //showRootCategories
//used for callback on showCartInModal function
//
handleCart : {
onSuccess : function(tagObj) {
// dump("BEGIN quickstart.callbacks.handleCart.onSuccess");
// dump(" -> tagObj: "); dump(tagObj);
_app.ext.quickstart.u.handleMinicartUpdate(tagObj);
//empty is to get rid of loading gfx.
var $cart = tagObj.jqObj || $(_app.u.jqSelector('#',tagObj.parentID));
// * 201401 -> removing all references to renderFunctions in favor of tlc.
// $cart.empty().append(_app.renderFunctions.transmogrify('modalCartContents',tagObj.templateID,_app.data[tagObj.datapointer]));
$cart.empty().tlc(tagObj)
tagObj.state = 'complete'; //needed for handleTemplateEvents.
_app.renderFunctions.handleTemplateEvents($cart,tagObj);
}
}, //updateMCLineItems
//used in init.
updateMCLineItems : {
onSuccess : function(tagObj) {
// dump("BEGIN quickstart.callbacks.updateMCLineItems");
_app.ext.quickstart.u.handleMinicartUpdate(tagObj);
}
}, //updateMCLineItems
showProd : {
onSuccess : function(tagObj) {
// dump("BEGIN quickstart.callbacks.showProd");
// dump(tagObj);
var tmp = _app.data[tagObj.datapointer];
var pid = _app.data[tagObj.datapointer].pid;
tmp.session = _app.ext.quickstart.vars.session;
if(typeof _app.data['appReviewsList|'+pid] == 'object') {
tmp['reviews'] = _app.ext.store_product.u.summarizeReviews(pid); //generates a summary object (total, average)
tmp['reviews']['@reviews'] = _app.data['appReviewsList|'+pid]['@reviews']
}
// dump("Rendering product template for: "+pid);
tagObj.jqObj.tlc({'verb':'translate','dataset':tmp});
tagObj.pid = pid;
//build queries will validate the namespaces used AND also fetch the parent product if this item is a child.
_app.ext.quickstart.u.buildQueriesFromTemplate(tagObj);
_app.model.dispatchThis();
// SANITY - handleTemplateEvents does not get called here. It'll get executed as part of showPageContent callback, which is executed in buildQueriesFromTemplate.
},
onError : function(responseData,uuid) {
// dump(responseData);
// $('#mainContentArea').empty();
_app.u.throwMessage(responseData);
}
}, //showProd
handleBuyerAddressUpdate : {
onSuccess : function(tagObj) {
dump("BEGIN quickstart.callbacks.handleBuyerAddressUpdate");
// dump(tagObj);
$parent = $('#'+tagObj.parentID);
$('button',$parent).removeAttr('disabled').removeClass('ui-state-disabled');
$('.changeLog',$parent).empty().append('Changes Saved');
$('.edited',$parent).removeClass('edited'); //if the save button is clicked before 'exiting' the input, the edited class wasn't being removed.
$('.buttonMenu',$parent).find('.offMenu').show();
$('.buttonMenu',$parent).find('.onMenu').hide();
},
onError : function(responseData,uuid) {
var $parent = $('#'+tagObj.parentID);
$('.changeLog',$parent).append(_app.u.formatResponseErrors(responseData))
$('button',$parent).removeAttr('disabled').removeClass('ui-state-disabled');
}
}, //handleBuyerAddressUpdate
//used as part of showContent for the home and category pages. goes and gets all the data.
fetchPageContent : {
onSuccess : function(tagObj) {
var catSafeID = tagObj.datapointer.split('|')[1];
tagObj.navcat = catSafeID;
// passing in unsanitized tagObj caused an issue with showPageContent
_app.ext.quickstart.u.buildQueriesFromTemplate($.extend(true, {}, tagObj));
_app.model.dispatchThis();
},
onError : function(responseData) {
_app.u.throwMessage(responseData);
$('.loadingBG',$('#mainContentArea')).removeClass('loadingBG'); //nuke all loading gfx.
_app.ext.quickstart.u.changeCursor('auto'); //revert cursor so app doesn't appear to be in waiting mode.
}
}, //fetchPageContent
//used as part of showContent for the home and category pages. Will display the data retrieved from fetch.
showPageContent : {
onSuccess : function(tagObj) {
// dump(" BEGIN quickstart.callbacks.onSuccess.showPageContent");
//when translating a template, only 1 dataset can be passed in, so detail and page are merged and passed in together.
//cat page handling.
if(tagObj.navcat) {
// dump("BEGIN quickstart.callbacks.showPageContent ["+tagObj.navcat+"]");
tagObj.dataset = {};
//if no %page vars were requested, this datapointer won't be set and this would error.
if(_app.data["appPageGet|"+tagObj.navcat] && _app.data["appPageGet|"+tagObj.navcat]['%page']) {
tagObj.dataset = {'%page' : _app.data['appPageGet|'+tagObj.navcat]['%page']};
}
//deep extend so any non-duplicates in %page are preserved.
$.extend(true,tagObj.dataset,_app.data['appNavcatDetail|'+tagObj.navcat],{'session':_app.ext.quickstart.vars.session});
delete tagObj.datapointer; //delete this so tlc doesn't do an unnecessary extend (data is already merged)
tagObj.verb = 'translate';
// dump(" -> tagObj: "); dump(tagObj);
_app.ext.quickstart.u.updateDOMTitle((tagObj.navcat == '.' ? 'Home' : tagObj.dataset.pretty));
if(tagObj.lists && tagObj.lists.length) {
var L = tagObj.lists.length;
for(var i = 0; i < L; i += 1) {
tagObj.dataset[tagObj.lists[i]] = _app.data['appNavcatDetail|'+tagObj.lists[i]];
}
}
//a category page gets translated. A product page does not because the bulk of the product data has already been output. prodlists are being handled via buildProdlist
// _app.renderFunctions.translateTemplate(tmp,tagObj.parentID); // * 201401 -> removing all references to renderFunctions in favor of tlc.
tagObj.jqObj.tlc(tagObj);
}
//product page handline
else if(tagObj.pid) {
// the bulk of the product translation has already occured by now (attribs, reviews and session) via callbacks.showProd.
// product lists are being handled through 'buildProductList'.
var pData = _app.data['appProductGet|'+tagObj.pid] //shortcut.
_app.ext.quickstart.u.updateDOMTitle(pData['%attribs']['zoovy:prod_seo_title'] || pData['%attribs']['zoovy:prod_name']);
if(pData && pData['%attribs'] && pData['%attribs']['zoovy:grp_type'] == 'CHILD') {
if(pData['%attribs']['zoovy:grp_parent'] && _app.data['appProductGet|'+pData['%attribs']['zoovy:grp_parent']]) {
dump(" -> this is a child product and the parent prod is available. Fetch child data for siblings.");
$("[data-app-role='childrenSiblingsContainer']",$(_app.u.jqSelector('#',tagObj.parentID))).show().tlc({'verb':'translate','dataset':_app.data['appProductGet|'+pData['%attribs']['zoovy:grp_parent']]});
}
else if(!pData['%attribs']['zoovy:grp_parent']) {
dump("WARNING! this item is a child, but no parent is set. Not a critical error.");
}
else {
dump("WARNING! Got into showPageContent callback for a CHILD item, but PARENT ["+pData['%attribs']['zoovy:grp_parent']+"] data not in memory.");
}
}
else {} //not a child.
}
else {
dump("WARNING! showPageContent has no pid or navcat defined");
}
tagObj.state = 'complete'; //needed for handleTemplateEvents.
_app.u.handleButtons(tagObj.jqObj);
_app.u.handleCommonPlugins(tagObj.jqObj);
_app.renderFunctions.handleTemplateEvents((tagObj.jqObj || $(_app.u.jqSelector('#',tagObj.parentID))),tagObj);
},
onError : function(responseData,uuid) {
$('#mainContentArea').removeClass('loadingBG')
_app.u.throwMessage(responseData);
}
}, //showPageContent
//this is used for showing a customer list of product, such as wish or forget me lists
//formerly showlist
buyerListAsProdlist : {
onSuccess : function(tagObj) {
// dump('BEGIN _app.ext.quickstart.showList.onSuccess ');
var listID = tagObj.datapointer.split('|')[1];
var prods = _app.ext.store_crm.u.getSkusFromBuyerList(listID);
if(prods.length < 1) {
//list is empty.
tagObj.jqObj.parent().anymessage({'message':'This list ('+listID+') appears to be empty.'});
}
else {
// dump(prods);
// tagObj.jqObj.tlc({'verb':'translate','datapointer':tagObj.datapointer});
_app.ext.store_prodlist.u.buildProductList({"loadsTemplate":"productListTemplateBuyerList","withInventory":1,"withVariations":1,"parentID":tagObj.parentID,"csv":prods,"hide_summary":1,"hide_pagination":1},tagObj.jqObj);
_app.model.dispatchThis();
}
}
}, //showList
//a call back to be used to show a specific list of product in a specific element.
//requires templateID and targetID to be passed on the tag object.
showProdList : {
onSuccess : function(tagObj) {
// dump("BEGIN quickstart.callbacks.showProdList");
// dump(_app.data[tagObj.datapointer]);
if(_app.data[tagObj.datapointer]['@products'].length < 1) {
$('#'+tagObj.targetID).anymessage({'message':'This list ('+listID+') appears to be empty.'});
}
else {
_app.ext.store_prodlist.u.buildProductList({"templateID":tagObj.templateID,"parentID":tagObj.targetID,"csv":_app.data[tagObj.datapointer]['@products']})
_app.model.dispatchThis();
}
}
}, //showList
authenticateBuyer : {
onSuccess : function(tagObj) {
_app.vars.cid = _app.data[tagObj.datapointer].cid; //save to a quickly referencable location.
$('#loginSuccessContainer').show(); //contains 'continue' button.
$('#loginMessaging').empty().show().append("Thank you, you are now logged in."); //used for success and fail messaging.
$('#loginFormContainer').hide(); //contains actual form.
$('#recoverPasswordContainer').hide(); //contains password recovery form.
_app.ext.quickstart.u.handleLoginActions();
}
} //authenticateBuyer
}, //callbacks
//////////////////////////////////// WIKILINKFORMATS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
/*
the wiki translator has defaults for the links built in. however, these will most likely
need to be customized on a per-ria basis.
*/
wiki : {
":search" : function(suffix,phrase){
return "<a href='#' onClick=\"return showContent('search',{'KEYWORDS':'"+encodeURI(suffix)+"'}); \">"+phrase+"<\/a>";
},
":category" : function(suffix,phrase){
return "<a href='#category?navcat="+suffix+"' onClick='return showContent(\"category\",{\"navcat\":\""+suffix+"\"});'>"+phrase+"<\/a>";
},
":product" : function(suffix,phrase){
return "<a href='#product?pid="+suffix+"' onClick='return showContent(\"product\",{\"pid\":\""+suffix+"\"});'>"+phrase+"<\/a>";
},
":customer" : function(suffix,phrase){
return "<a href='#customer?show="+suffix+"' onClick='return showContent(\"customer\",{\"show\":\""+suffix+"\"});'>"+phrase+"<\/a>";
},
":policy" : function(suffix,phrase){
return "<a href='#policy?show="+suffix+"' onClick='return showContent(\"company\",{\"show\":\""+suffix+"\"});'>"+phrase+"<\/a>";
},
":app" : function(suffix,phrase){
var output; //what is returned.
if(suffix == 'contact') {
output = "<a href='#policy?show="+suffix+"' onClick='return showContent(\"company\",{\"show\":\""+suffix+"\"});'>"+phrase+"<\/a>";
}
else if(suffix == 'contact') {
output = "<a href='#policy?show="+suffix+"' onClick='return showContent(\"company\",{\"show\":\""+suffix+"\"});'>"+phrase+"<\/a>";
}
else {
//we'll want to do something fantastic here.
output = phrase;
}
return output;
},
":popup" : function(suffix,phrase) {
return "<a href=\""+suffix+"\" target='popup' data-app-click='quickstart|popup'>"+phrase+"</a>";
}
}, //wiki
// * 201403 -> infoObj now passed into pageTransition.
pageTransition : function($o,$n, infoObj) {
//if $o doesn't exist, the animation doesn't run and the new element doesn't show up, so that needs to be accounted for.
//$o MAY be a jquery instance but have no length, so check both.
if($o instanceof jQuery && $o.length) {
/*
*** 201403 -> move the scroll to top into the page transition for 2 reasons:
1. allows the animations to be performed sequentially, which will be less jittery than running two at the same time
2. Puts control of this into custom page transitions.
*/
if(infoObj.performJumpToTop && $(window).scrollTop() > 0) { // >0 scrolltop check should be on window, it'll work in ff AND chrome (body or html won't).
//new page content loading. scroll to top.
$('html, body').animate({scrollTop : 0},'fast',function(){
$o.fadeOut(1000, function(){$n.fadeIn(1000)}); //fade out old, fade in new.
})
}
else {
$o.fadeOut(1000, function(){$n.fadeIn(1000)}); //fade out old, fade in new.
}
}
else if($n instanceof jQuery) {
dump(" -> $o is not properly defined. jquery: "+($o instanceof jQuery)+" and length: "+$o.length);
$('html, body').animate({scrollTop : 0},'fast',function(){
$n.fadeIn(1000);
});
}
else {
//hhmm not sure how or why we got here.
dump("WARNING! in pageTransition, neither $o nor $n were instances of jQuery. how odd.",'warn');
}
}, //pageTransition
tlcFormats : {
searchbytag : function(data,thisTLC) {
var argObj = thisTLC.args2obj(data.command.args,data.globals); //this creates an object of the args
var query = {"size":(argObj.size || 4),"mode":"elastic-search","filter":{"term":{"tags":argObj.tag}}};
_app.ext.store_search.calls.appPublicProductSearch.init(query,$.extend({'datapointer':'appPublicSearch|tag|'+argObj.tag,'templateID':argObj.templateid,'extension':'store_search','callback':'handleElasticResults','list':data.globals.tags[data.globals.focusTag]},argObj));
_app.model.dispatchThis('mutable');
return false; //in this case, we're off to do an ajax request. so we don't continue the statement.
}
},
//////////////////////////////////// RENDERFORMATS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
renderFormats : {
//This function works in conjuction with the showContent/showPage and buildQueriesFromTemplate functions.
//the parent and subcategory data (appNavcatDetail) must be in memory already for this to work right.
//data.value is the category object. data.bindData is the bindData obj.
subcategorylist : function($tag,data) {
// dump("BEGIN control.renderFormats.subcats");
// dump(data.value);
var L = data.value.length;
var thisCatSafeID; //used in the loop below to store the cat id during each iteration
// dump(data);
for(var i = 0; i < L; i += 1) {
thisCatSafeID = data.value[i].id;
if(data.value[i].id[1] == '$') {
//ignore 'lists', which start with .$
// dump(" -> list! "+data.value[i].id);
}
else if(!data.value[i].pretty || data.value[i].pretty.charAt(0) == '!') {
//categories that start with ! are 'hidden' and should not be displayed.
}
else if(!$.isEmptyObject(_app.data['appNavcatDetail|'+thisCatSafeID])) {
// $tag.append(_app.renderFunctions.transmogrify({'id':thisCatSafeID,'catsafeid':thisCatSafeID},data.bindData.loadsTemplate,_app.data['appNavcatDetail|'+thisCatSafeID]));
$tag.tlc({
dataAttribs : {'id':thisCatSafeID,'catsafeid':thisCatSafeID},
dataset : _app.data['appNavcatDetail|'+thisCatSafeID],
templateid : data.bindData.loadsTemplate
})
}
else {
dump("WARNING - subcategoryList reference to appNavcatDetail|"+thisCatSafeID+" was an empty object.");
}
}
}, //subcategoryList
//set bind-data to val: product(zoovy:prod_is_tags) which is a comma separated list
//used for displaying a series of tags, such as on the product detail page. Will show any tag enabled.
//on bind-data, set maxTagsShown to 1 to show only 1 tag
tags : function($tag,data) {
var whitelist = new Array('IS_PREORDER','IS_DISCONTINUED','IS_SPECIALORDER','IS_SALE','IS_CLEARANCE','IS_NEWARRIVAL','IS_BESTSELLER','IS_USER1','IS_USER2','IS_USER3','IS_USER4','IS_USER5','IS_USER6','IS_USER7','IS_USER8','IS_USER9','IS_FRESH','IS_SHIPFREE');
// var csv = data.value.split(',');
var L = whitelist.length;
var tagsDisplayed = 0;
var maxTagsShown = _app.u.isSet(data.bindData.maxTagsShown) ? data.bindData.maxTagsShown : whitelist.length; //default to showing all enabled tags.
var spans = ""; //1 or more span tags w/ appropriate tag class applied
for(var i = 0; i < L; i += 1) {
if(data.value.indexOf(whitelist[i]) >= 0 && (tagsDisplayed <= maxTagsShown)) {
spans += "<span class='tagSprite "+whitelist[i].toLowerCase()+"'><\/span>";
tagsDisplayed += 1;
}
if(tagsDisplayed >= maxTagsShown) {break;} //exit early once enough tags are displayed.
}
$tag.append(spans);
}, //tags
//if first char is a !, hide that char, then render as text. used in breadcrumb
//likely to be used in prodcats if/when it's built.s
//here, on 'could' disable the display if they didn't want hidden cats to show in the breadcrumb.
cattext : function($tag,data) {
// dump(" -> value: "); dump(data.value);
if(data.value && data.value[0] == '!') {
data.value = data.value.substring(1)
}
_app.renderFormats.text($tag,data);
},
cpsiawarning : function($tag,data) {
if(data.value) {
var warnings = {
'choking_hazard_balloon' : 'Choking Hazard Balloon',
'choking_hazard_contains_a_marble' : 'Choking Hazard contains a marble',
'choking_hazard_contains_small_ball' : 'Choking Hazard contains a small ball',
'choking_hazard_is_a_marble' : 'Choking Hazard is a marble',
'choking_hazard_is_a_small_ball' : 'Choking Hazard is a small ball',
'choking_hazard_small_parts' : 'Choking Hazard small parts',
'no_warning_applicable' : 'No Warning Applicable'
};
if(warnings[data.value]) {
$tag.append(warnings[data.value]);
}
else {
$tag.append(data.value);
}
}
},
addpicslider : function($tag,data) {
// dump("BEGIN quickstart.renderFormats.addPicSlider: "+data.value);
if(data.value && typeof _app.data['appProductGet|'+data.value] == 'object') {
var pdata = _app.data['appProductGet|'+data.value]['%attribs'];
//if image 1 or 2 isn't set, likely there are no secondary images. stop.
if(_app.u.isSet(pdata['zoovy:prod_image1']) && _app.u.isSet(pdata['zoovy:prod_image2'])) {
$tag.attr('data-pid',data.value); //no params are passed into picSlider function, so pid is added to tag for easy ref.
// dump(" -> image1 ["+pdata['zoovy:prod_image1']+"] and image2 ["+pdata['zoovy:prod_image2']+"] both are set.");
//adding this as part of mouseenter means pics won't be downloaded till/unless needed.
// no anonymous function in mouseenter. We'll need this fixed to ensure no double add (most likely) if template re-rendered.
// $tag.unbind('mouseenter.myslider'); // ensure event is only binded once.
$tag.bind('mouseenter.myslider',_app.ext.quickstart.u.addPicSlider2UL).bind('mouseleave',function(){window.slider.kill()})
}
}
},
//no click event is added to this. do that on a parent element so that this can be recycled.
youtubethumbnail : function($tag,data) {
if(data.value) {
$tag.attr('src',"https://i3.ytimg.com/vi/"+data.value+"/default.jpg");
}
return true;
}, //youtubeThumbnail
// used for a product list of an elastic search results set. a results object and category page product list array are structured differently.
// when using @products in a categoryDetail object, use productList as the renderFormat
// this function gets executed after the request has been made, in the showPageContent response. for this reason it should NOT BE MOVED to store_search
// ## this needs to be upgraded to use _app.ext.store_search.u.getElasticResultsAsJQObject
productsearch : function($tag,data) {
// dump("BEGIN quickstart.renderFormats.productSearch");
data.bindData = _app.renderFunctions.parseDataBind($tag.attr('data-bind'));
// dump(data);
if(data.value) {
var parentID = $tag.attr('id');
var L = data.value.hits.hits.length;
var templateID = data.bindData.loadsTemplate ? data.bindData.loadsTemplate : 'productListTemplateResults';
var pid;
if(data.value.hits.total) {
for(var i = 0; i < L; i += 1) {
pid = data.value.hits.hits[i]['_id'];
// $tag.append(_app.renderFunctions.transmogrify({'id':parentID+'_'+pid,'pid':pid},templateID,data.value.hits.hits[i]['_source']));
$tag.tlc({
dataAttribs : {'id':parentID+'_'+pid,'pid':pid},
templateid : templateID,
dataset : data.value.hits.hits[i]['_source']
});
}
if(data.bindData.before) {$tag.before(data.bindData.before)} //used for html
if(data.bindData.after) {$tag.after(data.bindData.after)}
if(data.bindData.wrap) {$tag.wrap(data.bindData.wrap)}
}
}
else {} //no value, so do nothing.
}, //productSearch
/*
data.value in a banner element is passed in as a string of key value pairs:
LINK, ALT and IMG
The value of link could be a hash (anchor) or a url (full or relative) so we try to guess.
fallback is to just output the value.
*/
banner : function($tag, data) {
if(data.value) {
// dump("begin quickstart.renderFormats.banner");
var obj = _app.u.kvp2Array(data.value), //returns an object LINK, ALT and IMG
hash, //used to store the href value in hash syntax. ex: #company?show=return
pageInfo = {};
//if value starts with a #, then most likely the hash syntax is being used.
if(obj.LINK && obj.LINK.indexOf('#') == 0) {
hash = obj.LINK;
pageInfo = _app.ext.quickstart.u.getPageInfoFromHash(hash);
}
// Initially attempted to do some sort of validating to see if this was likely to be a intra-store link.
// && data.value.indexOf('/') == -1 || data.value.indexOf('http') == -1 || data.value.indexOf('www') > -1
else if(obj.LINK) {
pageInfo = _app.ext.quickstart.u.detectRelevantInfoToPage(obj.LINK);
if(pageInfo.pageType) {
hash = _app.ext.quickstart.u.getHashFromPageInfo(pageInfo);
}
else {
hash = obj.LINK
}
}
else {
//obj.link is not set
}
if(!_app.u.isSet(obj.IMG)) {$tag.remove()} //if the image isn't set, don't show the banner. if a banner is set, then unset, val may = ALT=&IMG=&LINK=
else {
//if we don't have a valid pageInfo object AND a valid hash, then we'll default to what's in the obj.LINK value.
$tag.attr('alt',obj.ALT);
//if the link isn't set, no href is added. This is better because no 'pointer' is then on the image which isn't linked.
if(obj.LINK) {
// dump(" -> obj.LINK is set: "+obj.LINK);
var $a = $("<a />").addClass('bannerBind').attr({'href':hash,'title':obj.ALT});
if(pageInfo && pageInfo.pageType) {
$a.click(function(){
return showContent('',pageInfo)
})
}
$tag.wrap($a);
}
data.value = obj.IMG; //this will enable the image itself to be rendered by the default image handler. recycling is good.
_app.renderFormats.imageURL($tag,data);
}
}
}, //banner
//could be used for some legacy upgrades that used the old textbox/image element combo to create a banner.
legacyurltoria : function($tag,data) {
if(data.value == '#') {
$tag.removeClass('pointer');
}
else if(data.value && data.value.indexOf('#!') == 0) {
//link is formatted correctly. do nothing.
}
else if(data.value) {
$tag.attr('href',_app.ext.quickstart.u.getHashFromPageInfo(_app.ext.quickstart.u.detectRelevantInfoToPage(data.value)));
}
else {
//data.value is not set. do nothing.
}
}, //legacyURLToRIA
//use in a cart item spec. When clicked, button will first add the item to the wishlist and then, if that's succesful, remove the item from the cart.
// render format will also hide the button if the user is not logged in.
movetowishlistbutton : function($tag,data) {
//nuke remove button for coupons.
if(data.value.stid[0] == '%') {$tag.remove()} //coupon.
else if(data.value.asm_master) {$tag.remove()} //assembly 'child'.
else if(_app.u.buyerIsAuthenticated()) {
$tag.show().button({icons: {primary: "ui-icon-heart"},text: false});
$tag.off('click.moveToWishlist').on('click.moveToWishList',function(){
_app.ext.quickstart.a.moveItemFromCartToWishlist(data.value,$tag.closest("[data-template-role='cart']"));
});
}
else {$tag.hide();}
},
//This is for use on a category or search results page.
//changes the text on the button based on certain attributes.
//_app.ext.quickstart.u.handleAddToCart($(this),{'action':'modal'});
addtocartbutton : function($tag,data) {
// dump("BEGIN store_product.renderFunctions.addtocartbutton");
//if price is not set, item isn't purchaseable. buttonState is set to 'disabled' if item isn't purchaseable or is out of stock.
var className, price, buttonState, buttonText = 'Add to Cart',
pid = data.value.pid, //...pid set in both elastic and appProductGet
inv = _app.ext.store_product.u.getProductInventory(_app.data['appProductGet|'+pid]),
$form = $tag.closest('form');
// dump(" -> $form.length: "+$form.length);
// if(_app.model.fetchData('appProductGet|'+pid)) {}
if(data.bindData.isElastic) {
price = data.value.base_price;
// ** 201332 indexOf changed to $.inArray for IE8 compatibility, since IE8 only supports the indexOf method on Strings
if($.inArray('IS_PREORDER', data.value.tags) > -1) {buttonText = 'Preorder'; className = 'preorder';}
else if($.inArray('IS_COLORFUL', data.value.tags) > -1) {buttonText = 'Choose Color'; className = 'variational colorful';}
else if($.inArray('IS_SIZEABLE', data.value.tags) > -1) {buttonText = 'Choose Size'; className = 'variational sizeable';}
else if(data.value.pogs.length > 0) {buttonText = 'Choose Options'; className = 'variational';}
else {}
//look in tags for tags. indexOf
}
else if(data.value['%attribs']) {
var pData = data.value['%attribs']; //shortcut
price = pData['zoovy:base_price'];
if(pData['is:preorder']) {
buttonText = 'Preorder'; className = 'preorder';
}
else if(pData['is:colorful']) {
buttonText = 'Choose Color'; className = 'variational colorful';
}
else if(pData['is:sizeable']) {
buttonText = 'Choose Size'; className = 'variational sizeable';
}
//pdata is a shortcut to attribs.
//*** 201346 Must also check for product children as variations, as parent products cannot be added to cart.
else if(!$.isEmptyObject(data.value['@variations']) || pData['zoovy:grp_children']) {
buttonText = 'Choose Options'; className = 'variational';
}
else {
}
}
//no price and/or no inventory mean item is not purchaseable.
if(!price) {
buttonState = 'disable';
}
//*** 201352 original inventory check - if(inv...) -was short circuiting on 0/false inventory, meaning that prods with no inventory were not getting this button disabled. -mc
else if(typeof inv !== "undefined" && (!inv || inv <= 0)) {buttonState = 'disable';}
else{}
// dump(" -> inv: "+inv);
$tag.addClass(className).text(buttonText);
$tag.button();
if(buttonState) {$tag.button(buttonState)}
else {
if(buttonText.toLowerCase() == 'add to cart') {
$tag.on('click.detailsOrAdd',function(event){
event.preventDefault();
$form.trigger('submit'); //submitting the form (which has an add to cart action) instead of directly executing some action here, makes this more versatile. allows the action to be changed to support other add to cart/display cart actions.
})
}
else {
$tag.attr({'data-hash':'#!product/'+pid,'data-app-click':'quickstart|showContent'});
}
}
// dump(" -> ID at end: "+$tag.attr('id'));
}, //addtocartbutton
//pass in the sku for the bindata.value so that the original data object can be referenced for additional fields.
// will show price, then if the msrp is MORE than the price, it'll show that and the savings/percentage.
priceretailsavingsdifference : function($tag,data) {
var o; //output generated.
var pData = data.value;
//use original pdata vars for display of price/msrp. use parseInts for savings computation only.
var price = Number(pData['zoovy:base_price']);
var msrp = Number(pData['zoovy:prod_msrp']);
if(price > 0 && (msrp - price > 0)) {
o = _app.u.formatMoney(msrp-price,'$',2,true)
$tag.append(o);
}
else {
$tag.hide(); //if msrp > price, don't show savings because it'll be negative.
}
}, //priceRetailSavings
//pass in the sku for the bindata.value so that the original data object can be referenced for additional fields.
// will show price, then if the msrp is MORE than the price, it'll show that and the savings/percentage.
priceretailsavingspercentage : function($tag,data) {
var o; //output generated.
var pData = data.value;
//use original pdata vars for display of price/msrp. use parseInts for savings computation only.
var price = Number(pData['zoovy:base_price']);
var msrp = Number(pData['zoovy:prod_msrp']);
if(price > 0 && (msrp - price > 0)) {
var savings = (( msrp - price ) / msrp) * 100;
o = savings.toFixed(0)+'%';
$tag.append(o);
}
else {
$tag.hide(); //if msrp > price, don't show savings because it'll be negative.
}
} //priceRetailSavings
}, //renderFormats
//////////////////////////////////// ACTION [a] \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
a : {
//loads page content. pass in a type: category, product, customer or help
// and a page info: catSafeID, sku, customer admin page (ex: newsletter) or 'returns' (respectively to the line above.
// myria.vars.session is where some user experience data is stored, such as recent searches or recently viewed items.
// -> unshift is used in the case of 'recent' so that the 0 spot always holds the most recent and also so the length can be maintained (kept to a reasonable #).
// infoObj.back can be set to 0 to skip a URI update (will skip both hash state and popstate.)
showContent : function(pageType,infoObj) {
// dump("BEGIN showContent ["+pageType+"]."); dump(infoObj);
infoObj = infoObj || {}; //could be empty for a cart or checkout
/*
what is returned. is set to true if pop/pushState NOT supported.
if the onclick is set to return showContent(... then it will return false for browser that support push/pop state but true
for legacy browsers. That means old browsers will use the anchor to retain 'back' button functionality.
*/
var
r = false,
$old = $("#mainContentArea :visible:first"), //used for transition (actual and validation).
$new; //a jquery object returned by the 'show' functions (ex: showProd).
//clicking to links (two product, for example) in a short period of time was rendering both pages at the same time.
//this will fix that and only show the last clicked item. state of the world render this code obsolete.
if($old.length) {
$old.siblings().hide(); //make sure only one 'page' is visible.
}
_app.ext.quickstart.u.closeAllModals(); //important cuz a 'showpage' could get executed via wiki in a modal window.
//if pageType isn't passed in, we're likely in a popState, so look in infoObj.
if(pageType){infoObj.pageType = pageType} //pageType
else if(pageType == '') {pageType = infoObj.pageType}
_app.ext.quickstart.u.handleSearchInput(pageType); //will clear keyword searches when on a non-search page, to avoid confusion.
//set some defaults.
infoObj.back = infoObj.back == 0 ? infoObj.back : -1; //0 is no 'back' action. -1 will add a pushState or hash change.
infoObj.performTransition = infoObj.performTransition || _app.ext.quickstart.u.showtransition(infoObj,$old); //specific instances skip transition.
infoObj.state = 'init'; //needed for handleTemplateEvents.
//if there's history (all pages loads after first, execute the onDeparts functions.
//must be run before handleSandHOTW or history[0] will be this infoObj, not the last one.
if(!$.isEmptyObject(_app.ext.quickstart.vars.hotw[0])) {
_app.renderFunctions.handleTemplateEvents($old,$.extend(_app.ext.quickstart.vars.hotw[0],{"state":"depart"}))
}
_app.ext.quickstart.u.handleSandHOTW(infoObj);
//handles the appnav. the ...data function must be run first because the display function uses params set by the function.
_app.ext.quickstart.u.handleAppNavData(infoObj);
_app.ext.quickstart.u.handleAppNavDisplay(infoObj);
switch(pageType) {
case 'product':
//add item to recently viewed list IF it is not already in the list.
if($.inArray(infoObj.pid,_app.ext.quickstart.vars.session.recentlyViewedItems) < 0) {
_app.ext.quickstart.vars.session.recentlyViewedItems.unshift(infoObj.pid);
}
else {
// ** 201332 indexOf changed to $.inArray for IE8 compatibility, since IE8 only supports the indexOf method on Strings
//the item is already in the list. move it to the front.
_app.ext.quickstart.vars.session.recentlyViewedItems.splice(0, 0, _app.ext.quickstart.vars.session.recentlyViewedItems.splice($.inArray(infoObj.pid, _app.ext.quickstart.vars.session.recentlyViewedItems), 1)[0]);
}
$new = _app.ext.quickstart.u.showProd(infoObj);
break;
case 'homepage':
infoObj.pageType = 'homepage';
infoObj.navcat = zGlobals.appSettings.rootcat;
$new = _app.ext.quickstart.u.showPage(infoObj);
break;
case 'category':
//add item to recently viewed list IF it is not already the most recent in the list.
//Originally, used: if($.inArray(infoObj.navcat,_app.ext.quickstart.vars.session.recentCategories) < 0)
//bad mojo because spot 0 in array isn't necessarily the most recently viewed category, which it should be.
if(_app.ext.quickstart.vars.session.recentCategories[0] != infoObj.navcat) {
_app.ext.quickstart.vars.session.recentCategories.unshift(infoObj.navcat);
}
$new = _app.ext.quickstart.u.showPage(infoObj); //### look into having showPage return infoObj instead of just parentID.
break;
case 'search':
// dump(" -> Got to search case.");
$new = _app.ext.quickstart.u.showSearch(infoObj);
break;
case 'customer':
// if('file:' == document.location.protocol || !_app.ext.quickstart.u.thisArticleRequiresLogin(infoObj) || 'https:' == document.location.protocol) {
//201404 -> change in logic so that secure or file always hit before checking if authentication is required. reduces overhead.
if('https:' == document.location.protocol || 'file:' == document.location.protocol || !_app.ext.quickstart.u.thisArticleRequiresLogin(infoObj)) {