helper.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. /**
  19. * Remove spaces at beginning and ending of line.
  20. * @example
  21. * var str = " I'm a string "
  22. * str.trim() // return "I'm a string"
  23. * @method trim
  24. * @return {string}
  25. */
  26. String.prototype.trim = function () {
  27. return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  28. };
  29. /**
  30. * Determines whether string end within another string.
  31. *
  32. * @method endsWith
  33. * @param suffix {string} substring for search
  34. * @return {boolean}
  35. */
  36. String.prototype.endsWith = function(suffix) {
  37. return this.indexOf(suffix, this.length - suffix.length) !== -1;
  38. };
  39. /**
  40. * Determines whether string start within another string.
  41. *
  42. * @method startsWith
  43. * @param prefix {string} substring for search
  44. * @return {boolean}
  45. */
  46. String.prototype.startsWith = function (prefix){
  47. return this.indexOf(prefix) == 0;
  48. };
  49. /**
  50. * Determines whether string founded within another string.
  51. *
  52. * @method contains
  53. * @param substring {string} substring for search
  54. * @return {boolean}
  55. */
  56. String.prototype.contains = function(substring) {
  57. return this.indexOf(substring) != -1;
  58. };
  59. /**
  60. * Capitalize the first letter of string.
  61. * @method capitalize
  62. * @return {string}
  63. */
  64. String.prototype.capitalize = function () {
  65. return this.charAt(0).toUpperCase() + this.slice(1);
  66. };
  67. /**
  68. * Capitalize the first letter of string.
  69. * And set to lowercase other part of string
  70. * @method toCapital
  71. * @return {string}
  72. */
  73. String.prototype.toCapital = function () {
  74. return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
  75. };
  76. /**
  77. * Finds the value in an object where this string is a key.
  78. * Optionally, the index of the key can be provided where the
  79. * value of the nth key in the hierarchy is returned.
  80. *
  81. * Example:
  82. * var tofind = 'smart';
  83. * var person = {'name': 'Bob Bob', 'smart': 'no', 'age': '28', 'personality': {'smart': 'yes', 'funny': 'yes', 'emotion': 'happy'} };
  84. * tofind.findIn(person); // 'no'
  85. * tofind.findIn(person, 0); // 'no'
  86. * tofind.findIn(person, 1); // 'yes'
  87. * tofind.findIn(person, 2); // null
  88. *
  89. * @method findIn
  90. * @param multi {object}
  91. * @param index {number} Occurrence count of this key
  92. * @return {*} Value of key at given index
  93. */
  94. String.prototype.findIn = function(multi, index, _foundValues) {
  95. if (!index) {
  96. index = 0;
  97. }
  98. if (!_foundValues) {
  99. _foundValues = [];
  100. }
  101. multi = multi || '';
  102. var value = null;
  103. var str = this.valueOf();
  104. if (typeof multi == 'object') {
  105. for ( var key in multi) {
  106. if (value != null) {
  107. break;
  108. }
  109. if (key == str) {
  110. _foundValues.push(multi[key]);
  111. }
  112. if (_foundValues.length - 1 == index) {
  113. // Found the value
  114. return _foundValues[index];
  115. }
  116. if (typeof multi[key] == 'object') {
  117. value = value || this.findIn(multi[key], index, _foundValues);
  118. }
  119. }
  120. }
  121. return value;
  122. };
  123. /**
  124. * Replace {i} with argument. where i is number of argument to replace with.
  125. * @example
  126. * var str = "{0} world{1}";
  127. * str.format("Hello", "!") // return "Hello world!"
  128. *
  129. * @method format
  130. * @return {string}
  131. */
  132. String.prototype.format = function () {
  133. var args = arguments;
  134. return this.replace(/{(\d+)}/g, function (match, number) {
  135. return typeof args[number] != 'undefined' ? args[number] : match;
  136. });
  137. };
  138. /**
  139. * Wrap words in string within template.
  140. *
  141. * @method highlight
  142. * @param {string[]} words - words to wrap
  143. * @param {string} [highlightTemplate="<b>{0}</b>"] - template for wrapping
  144. * @return {string}
  145. */
  146. String.prototype.highlight = function (words, highlightTemplate) {
  147. var self = this;
  148. highlightTemplate = highlightTemplate ? highlightTemplate : "<b>{0}</b>";
  149. words.forEach(function (word) {
  150. var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
  151. self = self.replace(searchRegExp, function (found) {
  152. return highlightTemplate.format(found);
  153. });
  154. });
  155. return self;
  156. };
  157. /**
  158. * Convert time in milliseconds to object contained days, hours and minutes.
  159. * @typedef ConvertedTime
  160. * @type {Object}
  161. * @property {number} d - days
  162. * @property {number} h - hours
  163. * @property {string} m - minutes
  164. * @example
  165. * var time = 1000000000;
  166. * time.toDaysHoursMinutes() // {d: 11, h: 13, m: "46.67"}
  167. *
  168. * @method toDaysHoursMinutes
  169. * @return {object}
  170. */
  171. Number.prototype.toDaysHoursMinutes = function () {
  172. var formatted = {},
  173. dateDiff = this,
  174. secK = 1000, //ms
  175. minK = 60 * secK, // sec
  176. hourK = 60 * minK, // sec
  177. dayK = 24 * hourK;
  178. dateDiff = parseInt(dateDiff);
  179. formatted.d = Math.floor(dateDiff / dayK);
  180. dateDiff -= formatted.d * dayK;
  181. formatted.h = Math.floor(dateDiff / hourK);
  182. dateDiff -= formatted.h * hourK;
  183. formatted.m = (dateDiff / minK).toFixed(2);
  184. return formatted;
  185. };
  186. /**
  187. Sort an array by the key specified in the argument.
  188. Handle only native js objects as element of array, not the Ember's object.
  189. Can be used as alternative to sortProperty method of Ember library
  190. in order to speed up executing on large data volumes
  191. @method sortBy
  192. @param {String} path name(s) to sort on
  193. @return {Array} The sorted array.
  194. */
  195. Array.prototype.sortPropertyLight = function (path) {
  196. var realPath = (typeof path === "string") ? path.split('.') : [];
  197. this.sort(function (a, b) {
  198. var aProperty = a;
  199. var bProperty = b;
  200. realPath.forEach(function (key) {
  201. aProperty = aProperty[key];
  202. bProperty = bProperty[key];
  203. });
  204. if (aProperty > bProperty) return 1;
  205. if (aProperty < bProperty) return -1;
  206. return 0;
  207. });
  208. return this;
  209. };
  210. /** @namespace Em **/
  211. Em.CoreObject.reopen({
  212. t:function (key, attrs) {
  213. return Em.I18n.t(key, attrs)
  214. }
  215. });
  216. Em.TextArea.reopen(Em.I18n.TranslateableAttributes);
  217. /** @namespace Em.Handlebars **/
  218. Em.Handlebars.registerHelper('log', function (variable) {
  219. console.log(variable);
  220. });
  221. Em.Handlebars.registerHelper('warn', function (variable) {
  222. console.warn(variable);
  223. });
  224. Em.Handlebars.registerHelper('highlight', function (property, words, fn) {
  225. var context = (fn.contexts && fn.contexts[0]) || this;
  226. property = Em.Handlebars.getPath(context, property, fn);
  227. words = words.split(";");
  228. // if (highlightTemplate == undefined) {
  229. var highlightTemplate = "<b>{0}</b>";
  230. // }
  231. words.forEach(function (word) {
  232. var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
  233. property = property.replace(searchRegExp, function (found) {
  234. return highlightTemplate.format(found);
  235. });
  236. });
  237. return new Em.Handlebars.SafeString(property);
  238. });
  239. /**
  240. * @namespace App
  241. */
  242. App = require('app');
  243. /**
  244. * Certain variables can have JSON in string
  245. * format, or in JSON format itself.
  246. *
  247. * @memberof App
  248. * @function parseJson
  249. * @param {string|object}
  250. * @return {object}
  251. */
  252. App.parseJSON = function (value) {
  253. if (typeof value == "string") {
  254. return jQuery.parseJSON(value);
  255. }
  256. return value;
  257. };
  258. /**
  259. * Check for empty <code>Object</code>, built in Em.isEmpty()
  260. * doesn't support <code>Object</code> type
  261. *
  262. * @memberof App
  263. * @method isEmptyObject
  264. * @param obj {Object}
  265. * @return {Boolean}
  266. */
  267. App.isEmptyObject = function(obj) {
  268. var empty = true;
  269. for (var prop in obj) { if (obj.hasOwnProperty(prop)) {empty = false; break;} }
  270. return empty;
  271. }
  272. /**
  273. * Returns object with defined keys only.
  274. *
  275. * @memberof App
  276. * @method permit
  277. * @param {Object} obj - input object
  278. * @param {String|Array} keys - allowed keys
  279. * @return {Object}
  280. */
  281. App.permit = function(obj, keys) {
  282. var result = {};
  283. if (typeof obj !== 'object' || App.isEmptyObject(obj)) return result;
  284. if (typeof keys == 'string') keys = Array(keys);
  285. keys.forEach(function(key) {
  286. if (obj.hasOwnProperty(key))
  287. result[key] = obj[key];
  288. });
  289. return result;
  290. };
  291. /**
  292. *
  293. * @namespace App
  294. * @namespace App.format
  295. */
  296. App.format = {
  297. /**
  298. * @memberof App.format
  299. * @type {object}
  300. * @property components
  301. */
  302. components: {
  303. 'API': 'API',
  304. 'DECOMMISSION_DATANODE': 'Update Exclude File',
  305. 'DRPC': 'DRPC',
  306. 'FLUME_HANDLER': 'Flume Agent',
  307. 'GLUSTERFS': 'GLUSTERFS',
  308. 'HBASE': 'HBase',
  309. 'HBASE_REGIONSERVER': 'RegionServer',
  310. 'HCAT': 'HCat',
  311. 'HDFS': 'HDFS',
  312. 'HISTORYSERVER': 'History Server',
  313. 'HIVE_SERVER': 'HiveServer2',
  314. 'JCE': 'JCE',
  315. 'MAPREDUCE': 'MapReduce',
  316. 'MAPREDUCE2': 'MapReduce2',
  317. 'MYSQL': 'MySQL',
  318. 'REST': 'REST',
  319. 'SECONDARY_NAMENODE': 'SNameNode',
  320. 'STORM_REST_API': 'Storm REST API Server',
  321. 'WEBHCAT': 'WebHCat',
  322. 'YARN': 'YARN',
  323. 'UI': 'UI',
  324. 'ZKFC': 'ZKFailoverController',
  325. 'ZOOKEEPER': 'ZooKeeper',
  326. 'ZOOKEEPER_QUORUM_SERVICE_CHECK': 'ZK Quorum Service Check'
  327. },
  328. /**
  329. * @memberof App.format
  330. * @property command
  331. * @type {object}
  332. */
  333. command: {
  334. 'INSTALL': 'Install',
  335. 'UNINSTALL': 'Uninstall',
  336. 'START': 'Start',
  337. 'STOP': 'Stop',
  338. 'EXECUTE': 'Execute',
  339. 'ABORT': 'Abort',
  340. 'UPGRADE': 'Upgrade',
  341. 'RESTART': 'Restart',
  342. 'SERVICE_CHECK': 'Check',
  343. 'Excluded:': 'Decommission:',
  344. 'Included:': 'Recommission:'
  345. },
  346. /**
  347. * convert role to readable string
  348. *
  349. * @memberof App.format
  350. * @method role
  351. * @param {string} role
  352. * return {string}
  353. */
  354. role:function (role) {
  355. return this.normalizeName(role);
  356. },
  357. /**
  358. * Try to format non predefined names to readable format.
  359. *
  360. * @method normalizeName
  361. * @param name {String} - name to format
  362. * @return {String}
  363. */
  364. normalizeName: function(name) {
  365. if (!name || typeof name != 'string') return '';
  366. if (this.components[name]) return this.components[name];
  367. name = name.toLowerCase();
  368. var suffixNoSpaces = ['node','tracker','manager'];
  369. var suffixRegExp = new RegExp('(\\w+)(' + suffixNoSpaces.join('|') + ')', 'gi');
  370. if (/_/g.test(name)) {
  371. name = name.split('_').map(function(singleName) {
  372. return this.normalizeName(singleName.toUpperCase());
  373. }, this).join(' ');
  374. } else if(suffixRegExp.test(name)) {
  375. suffixRegExp.lastIndex = 0;
  376. var matches = suffixRegExp.exec(name);
  377. name = matches[1].capitalize() + matches[2].capitalize();
  378. };
  379. return name.capitalize();
  380. },
  381. /**
  382. * convert command_detail to readable string, show the string for all tasks name
  383. *
  384. * @memberof App.format
  385. * @method commandDetail
  386. * @param {string} command_detail
  387. * @return {string}
  388. */
  389. commandDetail: function (command_detail) {
  390. var detailArr = command_detail.split(' ');
  391. var self = this;
  392. var result = '';
  393. detailArr.forEach( function(item) {
  394. // if the item has the pattern SERVICE/COMPONENT, drop the SERVICE part
  395. if (item.contains('/')) {
  396. item = item.split('/')[1];
  397. }
  398. // ignore 'DECOMMISSION', command came from 'excluded/included'
  399. if (item == 'DECOMMISSION,') {
  400. item = '';
  401. }
  402. if (self.components[item]) {
  403. result = result + ' ' + self.components[item];
  404. } else if (self.command[item]) {
  405. result = result + ' ' + self.command[item];
  406. } else {
  407. result = result + ' ' + self.role(item);
  408. }
  409. });
  410. if (result === ' Nagios Update Ignore Actionexecute') {
  411. result = Em.I18n.t('common.maintenance.task');
  412. }
  413. return result;
  414. },
  415. /**
  416. * Convert uppercase status name to lowercase.
  417. * <br>
  418. * <br>PENDING - Not queued yet for a host
  419. * <br>QUEUED - Queued for a host
  420. * <br>IN_PROGRESS - Host reported it is working
  421. * <br>COMPLETED - Host reported success
  422. * <br>FAILED - Failed
  423. * <br>TIMEDOUT - Host did not respond in time
  424. * <br>ABORTED - Operation was abandoned
  425. *
  426. * @memberof App.format
  427. * @method taskStatus
  428. * @param {string} _taskStatus
  429. * @return {string}
  430. *
  431. */
  432. taskStatus:function (_taskStatus) {
  433. return _taskStatus.toLowerCase();
  434. }
  435. };
  436. /**
  437. * wrapper to bootstrap popover
  438. * fix issue when popover stuck on view routing
  439. *
  440. * @memberof App
  441. * @method popover
  442. * @param {DOMElement} self
  443. * @param {object} options
  444. */
  445. App.popover = function (self, options) {
  446. self.popover(options);
  447. self.on("remove DOMNodeRemoved", function () {
  448. $(this).trigger('mouseleave');
  449. });
  450. };
  451. /**
  452. * wrapper to bootstrap tooltip
  453. * fix issue when tooltip stuck on view routing
  454. * @memberof App
  455. * @method tooltip
  456. * @param {DOMElement} self
  457. * @param {object} options
  458. */
  459. App.tooltip = function (self, options) {
  460. self.tooltip(options);
  461. /* istanbul ignore next */
  462. self.on("remove DOMNodeRemoved", function () {
  463. $(this).trigger('mouseleave');
  464. });
  465. };
  466. /**
  467. * wrapper to Date().getTime()
  468. * fix issue when client clock and server clock not sync
  469. *
  470. * @memberof App
  471. * @method dateTime
  472. * @return {Number} timeStamp of current server clock
  473. */
  474. App.dateTime = function() {
  475. return new Date().getTime() + App.clockDistance;
  476. };
  477. /**
  478. * Helper function for bound property helper registration
  479. * @memberof App
  480. * @method registerBoundHelper
  481. * @param name {String} name of helper
  482. * @param view {Em.View} view
  483. */
  484. App.registerBoundHelper = function(name, view) {
  485. Em.Handlebars.registerHelper(name, function(property, options) {
  486. options.hash.contentBinding = property;
  487. return Em.Handlebars.helpers.view.call(this, view, options);
  488. });
  489. };
  490. /*
  491. * Return singular or plural word based on Em.I18n, view|controller context property key.
  492. *
  493. * Example: {{pluralize hostsCount singular="t:host" plural="t:hosts"}}
  494. * {{pluralize hostsCount singular="@view.hostName"}}
  495. */
  496. App.registerBoundHelper('pluralize', Em.View.extend({
  497. tagName: 'span',
  498. template: Em.Handlebars.compile('{{view.wordOut}}'),
  499. wordOut: function() {
  500. var count, singular, plural;
  501. count = this.get('content');
  502. singular = this.get('singular');
  503. plural = this.get('plural');
  504. return this.getWord(count, singular, plural);
  505. }.property('content'),
  506. /**
  507. * Get computed word.
  508. *
  509. * @param {Number} count
  510. * @param {String} singular
  511. * @param {String} [plural]
  512. * @return {String}
  513. * @method getWord
  514. */
  515. getWord: function(count, singular, plural) {
  516. singular = this.parseValue(singular);
  517. // if plural not passed
  518. if (!plural) plural = singular + 's';
  519. else plural = this.parseValue(plural);
  520. if (singular && plural) {
  521. if (count > 1) {
  522. return plural;
  523. } else {
  524. return singular;
  525. }
  526. }
  527. return '';
  528. },
  529. /**
  530. * Detect and return value from its instance.
  531. *
  532. * @param {String} value
  533. * @return {*}
  534. * @method parseValue
  535. **/
  536. parseValue: function(value) {
  537. switch (value[0]) {
  538. case '@':
  539. value = this.getViewPropertyValue(value);
  540. break;
  541. case 't':
  542. value = this.tDetect(value);
  543. break;
  544. default:
  545. break;
  546. }
  547. return value;
  548. },
  549. /*
  550. * Detect for Em.I18n.t reference call
  551. * @params word {String}
  552. * return {String}
  553. */
  554. tDetect: function(word) {
  555. var splitted = word.split(':');
  556. if (splitted.length > 1 && splitted[0] == 't') {
  557. return Em.I18n.t(splitted[1]);
  558. } else {
  559. return splitted[0];
  560. }
  561. },
  562. /**
  563. * Get property value from view|controller by its key path.
  564. *
  565. * @param {String} value - key path
  566. * @return {*}
  567. * @method getViewPropertyValue
  568. **/
  569. getViewPropertyValue: function(value) {
  570. value = value.substr(1);
  571. var keyword = value.split('.')[0]; // return 'controller' or 'view'
  572. switch (keyword) {
  573. case 'controller':
  574. return Em.get(this, value);
  575. case 'view':
  576. return Em.get(this, value.replace(/^view/, 'parentView'))
  577. default:
  578. break;
  579. }
  580. }
  581. })
  582. );
  583. /**
  584. * Return defined string instead of empty if value is null/undefined
  585. * by default is `n/a`.
  586. *
  587. * @param empty {String} - value instead of empty string (not required)
  588. * can be used with Em.I18n pass value started with't:'
  589. *
  590. * Examples:
  591. *
  592. * default value will be returned
  593. * {{formatNull service.someValue}}
  594. *
  595. * <code>empty<code> will be returned
  596. * {{formatNull service.someValue empty="I'm empty"}}
  597. *
  598. * Em.I18n translation will be returned
  599. * {{formatNull service.someValue empty="t:my.key.to.translate"
  600. */
  601. App.registerBoundHelper('formatNull', Em.View.extend({
  602. tagName: 'span',
  603. template: Em.Handlebars.compile('{{view.result}}'),
  604. result: function() {
  605. var emptyValue = this.get('empty') ? this.get('empty') : Em.I18n.t('services.service.summary.notAvailable');
  606. emptyValue = emptyValue.startsWith('t:') ? Em.I18n.t(emptyValue.substr(2, emptyValue.length)) : emptyValue;
  607. return (this.get('content') || this.get('content') == 0) ? this.get('content') : emptyValue;
  608. }.property('content')
  609. }));
  610. /**
  611. * Return formatted string with inserted <code>wbr</code>-tag after each dot
  612. *
  613. * @param {String} content
  614. *
  615. * Examples:
  616. *
  617. * returns 'apple'
  618. * {{formatWordBreak 'apple'}}
  619. *
  620. * returns 'apple.<wbr />banana'
  621. * {{formatWordBreak 'apple.banana'}}
  622. *
  623. * returns 'apple.<wbr />banana.<wbr />uranium'
  624. * {{formatWordBreak 'apple.banana.uranium'}}
  625. */
  626. App.registerBoundHelper('formatWordBreak', Em.View.extend({
  627. tagName: 'span',
  628. template: Em.Handlebars.compile('{{{view.result}}}'),
  629. /**
  630. * @type {string}
  631. */
  632. result: function() {
  633. return this.get('content') && this.get('content').replace(/\./g, '.<wbr />');
  634. }.property('content')
  635. }));
  636. /**
  637. * Ambari overrides the default date transformer.
  638. * This is done because of the non-standard data
  639. * sent. For example Nagios sends date as "12345678".
  640. * The problem is that it is a String and is represented
  641. * only in seconds whereas Javascript's Date needs
  642. * milliseconds representation.
  643. */
  644. DS.attr.transforms.date = {
  645. from: function (serialized) {
  646. var type = typeof serialized;
  647. if (type === Em.I18n.t('common.type.string')) {
  648. serialized = parseInt(serialized);
  649. type = typeof serialized;
  650. }
  651. if (type === Em.I18n.t('common.type.number')) {
  652. if (!serialized ){ //serialized timestamp = 0;
  653. return 0;
  654. }
  655. // The number could be seconds or milliseconds.
  656. // If seconds, then the length is 10
  657. // If milliseconds, the length is 13
  658. if (serialized.toString().length < 13) {
  659. serialized = serialized * 1000;
  660. }
  661. return new Date(serialized);
  662. } else if (serialized === null || serialized === undefined) {
  663. // if the value is not present in the data,
  664. // return undefined, not null.
  665. return serialized;
  666. } else {
  667. return null;
  668. }
  669. },
  670. to: function (deserialized) {
  671. if (deserialized instanceof Date) {
  672. return deserialized.getTime();
  673. } else if (deserialized === undefined) {
  674. return undefined;
  675. } else {
  676. return null;
  677. }
  678. }
  679. };
  680. DS.attr.transforms.object = {
  681. from: function(serialized) {
  682. return Ember.none(serialized) ? null : Object(serialized);
  683. },
  684. to: function(deserialized) {
  685. return Ember.none(deserialized) ? null : Object(deserialized);
  686. }
  687. };
  688. /**
  689. * Allows EmberData models to have array properties.
  690. *
  691. * Declare the property as <code>
  692. * operations: DS.attr('array'),
  693. * </code> and
  694. * during load provide a JSON array for value.
  695. *
  696. * This transform simply assigns the same array in both directions.
  697. */
  698. DS.attr.transforms.array = {
  699. from : function(serialized) {
  700. return serialized;
  701. },
  702. to : function(deserialized) {
  703. return deserialized;
  704. }
  705. };