PageRenderTime 26ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/MS Silk/Scripts/Debug/mstats.vehicle-list.js

#
JavaScript | 528 lines | 366 code | 82 blank | 80 comment | 28 complexity | c92bee47e279cd2de881785755ee2ff8 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0
  1. //===================================================================================
  2. // Microsoft patterns & practices
  3. // Silk : Web Client Guidance
  4. //===================================================================================
  5. // Copyright (c) Microsoft Corporation. All rights reserved.
  6. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
  7. // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
  8. // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  9. // FITNESS FOR A PARTICULAR PURPOSE.
  10. //===================================================================================
  11. // The example companies, organizations, products, domain names,
  12. // e-mail addresses, logos, people, places, and events depicted
  13. // herein are fictitious. No association with any real company,
  14. // organization, product, domain name, email address, logo, person,
  15. // places, or events is intended or should be inferred.
  16. //===================================================================================
  17. /*jslint onevar: true, undef: true, newcap: true, regexp: true,
  18. plusplus: true, bitwise: true, devel: true, maxerr: 50 */
  19. /*global window: true, jQuery:true */
  20. (function (mstats, $) {
  21. var animationLength = 600,
  22. delayLength = 400,
  23. dataUrl;
  24. $.widget('mstats.vehicleList', {
  25. // default options
  26. options: {
  27. // Default to $.ajax when sendRequest is undefined.
  28. // The extra function indirection allows $.ajax substitution because
  29. // the widget framework is using the real $.ajax during options
  30. // initialization.
  31. sendRequest: function (ajaxOptions) { $.ajax(ajaxOptions); },
  32. // invalidateData allows the vehicleList to invalidate data
  33. // stored in the data cache.
  34. invalidateData: function () {
  35. mstats.log('no data invalidation function provided.');
  36. },
  37. // option controlling the compact state of the widget.
  38. layout: 'dashboard',
  39. // option providing the event publisher used for communicating with
  40. // the status widget.
  41. publish: function () { mstats.log('The publish option on vehicleList has not been set'); },
  42. // option for the selectedVehicleId
  43. selectedVehicleId: 0,
  44. visible: true,
  45. isCollapsed: false
  46. },
  47. // Creates the widget, taking over the UI contained in it, the links in it,
  48. // and adding the necessary behaviors.
  49. _create: function () {
  50. dataUrl = this.element.data('list-url');
  51. this._widgetizeVehicleTiles();
  52. this._bindEventHandlers();
  53. this._makeSortable();
  54. this._getVehicleData();
  55. // ensure that setOption is called with the provided options
  56. // since the widget framework does not do this.
  57. this._setOptions(this.options);
  58. if (this.options.layout !== 'dashboard') {
  59. this._setOption('isCollapsed', true);
  60. this._checkSelectedVehicleId();
  61. }
  62. },
  63. // Bind to necessary events within the vehicle list
  64. _bindEventHandlers: function () {
  65. var that = this;
  66. this.element.delegate('[data-action]', 'click.' + this.name, function () {
  67. that._setOption('selectedVehicleId', $(this).closest('.vehicle').data('vehicle-id'));
  68. // intentionally NOT calling preventDefault so the adjusted (bbq) links will work
  69. });
  70. },
  71. // add the tile widget to each tile, and the vehicle widget to each vehicle
  72. _widgetizeVehicleTiles: function () {
  73. this.element
  74. .find('#vehicle-list-content > div').tile() // widgetize tiles
  75. .find('.vehicle').vehicle(); // widgetize vehicles
  76. },
  77. // Gets the vehicle list data (via the sendRequest option) from the dataUrl
  78. // option endpoint. This method also applies the data to the template
  79. // provided in option.templateId
  80. _getVehicleData: function () {
  81. var that = this,
  82. $template = $(this.options.templateId);
  83. if (!$template.length) {
  84. this._showVehicles();
  85. mstats.log('Cannot apply templates as there is no template defined.');
  86. return;
  87. }
  88. this.options.sendRequest({
  89. url: dataUrl,
  90. success: function (data) {
  91. var vehicleTemplate = $template.tmpl(data),
  92. buttonTemplate = $('#mstats-add-vehicle-button-template').tmpl();
  93. that.element.find('#vehicle-list-content')
  94. .html(vehicleTemplate)
  95. .append(buttonTemplate);
  96. that._widgetizeVehicleTiles();
  97. // ensure that setOption is called with the provided options
  98. // since the widget framework does not do this.
  99. that._setOptions(that.options);
  100. // now animate them into view
  101. that._showVehicles();
  102. },
  103. error: function () {
  104. that._hideVehicles();
  105. that._showErrorMessage();
  106. }
  107. });
  108. },
  109. _hideVehicles: function () {
  110. this.element.find(':mstats-tile').hide();
  111. },
  112. // Show the vehicles list
  113. _showVehicles: function () {
  114. // consider moving this to inside the tile widget
  115. this.element.find(':mstats-tile').show();
  116. },
  117. /********************************************************
  118. * Status Methods
  119. ********************************************************/
  120. _publishStatus: function (status) {
  121. this.options.publish(mstats.events.status, status);
  122. },
  123. // hide the vehicles list and show the status widget in the error state
  124. _showErrorMessage: function () {
  125. this._publishStatus({
  126. type: 'loadError',
  127. message: 'An error occurred while loading the requested data. ' +
  128. 'Please try again.',
  129. duration: 10000
  130. });
  131. },
  132. // show the status widget in the saving state
  133. _showSaveMessage: function () {
  134. this._publishStatus({
  135. type: 'saving',
  136. message: 'Saving sort order ...'
  137. });
  138. },
  139. // show the status widget in the saved state
  140. _showSavedMessage: function () {
  141. this._publishStatus({
  142. type: 'saved',
  143. message: 'Sort order saved',
  144. duration: 5000
  145. });
  146. },
  147. // show the status widget in the error state after a failed save
  148. _showSaveErrorMessage: function () {
  149. this._publishStatus({
  150. type: 'saveError',
  151. message: 'An error occurred while saving the sort order. ' +
  152. 'Please reload the page and try again.',
  153. duration: 10000
  154. });
  155. },
  156. /********************************************************
  157. * Sorting Methods
  158. ********************************************************/
  159. // add the sortable behavior to the vehicle list
  160. _makeSortable: function () {
  161. var that = this;
  162. this.element.sortable({
  163. axis: false,
  164. items: ':mstats-tile:has(:mstats-vehicle)',
  165. containment: '#vehicles',
  166. handle: '.header',
  167. stop: function () {
  168. that.saveSortOrder();
  169. }
  170. });
  171. },
  172. // save the vehicle list sort order
  173. saveSortOrder: function () {
  174. var that = this,
  175. url = this.element.data('sort-url'),
  176. sortorder = [];
  177. this.element.find(':mstats-vehicle').each(function () {
  178. var id = $(this).data('vehicle-id');
  179. sortorder.push(id);
  180. });
  181. this._showSaveMessage();
  182. // Passing the sort order as a string avoids the need
  183. // for a custom model binder on the server side.
  184. this.options.sendRequest({
  185. url: url,
  186. cache: false,
  187. data: {
  188. SortOrder: sortorder.toString()
  189. },
  190. success: function () {
  191. that._showSavedMessage();
  192. },
  193. error: function () {
  194. that._showSaveErrorMessage();
  195. }
  196. });
  197. },
  198. // handle setting options
  199. _setOption: function (key, value) {
  200. $.Widget.prototype._setOption.apply(this, arguments);
  201. if (value <= 0) {
  202. return;
  203. }
  204. switch (key) {
  205. case 'layout':
  206. this._setLayoutOption(value);
  207. break;
  208. case 'selectedVehicleId':
  209. this._expandVehicle(value);
  210. this._collapseVehicles();
  211. break;
  212. }
  213. },
  214. _checkSelectedVehicleId: function () {
  215. var vid,
  216. state = $.bbq.getState() || {};
  217. if (state.vid) {
  218. vid = parseInt(state.vid, 10);
  219. if (vid !== this.options.selectedVehicleId) {
  220. this._setOption('selectedVehicleId', state.vid);
  221. }
  222. }
  223. },
  224. // adjust the size of the widget between compact and expanded mode
  225. _setLayoutOption: function (layout) {
  226. switch (layout.toLowerCase()) {
  227. case 'dashboard':
  228. // ensure any changes to styles are cleaned up before changing classes
  229. this.element.removeAttr('style')
  230. .removeClass('compact')
  231. .sortable('option', 'axis', false);
  232. break;
  233. case 'details':
  234. // intentional fall through
  235. case 'reminder':
  236. // intentional fall through
  237. case 'fillup':
  238. // ensure any changes to styles are cleaned up before adding classes
  239. this.element.removeAttr('style')
  240. .addClass('compact')
  241. .sortable('option', 'axis', 'y');
  242. this._collapseVehicles();
  243. break;
  244. }
  245. },
  246. /********************************************************
  247. * Animations
  248. ********************************************************/
  249. // collapse all but the selected vehicle
  250. _collapseVehicles: function () {
  251. var selected = this.options.selectedVehicleId;
  252. this.element.find(':mstats-vehicle').each(function () {
  253. var $this = $(this);
  254. if ($this.vehicle('option', 'id') !== selected) {
  255. $this.vehicle('collapse');
  256. }
  257. });
  258. },
  259. // expand all the vehicles
  260. _expandVehicles: function () {
  261. this.element.find(':mstats-vehicle').each(function () {
  262. $(this).vehicle('expand');
  263. });
  264. },
  265. // expand only the specified vehicle
  266. _expandVehicle: function (id) {
  267. var selected = id;
  268. this.element.find(':mstats-vehicle').each(function () {
  269. var $this = $(this);
  270. if (($this.vehicle('option', 'id') === selected)
  271. && $this.vehicle('option', 'collapsed')) {
  272. $this.vehicle('expand');
  273. }
  274. });
  275. },
  276. // shrink the list to the one column size
  277. _narrowToSingleColumn: function () {
  278. var $element = this.element;
  279. if (!this.options.isCollapsed) {
  280. $element.delay(delayLength).animate({
  281. left: 0
  282. }, {
  283. duration: animationLength,
  284. complete: function () {
  285. $element.addClass('compact');
  286. }
  287. });
  288. }
  289. },
  290. // grow the list to the two column size
  291. _expandToDoubleColumn: function () {
  292. if (this.options.isCollapsed) {
  293. this.element.removeClass('compact')
  294. .animate({
  295. left: 400
  296. }, {
  297. duration: animationLength
  298. });
  299. }
  300. },
  301. _scrollToSelectedVehicle: function () {
  302. var that = this;
  303. this.element.find(':mstats-tile').each(function () {
  304. var id,
  305. top,
  306. $this = $(this);
  307. id = $this.find('.vehicle').vehicle('option', 'id');
  308. top = $this.offset().top;
  309. if (id === that.options.selectedVehicleId) {
  310. if (top > 300) {
  311. $('html, body').delay(delayLength)
  312. .animate({ scrollTop: top }, 500);
  313. }
  314. }
  315. });
  316. },
  317. /********************************************************
  318. * Public Methods
  319. ********************************************************/
  320. // called externally (usually by the layoutManager widget)
  321. goToDetailsLayout: function () {
  322. var selectedVehicle,
  323. vid = 0,
  324. that = this,
  325. runningTop = 0,
  326. animationInfoArray,
  327. state = $.bbq.getState() || {};
  328. this._checkSelectedVehicleId();
  329. selectedVehicle = this.options.selectedVehicleId;
  330. if (!this.options.isCollapsed) {
  331. this.element.find(':mstats-tile').each(function () {
  332. $(this).tile('beginAnimation');
  333. });
  334. this.element.find(':mstats-tile').each(function () {
  335. var $this = $(this);
  336. vid = $this.find('.vehicle').vehicle('option', 'id');
  337. animationInfoArray = [{
  338. position: { top: runningTop },
  339. duration: animationLength
  340. }, {
  341. position: { left: 0 },
  342. duration: animationLength
  343. }];
  344. $this.tile('moveTo', animationInfoArray, function () {
  345. that.element.find(':mstats-tile').each(function () {
  346. $(this).tile('endAnimation');
  347. });
  348. });
  349. // calculate the runningTop for next time around
  350. if (vid === selectedVehicle) {
  351. runningTop += 321;
  352. } else {
  353. runningTop += 206;
  354. }
  355. });
  356. this._narrowToSingleColumn();
  357. }
  358. this._scrollToSelectedVehicle();
  359. if (state && state.layout) {
  360. this.options.layout = state.layout;
  361. }
  362. this.options.isCollapsed = true;
  363. },
  364. // called externally (usually by the layoutManager widget)
  365. goToDashboardLayout: function () {
  366. var vid = 0,
  367. that = this,
  368. animationInfoArray;
  369. this.options.layout = 'dashboard';
  370. this.options.selectedVehicleId = 0;
  371. if (!this.options.isCollapsed) {
  372. return;
  373. }
  374. this.element.find(':mstats-tile').each(function () {
  375. $(this).tile('beginAnimation');
  376. });
  377. this._expandToDoubleColumn();
  378. this.element.find(':mstats-tile').each(function (index) {
  379. var $this = $(this);
  380. vid = $this.find('.vehicle').vehicle('option', 'id');
  381. animationInfoArray = [{
  382. position: { left: index % 2 * 260 },
  383. duration: animationLength
  384. }, {
  385. position: { top: Math.floor(index / 2) * 320 },
  386. duration: animationLength
  387. }];
  388. that._expandVehicles();
  389. $this.tile('moveTo', animationInfoArray, function () {
  390. that.element.find(':mstats-tile').each(function () {
  391. $(this).tile('endAnimation');
  392. });
  393. });
  394. });
  395. this.options.isCollapsed = false;
  396. },
  397. moveOffScreen: function () {
  398. var that = this;
  399. if (!this.options.visible) { return; }
  400. this.element.css('position', 'absolute')
  401. .delay(delayLength)
  402. .animate({
  403. left: '-=600',
  404. opacity: 0
  405. }, {
  406. duration: animationLength,
  407. complete: function () {
  408. that.element.hide();
  409. }
  410. });
  411. this.options.visible = false;
  412. },
  413. moveOnScreen: function () {
  414. if (this.options.visible) {
  415. return;
  416. }
  417. this.element.css('opacity', 0)
  418. .animate({
  419. opacity: 1,
  420. left: '+=600'
  421. }, animationLength)
  422. .show();
  423. this._setOption('visible', true);
  424. },
  425. _invalidateData: function () {
  426. if ($.isFunction(this.options.invalidateData)) {
  427. this.options.invalidateData(dataUrl);
  428. }
  429. },
  430. refreshData: function () {
  431. this._getVehicleData();
  432. },
  433. requeryData: function () {
  434. this._invalidateData();
  435. this.refreshData();
  436. },
  437. // cleanup
  438. destroy: function () {
  439. this.element
  440. .sortable('destroy')
  441. .find(':mstats-tile').tile('destroy')
  442. .find(':mstats-vehicle').vehicle('destroy');
  443. $.Widget.prototype.destroy.call(this);
  444. }
  445. });
  446. } (this.mstats = this.mstats || {}, jQuery));