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.

451 lines
9.7 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. trace(this.categories);
  129. var firstCategGroup = null;
  130. if (this.categories.length > 0){
  131. firstCategGroup = this.categories[0].categs;
  132. }
  133. //trace(firstCategGroup);
  134. //trace(pinnedCategories);
  135. var pList = this.productsArray.copy();
  136. //for ( p in pList) trace(p.name+" : " + p.categories);
  137. //trace("----------------");
  138. //sort by categs
  139. for ( p in pList.copy() ){
  140. //trace(p.name+" : " + p.categories);
  141. untyped p.element.remove();
  142. for ( categ in p.categories){
  143. if (firstCategGroup != null && Lambda.find(firstCategGroup, function(c) return c.id == categ) != null){
  144. //is in this category group
  145. var g = groups.get(categ);
  146. if ( g == null){
  147. var name = findCategoryName(categ);
  148. g = {name:name,products:[]};
  149. }
  150. g.products.push(p);
  151. //trace("remove " + p.name);
  152. pList.remove(p);
  153. groups.set(categ, g);
  154. }
  155. else{
  156. // is in pinned group ?
  157. var isInPinnedCateg = false;
  158. for ( cg in pinnedCategories){
  159. if (Lambda.find(cg.categs, function(c) return c.id == categ) != null){
  160. isInPinnedCateg = true;
  161. break;
  162. }
  163. }
  164. if (isInPinnedCateg){
  165. var c = pinned.get(categ);
  166. if ( c == null){
  167. var name = findCategoryName(categ);
  168. c = {name:name,products:[]};
  169. }
  170. c.products.push(p);
  171. //trace( "add " + p.name+" in PINNED");
  172. pList.remove(p);
  173. pinned.set(categ, c);
  174. }else{
  175. //not in the selected categ nor in pinned groups
  176. continue;
  177. }
  178. }
  179. }
  180. }
  181. //if some untagged products remain
  182. if (pList.length > 0){
  183. groups.set(0,{name:"Autres",products:pList});
  184. }
  185. //trace("----------------");
  186. //render
  187. var container = App.j(".shop .body");
  188. //render firts "pinned" groups , then "groups"
  189. for ( source in [pinned, groups]){
  190. for (o in source){
  191. if (o.products.length == 0) continue;
  192. container.append("<div class='col-md-12 col-xs-12 col-sm-12 col-lg-12'><div class='catHeader'>" + o.name + "</div></div>");
  193. for ( p in o.products){
  194. //trace("GROUP "+o.name+" : "+p.name);
  195. //if the element has already been inserted, we need to clone it
  196. if (untyped p.element.parent().length == 0){
  197. container.append( untyped p.element );
  198. }else{
  199. var clone = untyped p.element.clone();
  200. container.append( clone );
  201. }
  202. }
  203. }
  204. }
  205. App.j(".product").show();
  206. }
  207. /**
  208. * is shopping cart empty ?
  209. */
  210. public function isEmpty(){
  211. return order.products.length == 0;
  212. }
  213. /**
  214. * submit cart
  215. */
  216. public function submit() {
  217. var req = new haxe.Http("/shop/submit");
  218. req.onData = function(d) {
  219. App.instance.setWarningOnUnload(false);
  220. js.Browser.location.href = "/shop/validate/"+place+"/"+date;
  221. }
  222. req.addParameter("data", haxe.Json.stringify(order));
  223. req.request(true);
  224. }
  225. /**
  226. * filter products by category
  227. */
  228. public function filter(cat:Int) {
  229. //icone sur bouton
  230. App.j(".tag").removeClass("active").children().remove("span");//clean
  231. var bt = App.j("#tag" + cat);
  232. bt.addClass("active").prepend("<span class ='glyphicon glyphicon-ok'></span> ");
  233. //affiche/masque produits
  234. for (p in products) {
  235. if (cat==0 || Lambda.has(p.categories, cat)) {
  236. App.j(".shop .product" + p.id).fadeIn(300);
  237. }else {
  238. App.j(".shop .product" + p.id).fadeOut(300);
  239. }
  240. }
  241. }
  242. /**
  243. * remove a product from cart
  244. * @param pid
  245. */
  246. public function remove(pid:Int ) {
  247. loader.show();
  248. //add server side
  249. var r = new haxe.Http('/shop/remove/$pid');
  250. r.onData = function(data:String) {
  251. loader.hide();
  252. var d = haxe.Json.parse(data);
  253. if (!d.success) js.Browser.alert("Erreur : "+d);
  254. //remove locally
  255. for ( p in order.products.copy()) {
  256. if (p.productId == pid) {
  257. order.products.remove(p);
  258. render();
  259. return;
  260. }
  261. }
  262. render();
  263. }
  264. r.request();
  265. }
  266. /**
  267. * loads products DB and existing cart in ajax
  268. */
  269. public function init(place:Int,date:String) {
  270. this.place = place;
  271. this.date = date;
  272. loader = App.j("#cartContainer #loader");
  273. var req = new haxe.Http("/shop/init/"+place+"/"+date);
  274. req.onData = function(data) {
  275. loader.hide();
  276. var data : {
  277. products:Array<ProductInfo>,
  278. categories:Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>,
  279. order:OrderInSession } = haxe.Unserializer.run(data);
  280. //populate local categories lists
  281. for ( cg in data.categories){
  282. if (cg.pinned){
  283. pinnedCategories.push(cg);
  284. }else{
  285. categories.push(cg);
  286. }
  287. }
  288. //product DB
  289. for (p in data.products) {
  290. //catch dom element for further usage
  291. untyped p.element = App.j(".product"+p.id);
  292. var id : Int = p.id;
  293. //var id : Int = p.id;
  294. //id = id + 1;
  295. this.products.set(id, p);
  296. this.productsArray.push(p);
  297. //trace(p.name+" : " + p.categories);
  298. }
  299. //existing order
  300. for ( p in data.order.products) {
  301. subAdd(p.productId,p.quantity );
  302. }
  303. render();
  304. sortProductsBy();
  305. }
  306. req.request();
  307. //DISABLED : pb quand le panier est plus haut que l'ecran
  308. //scroll mgmt, only for large screens. Otherwise let the cart on page bottom.
  309. /*if (js.Browser.window.matchMedia("(min-width: 1024px)").matches) {
  310. jWindow = App.j(js.Browser.window);
  311. cartContainer = App.j("#cartContainer");
  312. cartTop = cartContainer.position().top;
  313. cartLeft = cartContainer.position().left;
  314. cartWidth = cartContainer.width();
  315. jWindow.scroll(onScroll);
  316. }*/
  317. }
  318. /**
  319. * keep the cart on top when scrolling
  320. * @param e
  321. */
  322. public function onScroll(e:Dynamic) {
  323. //cart container top position
  324. if (jWindow.scrollTop() > cartTop) {
  325. //trace("absolute !");
  326. cartContainer.addClass("scrolled");
  327. cartContainer.css('left', Std.string(cartLeft) + "px");
  328. cartContainer.css('top', Std.string(/*cartTop*/10) + "px");
  329. cartContainer.css('width', Std.string(cartWidth) + "px");
  330. }else {
  331. cartContainer.removeClass("scrolled");
  332. cartContainer.css('left',"");
  333. cartContainer.css('top', "");
  334. cartContainer.css('width', "");
  335. }
  336. }
  337. }