PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/cart_implementations/backbone/scripts/backbone-uc-common-functions-1.3.js

https://github.com/reengo/responsive_checkout
JavaScript | 486 lines | 321 code | 85 blank | 80 comment | 36 complexity | e0469a5480374cf4dc8516b20c5d766a MD5 | raw file
  1. if (typeof String.prototype.trim === 'undefined') {
  2. String.prototype.trim = function() {
  3. return this.replace(/^\s+|\s+$/g, '');
  4. };
  5. }
  6. if (typeof String.prototype.startsWith === 'undefined') {
  7. String.prototype.startsWith = function(str) {
  8. return (this.indexOf(str) === 0);
  9. };
  10. }
  11. if (typeof String.prototype.endsWith === 'undefined') {
  12. String.prototype.endsWith = function(str) {
  13. return (this.length - str.length) == this.lastIndexOf(str);
  14. }
  15. }
  16. // =================================================
  17. // --- We make 'uc' our global space.
  18. // =================================================
  19. var uc = {
  20. models: {},
  21. collections: {},
  22. commonFunctions: {},
  23. views: {}
  24. };
  25. // this is a model which will reside within a nested collection. it should not perform client/server operations.
  26. uc.models.NestedModel = Backbone.Model.extend({
  27. 'fetch': function() {
  28. throw "This model is a nested Model. fetch() should not be called. The top level object will handle fetching.";
  29. },
  30. 'save': function() {
  31. throw "This model is a nested Model. save() should not be called. the top level object will will handle persistence.";
  32. }
  33. });
  34. // notice that Cart is a DeepModel (https://github.com/powmedia/backbone-deep-model)
  35. // notice that Cart uses nestCollection for the items array
  36. uc.models.DeepAndNestedModel = Backbone.DeepModel.extend({
  37. 'nested': [],
  38. 'initialize': function() {
  39. // create a top level property that points to each nested collection. This top level property will be the means
  40. // of tying views to the nested collection.
  41. var thisModel = this;
  42. _.each(this.nested, function(nest) {
  43. thisModel[nest.attributeName] = nest.collection.link(thisModel, nest.attributeName);
  44. });
  45. // var itemCollection = new app.collections.Items();
  46. // this.items = itemCollection.link(this, 'items');
  47. },
  48. clear: function(options) {
  49. var thisModel = this;
  50. _.each(this.nested, function(nest) {
  51. thisModel[nest.attributeName].reset();
  52. });
  53. (options || (options = {})).unset = true;
  54. return this.set(_.clone(this.attributes), options);
  55. },
  56. set: function(attributes, options) {
  57. // 1. do not allow deep-model sets to nested attributes. that will mess things up.
  58. // 2. after setting attributes, scan for nested ones and call reset on the nested collection so it will update.
  59. // deep model will try to handle deep linking, but we need to intercept that and disallow that. Implementations
  60. // should be setting the entire attribute, not deep linking. If a deep property needs to be changed, that should
  61. // be done through the nested collection.
  62. var that = this;
  63. _.each(this.nested, function(nest) {
  64. if (_.any(_.keys(attributes), function(attributeName) {
  65. return attributeName.startsWith(nest.attributeName + '.');
  66. })) {
  67. throw nest.attributeName + " must not be updated via deep-model sets(). access it through its collection interface.";
  68. }
  69. });
  70. // should this be Backbone.Model.prototype or Backbone.DeepModel.prototype??
  71. var result = Backbone.DeepModel.prototype.set.call(this, attributes, options);
  72. _.each(this.nested, function(nest) {
  73. // if a nested collection is part of the 'set' data, reset the nested collection
  74. if (attributes[nest.attributeName]) {
  75. var nestedList = attributes[nest.attributeName];
  76. that[nest.attributeName].reset(that, nest.attributeName, nestedList);
  77. }
  78. });
  79. return result;
  80. }
  81. });
  82. uc.collections.NestedCollection = Backbone.Collection.extend({
  83. 'fetch': function() {
  84. throw "This collection is a NestedCollection. fetch() should not be called. The parent item will handle fetching.";
  85. },
  86. 'save': function() {
  87. throw "This collection is a NestedCollection. save() should not be called. The parent item will handle persistence.";
  88. },
  89. // this is very similar to the reset function found in the backbone source, however, this function accepts
  90. // an array of *data*, not *models*. The models are created here and passed to the add function. This is done
  91. // so I could call reset and pass it my parent collection's property, which is an array of normal objects, not models.
  92. 'reset': function(parentModel, attributeName, options) {
  93. options || (options = {});
  94. if (parentModel) {
  95. // init the attribute if it doesn't exist yet. if it doesn't, why is reset being called??
  96. if (!parentModel.get(attributeName)) {
  97. parentModel.attributes[attributeName] = [];
  98. }
  99. var models = [];
  100. for (var j = 0, len = parentModel.attributes[attributeName].length; j < len; j++) {
  101. // copy the values
  102. var model = new this.model(parentModel.attributes[attributeName][j]);
  103. // now create linking to the newly copied values.
  104. parentModel.attributes[attributeName][j] = model.attributes;
  105. models.push(model);
  106. }
  107. this._reset();
  108. this.add(models, _.extend({silent: true}, options));
  109. } else {
  110. this._reset();
  111. }
  112. if (!options.silent) this.trigger('reset', this, options);
  113. return this;
  114. },
  115. // We took this code from a comment here: https://gist.github.com/1610397
  116. // It's purpose is to handle nested collections gracefully.
  117. 'link': function (parentModel, attributeName) {
  118. // init the attribute if it doesn't exist yet.
  119. if (!parentModel.get(attributeName)) {
  120. parentModel.attributes[attributeName] = [];
  121. }
  122. //setup nested references
  123. for (var i = 0; i < this.length; i++) {
  124. parentModel.attributes[attributeName][i] = this.at(i).attributes;
  125. }
  126. this.on('add', function (theAddedModel) {
  127. // if the parent model doesn't have the attribute yet, add it with a default of an empty array.
  128. if (!parentModel.get(attributeName)) {
  129. parentModel.attributes[attributeName] = [];
  130. }
  131. // add a placeholder to the parent's array, which I then overwrite with a link to this model's attributes.
  132. parentModel.get(attributeName).push({});
  133. // link the newly added array element to the new added model attributes.
  134. var idx = parentModel.get(attributeName).length - 1;
  135. parentModel.attributes[attributeName][idx] = theAddedModel.attributes;
  136. });
  137. // when a value is deleted from the nested collection, update the parent array.
  138. // this is done by filtering out the object with the matching id, leaving us with all the rest.
  139. this.on('remove', function (theDeletedModel) {
  140. var updateObj = {};
  141. var sourceArray = parentModel.get(attributeName);
  142. updateObj[attributeName] = _.filter(sourceArray, function(sourceObject) {
  143. return sourceObject[theDeletedModel.idAttribute] != theDeletedModel.id;
  144. });
  145. parentModel.set(updateObj);
  146. });
  147. return this;
  148. }
  149. });
  150. uc.collections.PagedCollection = Backbone.Collection.extend({
  151. pageSize: 0,
  152. pageNumber: 0,
  153. totalPages: 0,
  154. totalRecords: 0,
  155. queryParameters: {},
  156. paginationParameters: {'pageSize': 'pageSize', 'pageNumber':'pageNumber'},
  157. paginationHeaders: {'pageSize': 'uc-pagination-page-size', 'pageNumber':'uc-pagination-page-number', 'totalPages':'uc-pagination-total-pages', 'totalRecords': 'uc-pagination-total-records'},
  158. 'initialize': function() {
  159. var _url = this.url;
  160. this.url = function() {
  161. var pagedUrl = _.isFunction(_url) ? _url() : _url;
  162. if (this.queryParameters) {
  163. pagedUrl += '?' + jQuery.param(this.queryParameters);
  164. }
  165. return pagedUrl;
  166. }
  167. },
  168. 'parse': function(resp, xhr) {
  169. // check for the 3 pagination headers.
  170. var pageSize = parseInt(xhr.getResponseHeader(this.paginationHeaders['pageSize']), 10);
  171. if (isNaN(pageSize)) {
  172. pageSize = 0;
  173. }
  174. this.pageSize = pageSize;
  175. var pageNumber = parseInt(xhr.getResponseHeader(this.paginationHeaders['pageNumber']), 10);
  176. if (isNaN(pageNumber)) {
  177. pageNumber = 0;
  178. }
  179. this.pageNumber = pageNumber;
  180. var totalPages = parseInt(xhr.getResponseHeader(this.paginationHeaders['totalPages']), 10);
  181. if (isNaN(totalPages)) {
  182. totalPages = 0;
  183. }
  184. this.totalPages = totalPages;
  185. var totalRecords = parseInt(xhr.getResponseHeader(this.paginationHeaders['totalRecords']), 10);
  186. if (isNaN(totalRecords)) {
  187. totalRecords = 0;
  188. }
  189. this.totalRecords = totalRecords;
  190. return resp; // this single line is the default behavior. everything above is custom page code.
  191. },
  192. 'hasNext': function() {
  193. return this.totalPages && this.pageNumber && this.pageNumber < this.totalPages;
  194. },
  195. 'hasPrev': function() {
  196. return this.pageNumber && this.pageNumber > 1;
  197. },
  198. 'nextPage': function() {
  199. this.queryParameters[this.paginationParameters['pageNumber']] = this.pageNumber + 1;
  200. this.fetch();
  201. },
  202. 'prevPage': function() {
  203. this.queryParameters[this.paginationParameters['pageNumber']] = this.pageNumber - 1;
  204. this.fetch();
  205. },
  206. 'gotoPage': function(pageNo) {
  207. this.queryParameters[this.paginationParameters['pageNumber']] = pageNo;
  208. this.fetch();
  209. }
  210. });
  211. /**
  212. * this method creates/returns a method. The returned method is hardcoded to do content switching on a particular panel
  213. * in the application. The showView(view) method will be used often throughout this application to ensure that when
  214. * content is being updated, the old content is unbound from any event handlers to avoid zombie events. Think of it
  215. * like a programming 'destructor' that frees up event binding.
  216. * @param pane an html element (using a div) id that is used to update content.
  217. */
  218. //TODO - this should not be a global function.
  219. function createAppView(pane) {
  220. // notice! the return value is a new function that is hardwired to operate on whatever element id is passed in.
  221. return new function() {
  222. var that = {};
  223. that.showView = function(view, modal, title, width) {
  224. if (this.currentView) {
  225. this.currentView.close();
  226. }
  227. this.currentView = view;
  228. this.currentView.render();
  229. if (modal) {
  230. ucLoadPopup2(
  231. {container:'modalAppView',
  232. title: title,
  233. css: {'width': width},
  234. content: this.currentView.el,
  235. alwaysNew: true
  236. });
  237. } else {
  238. jQuery("#" + pane).html(this.currentView.el);
  239. }
  240. };
  241. that.clearView = function() {
  242. if (this.currentView) {
  243. this.currentView.close();
  244. }
  245. jQuery("#" + pane).html('');
  246. };
  247. return that;
  248. };
  249. }
  250. // add a close method to the view that does remove AND unbind. unbind only does dom.
  251. // this is required for the AppView methods above to work, since they call this close() method to clean up bound events
  252. Backbone.View.prototype.close = function() {
  253. this.remove();
  254. this.unbind();
  255. // we'll also need to unbind from the model or collection, but that must be done individually.
  256. // so if the view has 'onClose' stubbed, call that too.
  257. if (this.onClose) {
  258. this.onClose();
  259. }
  260. };
  261. // ---------------------------------------------------------------------
  262. // --- common functions
  263. // ---------------------------------------------------------------------
  264. uc.commonFunctions.setTabIndexes = function() {
  265. jQuery(':enabled:visible').each(function(i, e) {
  266. jQuery(e).attr('tabindex', i);
  267. });
  268. };
  269. uc.commonFunctions.startPleaseWait = function(msg) {
  270. var html = "<div class='spaced-div center'>Please be patient. This operation takes a long time.<br/><span class='ucPleaseWaitMsg'>"
  271. + (msg ? msg : "The page will refresh when finished.")
  272. + "</span><br /><img src='/js/jquery.smallhbar.indicator.gif' class='ajaxBusy' alt='busy, please wait'/></div>";
  273. var title = 'Please Wait';
  274. ucLoadPopup2(
  275. {container:'pleaseWaitDiv',
  276. title: title,
  277. css: {'width': '400px'},
  278. content: html,
  279. alwaysNew: true
  280. });
  281. };
  282. uc.commonFunctions.endPleaseWait = function() {
  283. ucDisablePopup2('pleaseWaitDiv');
  284. };
  285. /**
  286. * This is a really useful function for extracting an oid from an html element id. For example, if you have a lot
  287. * of with this kind of id: 'checkbox{{oid}}', then this method can return that oid.
  288. * So, an id of 'salesCb1024' will return 1024
  289. * @param id the full html element id
  290. * @param prefix the prefix of characters before the oid
  291. */
  292. uc.commonFunctions.parseOidFromId = function(id, prefix) {
  293. return id.substring(prefix.length);
  294. };
  295. /**
  296. * returns an array of items based on the supplied item ids
  297. * @param ids
  298. * @return an array of itemVO
  299. */
  300. uc.commonFunctions.getItemsById = function(ids, getCompleteItem) {
  301. var itemStr = (typeof ids === 'string' ? ids : ids.join(','));
  302. var filter = getCompleteItem ? '' : '&_detail=description';
  303. var queryString = 'id=' + encodeURIComponent(itemStr) + filter;
  304. var response = [];
  305. jQuery.ajax({
  306. url: '/rest/merchant/items',
  307. data: queryString,
  308. type: 'GET',
  309. async: false,
  310. dataType: 'json'
  311. }).done(function(data) {
  312. response = data;
  313. });
  314. return response;
  315. };
  316. /**
  317. * returns a hash!!
  318. * @param oids an array of merchant item oids
  319. * @return a hash of (itemId:itemVo)
  320. */
  321. uc.commonFunctions.getItemsByOid = function(oids) {
  322. var queryString = '_detail=description&oid=' + encodeURIComponent(oids.join(','));
  323. var response = {};
  324. jQuery.ajax({
  325. url: '/rest/merchant/items',
  326. data: queryString,
  327. type: 'GET',
  328. async: false,
  329. dataType: 'json'
  330. }).done(function(data) {
  331. _.each(data, function(item) {
  332. response[item.merchantItemOid] = item;
  333. });
  334. });
  335. return response;
  336. };
  337. /**
  338. * retrieves a customer or null if not found, and passes it to the callback.
  339. * if callback is null, the method runs synchronous and returns back the customer.
  340. * @param id either the customer oid or email address. either works
  341. * @param callback function that accepts the customer object
  342. * @return nothing
  343. */
  344. uc.commonFunctions.getCustomer = function(id, callback) {
  345. var response = null;
  346. jQuery.ajax({
  347. url: '/rest/merchant/customers/' + id,
  348. type: 'GET',
  349. async: (callback ? true : false),
  350. dataType: 'json'
  351. }).done(
  352. function(data) {
  353. if (callback) {
  354. callback(data);
  355. } else {
  356. response = data;
  357. }
  358. }).fail(function() {
  359. if (callback) {
  360. callback(null);
  361. }
  362. });
  363. return response;
  364. };
  365. /**
  366. *
  367. * constructor variables needed:
  368. * template: variable of an already compiled handlebars template
  369. * context: any context variables
  370. * className: class for the top level div.
  371. */
  372. uc.views.GenericBoundView = Backbone.View.extend({
  373. _modelBinder:undefined,
  374. events: {
  375. "focus input[type=text]": "selectText"
  376. },
  377. 'onClose': function() {
  378. this._modelBinder.unbind();
  379. },
  380. initialize: function() {
  381. this._modelBinder = new Backbone.ModelBinder();
  382. _.bindAll(this);
  383. if (this.options.functions) {
  384. _.extend(this, this.options.functions);
  385. }
  386. if (this.options.events) {
  387. this.delegateEvents(this.options.events);
  388. }
  389. },
  390. render: function() {
  391. this.$el.html(this.options.template(this.options.context));
  392. this._modelBinder.bind(this.model, this.el);
  393. if (this.options.clazz) {
  394. this.$el.addClass(this.options.clazz);
  395. }
  396. return this;
  397. },
  398. selectText: function(event) {
  399. jQuery(event.target).select();
  400. }
  401. });