You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
9.5 KiB

  1. import Common;
  2. import js.JQuery;
  3. /**
  4. * JS Shopping Cart
  5. *
  6. * @author fbarbut<francois.barbut@gmail.com>
  7. */
  8. class ShopCart
  9. {
  10. public var products : Map<Int,ProductInfo>; //product db
  11. public var productsArray : Array<ProductInfo>; //to keep order of products
  12. public var categories : Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>; //categ db
  13. public var pinnedCategories : Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>; //categ db
  14. public var order : OrderInSession;
  15. var loader : JQuery; //ajax loader gif
  16. //for scroll mgmt
  17. var cartTop : Int;
  18. var cartLeft : Int;
  19. var cartWidth : Int;
  20. var jWindow : JQuery;
  21. var cartContainer : JQuery;
  22. var date : String;
  23. var place : Int;
  24. public function new()
  25. {
  26. products = new Map();
  27. productsArray = [];
  28. order = cast { products:[] };
  29. categories = [];
  30. pinnedCategories = [];
  31. }
  32. public function add(pid:Int) {
  33. loader.show();
  34. var q = App.j('#productQt' + pid).val();
  35. var qt = 0.0;
  36. var p = this.products.get(pid);
  37. if (p.hasFloatQt) {
  38. q = StringTools.replace(q, ",", ".");
  39. qt = Std.parseFloat(q);
  40. }else {
  41. qt = Std.parseInt(q);
  42. }
  43. if (qt == null) {
  44. qt = 1;
  45. }
  46. //trace("qté : "+qt);
  47. //add server side
  48. var r = new haxe.Http('/shop/add/$pid/$qt');
  49. r.onData = function(data:String) {
  50. loader.hide();
  51. var d = haxe.Json.parse(data);
  52. if (!d.success) js.Browser.alert("Erreur : "+d);
  53. //add locally
  54. subAdd(pid, qt);
  55. render();
  56. }
  57. r.request();
  58. }
  59. function subAdd(pid, qt:Float ) {
  60. for ( p in order.products) {
  61. if (p.productId == pid) {
  62. p.quantity += qt;
  63. render();
  64. return;
  65. }
  66. }
  67. order.products.push( { productId:pid, quantity:qt } );
  68. }
  69. /**
  70. * Render the shopping cart and total
  71. */
  72. function render() {
  73. var c = App.j("#cart");
  74. c.empty();
  75. //render items in shopping cart
  76. c.append( Lambda.map(order.products, function( x ) {
  77. var p = this.products.get(x.productId);
  78. if (p == null) {
  79. //the product may have been disabled by an admin
  80. return "";
  81. }
  82. var btn = "<a onClick='cart.remove(" + p.id + ")' class='btn btn-default btn-xs' data-toggle='tooltip' data-placement='top' title='Retirer de la commande'><span class='glyphicon glyphicon-remove'></span></a>&nbsp;";
  83. return "<div class='row'>
  84. <div class = 'order col-md-9' > <b> " + x.quantity + " </b> x " + p.name+" </div>
  85. <div class = 'col-md-3'> "+btn+"</div>
  86. </div>";
  87. }).join("\n") );
  88. //compute total price
  89. var total = 0.0;
  90. for (p in order.products) {
  91. var pinfo = products.get(p.productId);
  92. if (pinfo == null) continue;
  93. total += p.quantity * pinfo.price;
  94. }
  95. var ffilter = new sugoi.form.filters.FloatFilter();
  96. var total = ffilter.filterString(Std.string(App.roundTo(total,2)));
  97. c.append("<div class='total'>TOTAL : " + total + "</div>");
  98. if (order.products.length > 0){
  99. App.instance.setWarningOnUnload(true,"Vous avez une commande en cours. Si vous quittez cette page sans confirmer, votre commande sera perdue.");
  100. }else{
  101. App.instance.setWarningOnUnload(false);
  102. }
  103. }
  104. function findCategoryName(cid:Int):String{
  105. for ( cg in this.categories ){
  106. for (c in cg.categs){
  107. if (cid == c.id) {
  108. return c.name;
  109. }
  110. }
  111. }
  112. for ( cg in this.pinnedCategories ){
  113. for (c in cg.categs){
  114. if (cid == c.id) {
  115. return c.name;
  116. }
  117. }
  118. }
  119. return null;
  120. }
  121. /**
  122. * Dynamically sort products by categories
  123. */
  124. public function sortProductsBy(){
  125. //store products by groups
  126. var groups = new Map<Int,{name:String,products:Array<ProductInfo>}>();
  127. var pinned = new Map<Int,{name:String,products:Array<ProductInfo>}>();
  128. var firstCategGroup = this.categories[0].categs;
  129. //trace(firstCategGroup);
  130. //trace(pinnedCategories);
  131. var pList = this.productsArray.copy();
  132. //for ( p in pList) trace(p.name+" : " + p.categories);
  133. //trace("----------------");
  134. //sort by categs
  135. for ( p in pList.copy() ){
  136. //trace(p.name+" : " + p.categories);
  137. untyped p.element.remove();
  138. for ( categ in p.categories){
  139. if (Lambda.find(firstCategGroup, function(c) return c.id == categ) != null){
  140. //is in this category group
  141. var g = groups.get(categ);
  142. if ( g == null){
  143. var name = findCategoryName(categ);
  144. g = {name:name,products:[]};
  145. }
  146. g.products.push(p);
  147. //trace("remove " + p.name);
  148. pList.remove(p);
  149. groups.set(categ, g);
  150. }
  151. else{
  152. // is in pinned group ?
  153. var isInPinnedCateg = false;
  154. for ( cg in pinnedCategories){
  155. if (Lambda.find(cg.categs, function(c) return c.id == categ) != null){
  156. isInPinnedCateg = true;
  157. break;
  158. }
  159. }
  160. if (isInPinnedCateg){
  161. var c = pinned.get(categ);
  162. if ( c == null){
  163. var name = findCategoryName(categ);
  164. c = {name:name,products:[]};
  165. }
  166. c.products.push(p);
  167. //trace( "add " + p.name+" in PINNED");
  168. pList.remove(p);
  169. pinned.set(categ, c);
  170. }else{
  171. //not in the selected categ nor in pinned groups
  172. continue;
  173. }
  174. }
  175. }
  176. }
  177. //if some untagged products remain
  178. if (pList.length > 0){
  179. groups.set(0,{name:"Autres",products:pList});
  180. }
  181. //trace("----------------");
  182. //render
  183. var container = App.j(".shop .body");
  184. //render firts "pinned" groups , then "groups"
  185. for ( source in [pinned, groups]){
  186. for (o in source){
  187. if (o.products.length == 0) continue;
  188. container.append("<div class='col-md-12 col-xs-12 col-sm-12 col-lg-12'><div class='catHeader'>" + o.name + "</div></div>");
  189. for ( p in o.products){
  190. //trace("GROUP "+o.name+" : "+p.name);
  191. //if the element has already been inserted, we need to clone it
  192. if (untyped p.element.parent().length == 0){
  193. container.append( untyped p.element );
  194. }else{
  195. var clone = untyped p.element.clone();
  196. container.append( clone );
  197. }
  198. }
  199. }
  200. }
  201. App.j(".product").show();
  202. }
  203. /**
  204. * is shopping cart empty ?
  205. */
  206. public function isEmpty(){
  207. return order.products.length == 0;
  208. }
  209. /**
  210. * submit cart
  211. */
  212. public function submit() {
  213. var req = new haxe.Http("/shop/submit");
  214. req.onData = function(d) {
  215. App.instance.setWarningOnUnload(false);
  216. js.Browser.location.href = "/shop/validate/"+place+"/"+date;
  217. }
  218. req.addParameter("data", haxe.Json.stringify(order));
  219. req.request(true);
  220. }
  221. /**
  222. * filter products by category
  223. */
  224. public function filter(cat:Int) {
  225. //icone sur bouton
  226. App.j(".tag").removeClass("active").children().remove("span");//clean
  227. var bt = App.j("#tag" + cat);
  228. bt.addClass("active").prepend("<span class ='glyphicon glyphicon-ok'></span> ");
  229. //affiche/masque produits
  230. for (p in products) {
  231. if (cat==0 || Lambda.has(p.categories, cat)) {
  232. App.j(".shop .product" + p.id).fadeIn(300);
  233. }else {
  234. App.j(".shop .product" + p.id).fadeOut(300);
  235. }
  236. }
  237. }
  238. /**
  239. * remove a product from cart
  240. * @param pid
  241. */
  242. public function remove(pid:Int ) {
  243. loader.show();
  244. //add server side
  245. var r = new haxe.Http('/shop/remove/$pid');
  246. r.onData = function(data:String) {
  247. loader.hide();
  248. var d = haxe.Json.parse(data);
  249. if (!d.success) js.Browser.alert("Erreur : "+d);
  250. //remove locally
  251. for ( p in order.products.copy()) {
  252. if (p.productId == pid) {
  253. order.products.remove(p);
  254. render();
  255. return;
  256. }
  257. }
  258. render();
  259. }
  260. r.request();
  261. }
  262. /**
  263. * loads products DB and existing cart in ajax
  264. */
  265. public function init(place:Int,date:String) {
  266. this.place = place;
  267. this.date = date;
  268. loader = App.j("#cartContainer #loader");
  269. var req = new haxe.Http("/shop/init/"+place+"/"+date);
  270. req.onData = function(data) {
  271. loader.hide();
  272. var data : {
  273. products:Array<ProductInfo>,
  274. categories:Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>,
  275. order:OrderInSession } = haxe.Unserializer.run(data);
  276. //populate local categories lists
  277. for ( cg in data.categories){
  278. if (cg.pinned){
  279. pinnedCategories.push(cg);
  280. }else{
  281. categories.push(cg);
  282. }
  283. }
  284. //product DB
  285. for (p in data.products) {
  286. //catch dom element for further usage
  287. untyped p.element = App.j(".product"+p.id);
  288. var id : Int = p.id;
  289. //var id : Int = p.id;
  290. //id = id + 1;
  291. this.products.set(id, p);
  292. this.productsArray.push(p);
  293. //trace(p.name+" : " + p.categories);
  294. }
  295. //existing order
  296. for ( p in data.order.products) {
  297. subAdd(p.productId,p.quantity );
  298. }
  299. render();
  300. sortProductsBy();
  301. }
  302. req.request();
  303. //DISABLED : pb quand le panier est plus haut que l'ecran
  304. //scroll mgmt, only for large screens. Otherwise let the cart on page bottom.
  305. /*if (js.Browser.window.matchMedia("(min-width: 1024px)").matches) {
  306. jWindow = App.j(js.Browser.window);
  307. cartContainer = App.j("#cartContainer");
  308. cartTop = cartContainer.position().top;
  309. cartLeft = cartContainer.position().left;
  310. cartWidth = cartContainer.width();
  311. jWindow.scroll(onScroll);
  312. }*/
  313. }
  314. /**
  315. * keep the cart on top when scrolling
  316. * @param e
  317. */
  318. public function onScroll(e:Dynamic) {
  319. //cart container top position
  320. if (jWindow.scrollTop() > cartTop) {
  321. //trace("absolute !");
  322. cartContainer.addClass("scrolled");
  323. cartContainer.css('left', Std.string(cartLeft) + "px");
  324. cartContainer.css('top', Std.string(/*cartTop*/10) + "px");
  325. cartContainer.css('width', Std.string(cartWidth) + "px");
  326. }else {
  327. cartContainer.removeClass("scrolled");
  328. cartContainer.css('left',"");
  329. cartContainer.css('top', "");
  330. cartContainer.css('width', "");
  331. }
  332. }
  333. }