Skip to content
Browse files

Initial Import of the all new Messagee based in the new Usergrid SDK …

…using JQuery and JQuery mobile
  • Loading branch information...
1 parent 56ede03 commit ed7893d3feb0a89f5509f14f589e3961d6952d81 @rodsimpson rodsimpson committed Aug 3, 2012
Showing with 2,965 additions and 26,068 deletions.
  1. +0 −4 README.txt
  2. +0 −33 app/app.js
  3. +0 −182 app/controllers/MsgController.js
  4. +0 −16 app/models/Activity.js
  5. +0 −11 app/models/Users.js
  6. +0 −3 app/stores/Users.js
  7. +0 −107 app/views/MsgPanel.js
  8. +0 −53 app/views/NewMsgPanel.js
  9. +0 −37 app/views/OtherPanel.js
  10. +0 −87 app/views/SettingsPanel.js
  11. +0 −24 app/views/Viewport.js
  12. +161 −61 index.html
  13. +2,208 −0 js/apigee.appSDK.js
  14. +596 −0 js/app.js
  15. +0 −173 main.js
  16. +0 −256 md5.js
  17. +0 −18 messagee-main.js
  18. +0 −4,367 phonegap-1.0.0.js
  19. +0 −239 phonegapdemo-w-sencha.js
  20. BIN profile-photo.jpg
  21. +0 −5,155 sencha/resources/css-debug/android.css
  22. +0 −5,178 sencha/resources/css-debug/apple.css
  23. +0 −4,843 sencha/resources/css-debug/bb6.css
  24. +0 −5,178 sencha/resources/css-debug/sencha-touch.css
  25. +0 −1 sencha/resources/css/android.css
  26. +0 −1 sencha/resources/css/apple.css
  27. +0 −1 sencha/resources/css/bb6.css
  28. +0 −1 sencha/resources/css/sencha-touch.css
  29. +0 −11 sencha/resources/sass/config-debug.rb
  30. +0 −11 sencha/resources/sass/config-release.rb
  31. +0 −11 sencha/resources/sass/config.rb
  32. +0 −6 sencha/resources/themes/compass_init.rb
  33. BIN sencha/resources/themes/images/default/check.png
  34. BIN sencha/resources/themes/images/default/disclosure.png
  35. BIN sencha/resources/themes/images/default/dotgrid.png
  36. BIN sencha/resources/themes/images/default/loading.gif
  37. BIN sencha/resources/themes/images/default/pictos/action.png
  38. BIN sencha/resources/themes/images/default/pictos/add.png
  39. BIN sencha/resources/themes/images/default/pictos/add1.png
  40. BIN sencha/resources/themes/images/default/pictos/add_black.png
  41. BIN sencha/resources/themes/images/default/pictos/address_book.png
  42. BIN sencha/resources/themes/images/default/pictos/arrow_down.png
  43. BIN sencha/resources/themes/images/default/pictos/arrow_left.png
  44. BIN sencha/resources/themes/images/default/pictos/arrow_right.png
  45. BIN sencha/resources/themes/images/default/pictos/arrow_up.png
  46. BIN sencha/resources/themes/images/default/pictos/at.png
  47. BIN sencha/resources/themes/images/default/pictos/atom.png
  48. BIN sencha/resources/themes/images/default/pictos/attachment.png
  49. BIN sencha/resources/themes/images/default/pictos/attachment2.png
  50. BIN sencha/resources/themes/images/default/pictos/attachment3.png
  51. BIN sencha/resources/themes/images/default/pictos/attachment_black.png
  52. BIN sencha/resources/themes/images/default/pictos/backspace.png
  53. BIN sencha/resources/themes/images/default/pictos/battery_full.png
  54. BIN sencha/resources/themes/images/default/pictos/battery_low.png
  55. BIN sencha/resources/themes/images/default/pictos/battery_power.png
  56. BIN sencha/resources/themes/images/default/pictos/blank.png
  57. BIN sencha/resources/themes/images/default/pictos/bolt.png
  58. BIN sencha/resources/themes/images/default/pictos/bolt_side.png
  59. BIN sencha/resources/themes/images/default/pictos/bookmark1.png
  60. BIN sencha/resources/themes/images/default/pictos/bookmark2.png
  61. BIN sencha/resources/themes/images/default/pictos/bookmark_black.png
  62. BIN sencha/resources/themes/images/default/pictos/bookmarks.png
  63. BIN sencha/resources/themes/images/default/pictos/briefcase1.png
  64. BIN sencha/resources/themes/images/default/pictos/briefcase2.png
  65. BIN sencha/resources/themes/images/default/pictos/brightness1.png
  66. BIN sencha/resources/themes/images/default/pictos/brightness2.png
  67. BIN sencha/resources/themes/images/default/pictos/broadcast.png
  68. BIN sencha/resources/themes/images/default/pictos/bug.png
  69. BIN sencha/resources/themes/images/default/pictos/bulb.png
  70. BIN sencha/resources/themes/images/default/pictos/bullseye1.png
  71. BIN sencha/resources/themes/images/default/pictos/bullseye2.png
  72. BIN sencha/resources/themes/images/default/pictos/calendar.png
  73. BIN sencha/resources/themes/images/default/pictos/calendar2.png
  74. BIN sencha/resources/themes/images/default/pictos/calendar_add.png
  75. BIN sencha/resources/themes/images/default/pictos/card1.png
  76. BIN sencha/resources/themes/images/default/pictos/card2.png
  77. BIN sencha/resources/themes/images/default/pictos/chart1.png
  78. BIN sencha/resources/themes/images/default/pictos/chart2.png
  79. BIN sencha/resources/themes/images/default/pictos/chart3.png
  80. BIN sencha/resources/themes/images/default/pictos/chat.png
  81. BIN sencha/resources/themes/images/default/pictos/chat1.png
  82. BIN sencha/resources/themes/images/default/pictos/chat2.png
  83. BIN sencha/resources/themes/images/default/pictos/chat3.png
  84. BIN sencha/resources/themes/images/default/pictos/chat4.png
  85. BIN sencha/resources/themes/images/default/pictos/chat_black1.png
  86. BIN sencha/resources/themes/images/default/pictos/chat_black2.png
  87. BIN sencha/resources/themes/images/default/pictos/check1.png
  88. BIN sencha/resources/themes/images/default/pictos/check2.png
  89. BIN sencha/resources/themes/images/default/pictos/check_black1.png
  90. BIN sencha/resources/themes/images/default/pictos/check_black2.png
  91. BIN sencha/resources/themes/images/default/pictos/check_dotted.png
  92. BIN sencha/resources/themes/images/default/pictos/circle.png
  93. BIN sencha/resources/themes/images/default/pictos/circle2.png
  94. BIN sencha/resources/themes/images/default/pictos/clash.png
  95. BIN sencha/resources/themes/images/default/pictos/cloud.png
  96. BIN sencha/resources/themes/images/default/pictos/cloud_black.png
  97. BIN sencha/resources/themes/images/default/pictos/cloud_black_upload1.png
  98. BIN sencha/resources/themes/images/default/pictos/cloud_black_upload2.png
  99. BIN sencha/resources/themes/images/default/pictos/cloud_bolt.png
  100. BIN sencha/resources/themes/images/default/pictos/cloud_download.png
  101. BIN sencha/resources/themes/images/default/pictos/code1.png
  102. BIN sencha/resources/themes/images/default/pictos/code2.png
  103. BIN sencha/resources/themes/images/default/pictos/compass1.png
  104. BIN sencha/resources/themes/images/default/pictos/compass2.png
  105. BIN sencha/resources/themes/images/default/pictos/compass3.png
  106. BIN sencha/resources/themes/images/default/pictos/compose.png
  107. BIN sencha/resources/themes/images/default/pictos/compose1.png
  108. BIN sencha/resources/themes/images/default/pictos/compose2.png
  109. BIN sencha/resources/themes/images/default/pictos/compose3.png
  110. BIN sencha/resources/themes/images/default/pictos/compose_black.png
  111. BIN sencha/resources/themes/images/default/pictos/contract.png
  112. BIN sencha/resources/themes/images/default/pictos/cube.png
  113. BIN sencha/resources/themes/images/default/pictos/data.png
  114. BIN sencha/resources/themes/images/default/pictos/delete.png
  115. BIN sencha/resources/themes/images/default/pictos/delete1.png
  116. BIN sencha/resources/themes/images/default/pictos/delete_black1.png
  117. BIN sencha/resources/themes/images/default/pictos/delete_black2.png
  118. BIN sencha/resources/themes/images/default/pictos/doc.png
  119. BIN sencha/resources/themes/images/default/pictos/doc2.png
  120. BIN sencha/resources/themes/images/default/pictos/doc_black.png
  121. BIN sencha/resources/themes/images/default/pictos/doc_black_landscape.png
  122. BIN sencha/resources/themes/images/default/pictos/doc_compose1.png
  123. BIN sencha/resources/themes/images/default/pictos/doc_compose2.png
  124. BIN sencha/resources/themes/images/default/pictos/doc_delete.png
  125. BIN sencha/resources/themes/images/default/pictos/doc_down.png
  126. BIN sencha/resources/themes/images/default/pictos/doc_drawer.png
  127. BIN sencha/resources/themes/images/default/pictos/doc_list.png
  128. BIN sencha/resources/themes/images/default/pictos/doc_new.png
  129. BIN sencha/resources/themes/images/default/pictos/doc_send.png
  130. BIN sencha/resources/themes/images/default/pictos/doc_up.png
  131. BIN sencha/resources/themes/images/default/pictos/docs1.png
  132. BIN sencha/resources/themes/images/default/pictos/docs2.png
  133. BIN sencha/resources/themes/images/default/pictos/docs_black1.png
  134. BIN sencha/resources/themes/images/default/pictos/docs_black2.png
  135. BIN sencha/resources/themes/images/default/pictos/download.png
  136. BIN sencha/resources/themes/images/default/pictos/download1.png
  137. BIN sencha/resources/themes/images/default/pictos/download2.png
  138. BIN sencha/resources/themes/images/default/pictos/download_screen.png
  139. BIN sencha/resources/themes/images/default/pictos/eject.png
  140. BIN sencha/resources/themes/images/default/pictos/empty1.png
  141. BIN sencha/resources/themes/images/default/pictos/empty2.png
  142. BIN sencha/resources/themes/images/default/pictos/equalizer1.png
  143. BIN sencha/resources/themes/images/default/pictos/equalizer2.png
  144. BIN sencha/resources/themes/images/default/pictos/event_complete.png
  145. BIN sencha/resources/themes/images/default/pictos/expand.png
  146. BIN sencha/resources/themes/images/default/pictos/favorites.png
  147. BIN sencha/resources/themes/images/default/pictos/favorites1.png
  148. BIN sencha/resources/themes/images/default/pictos/favorites_circle.png
  149. BIN sencha/resources/themes/images/default/pictos/fforward.png
  150. BIN sencha/resources/themes/images/default/pictos/find.png
  151. BIN sencha/resources/themes/images/default/pictos/flag.png
  152. BIN sencha/resources/themes/images/default/pictos/flickr2.png
  153. BIN sencha/resources/themes/images/default/pictos/folder.png
  154. BIN sencha/resources/themes/images/default/pictos/folder_add.png
  155. BIN sencha/resources/themes/images/default/pictos/folder_black.png
  156. BIN sencha/resources/themes/images/default/pictos/folder_black_open.png
  157. BIN sencha/resources/themes/images/default/pictos/folder_delete.png
  158. BIN sencha/resources/themes/images/default/pictos/folder_delete2.png
  159. BIN sencha/resources/themes/images/default/pictos/folder_lock.png
  160. BIN sencha/resources/themes/images/default/pictos/folder_open2.png
  161. BIN sencha/resources/themes/images/default/pictos/font.png
  162. BIN sencha/resources/themes/images/default/pictos/forbidden.png
  163. BIN sencha/resources/themes/images/default/pictos/forward_black.png
  164. BIN sencha/resources/themes/images/default/pictos/globe1.png
  165. BIN sencha/resources/themes/images/default/pictos/globe2.png
  166. BIN sencha/resources/themes/images/default/pictos/globe_black.png
  167. BIN sencha/resources/themes/images/default/pictos/headphones.png
  168. BIN sencha/resources/themes/images/default/pictos/heart.png
  169. BIN sencha/resources/themes/images/default/pictos/heart_circle.png
  170. BIN sencha/resources/themes/images/default/pictos/help.png
  171. BIN sencha/resources/themes/images/default/pictos/help_black.png
  172. BIN sencha/resources/themes/images/default/pictos/home.png
  173. BIN sencha/resources/themes/images/default/pictos/home2.png
  174. BIN sencha/resources/themes/images/default/pictos/hot.png
  175. BIN sencha/resources/themes/images/default/pictos/inbox1.png
  176. BIN sencha/resources/themes/images/default/pictos/inbox2.png
  177. BIN sencha/resources/themes/images/default/pictos/inbox3.png
  178. BIN sencha/resources/themes/images/default/pictos/infinite.png
  179. BIN sencha/resources/themes/images/default/pictos/infinite2.png
  180. BIN sencha/resources/themes/images/default/pictos/info.png
  181. BIN sencha/resources/themes/images/default/pictos/info2.png
  182. BIN sencha/resources/themes/images/default/pictos/info_plain.png
  183. BIN sencha/resources/themes/images/default/pictos/info_plain2.png
  184. BIN sencha/resources/themes/images/default/pictos/json.png
  185. BIN sencha/resources/themes/images/default/pictos/lab.png
  186. BIN sencha/resources/themes/images/default/pictos/layout.png
  187. BIN sencha/resources/themes/images/default/pictos/link1.png
  188. BIN sencha/resources/themes/images/default/pictos/link2.png
  189. BIN sencha/resources/themes/images/default/pictos/link_black.png
  190. BIN sencha/resources/themes/images/default/pictos/list.png
  191. BIN sencha/resources/themes/images/default/pictos/locate.png
  192. BIN sencha/resources/themes/images/default/pictos/locate1.png
  193. BIN sencha/resources/themes/images/default/pictos/locate2.png
  194. BIN sencha/resources/themes/images/default/pictos/locate3.png
  195. BIN sencha/resources/themes/images/default/pictos/locate4.png
  196. BIN sencha/resources/themes/images/default/pictos/lock_closed.png
  197. BIN sencha/resources/themes/images/default/pictos/lock_open.png
  198. BIN sencha/resources/themes/images/default/pictos/look.png
  199. BIN sencha/resources/themes/images/default/pictos/loop.png
  200. BIN sencha/resources/themes/images/default/pictos/loop2.png
  201. BIN sencha/resources/themes/images/default/pictos/loop_circle.png
  202. BIN sencha/resources/themes/images/default/pictos/magic.png
  203. BIN sencha/resources/themes/images/default/pictos/mail.png
  204. BIN sencha/resources/themes/images/default/pictos/mail1.png
  205. BIN sencha/resources/themes/images/default/pictos/mail2.png
  206. BIN sencha/resources/themes/images/default/pictos/mail3.png
  207. BIN sencha/resources/themes/images/default/pictos/mail4.png
  208. BIN sencha/resources/themes/images/default/pictos/mail5.png
  209. BIN sencha/resources/themes/images/default/pictos/maps.png
  210. BIN sencha/resources/themes/images/default/pictos/mic.png
  211. BIN sencha/resources/themes/images/default/pictos/minus1.png
  212. BIN sencha/resources/themes/images/default/pictos/minus2.png
  213. BIN sencha/resources/themes/images/default/pictos/minus_black1.png
  214. BIN sencha/resources/themes/images/default/pictos/minus_black2.png
  215. BIN sencha/resources/themes/images/default/pictos/monitor1.png
  216. BIN sencha/resources/themes/images/default/pictos/monitor2.png
  217. BIN sencha/resources/themes/images/default/pictos/monitor3.png
  218. BIN sencha/resources/themes/images/default/pictos/monitor4.png
  219. BIN sencha/resources/themes/images/default/pictos/more.png
  220. BIN sencha/resources/themes/images/default/pictos/mouse.png
  221. BIN sencha/resources/themes/images/default/pictos/move.png
  222. BIN sencha/resources/themes/images/default/pictos/music1.png
  223. BIN sencha/resources/themes/images/default/pictos/music2.png
  224. BIN sencha/resources/themes/images/default/pictos/nodes1.png
  225. BIN sencha/resources/themes/images/default/pictos/nodes2.png
  226. BIN sencha/resources/themes/images/default/pictos/note1.png
  227. BIN sencha/resources/themes/images/default/pictos/note2.png
  228. BIN sencha/resources/themes/images/default/pictos/note3.png
  229. BIN sencha/resources/themes/images/default/pictos/note_black.png
  230. BIN sencha/resources/themes/images/default/pictos/nuclear.png
  231. BIN sencha/resources/themes/images/default/pictos/organize.png
  232. BIN sencha/resources/themes/images/default/pictos/outbox.png
  233. BIN sencha/resources/themes/images/default/pictos/pause.png
  234. BIN sencha/resources/themes/images/default/pictos/phone1.png
  235. BIN sencha/resources/themes/images/default/pictos/phone2.png
  236. BIN sencha/resources/themes/images/default/pictos/phone_black.png
  237. BIN sencha/resources/themes/images/default/pictos/phone_ring1.png
  238. BIN sencha/resources/themes/images/default/pictos/phone_ring2.png
  239. BIN sencha/resources/themes/images/default/pictos/photo1.png
  240. BIN sencha/resources/themes/images/default/pictos/photo2.png
  241. BIN sencha/resources/themes/images/default/pictos/photo3.png
  242. BIN sencha/resources/themes/images/default/pictos/photo_black1.png
  243. BIN sencha/resources/themes/images/default/pictos/photo_black2.png
  244. BIN sencha/resources/themes/images/default/pictos/photos1.png
  245. BIN sencha/resources/themes/images/default/pictos/photos2.png
  246. BIN sencha/resources/themes/images/default/pictos/photos4.png
  247. BIN sencha/resources/themes/images/default/pictos/piechart.png
  248. BIN sencha/resources/themes/images/default/pictos/play1.png
  249. BIN sencha/resources/themes/images/default/pictos/play2.png
  250. BIN sencha/resources/themes/images/default/pictos/play_black1.png
  251. BIN sencha/resources/themes/images/default/pictos/play_black2.png
  252. BIN sencha/resources/themes/images/default/pictos/podcast.png
  253. BIN sencha/resources/themes/images/default/pictos/power socket.png
  254. BIN sencha/resources/themes/images/default/pictos/power_on.png
  255. BIN sencha/resources/themes/images/default/pictos/print.png
  256. BIN sencha/resources/themes/images/default/pictos/print2.png
  257. BIN sencha/resources/themes/images/default/pictos/quote1.png
  258. BIN sencha/resources/themes/images/default/pictos/quote2.png
  259. BIN sencha/resources/themes/images/default/pictos/quote_black1.png
  260. BIN sencha/resources/themes/images/default/pictos/quote_black2.png
  261. BIN sencha/resources/themes/images/default/pictos/quote_black3.png
  262. BIN sencha/resources/themes/images/default/pictos/refresh.png
  263. BIN sencha/resources/themes/images/default/pictos/refresh1.png
  264. BIN sencha/resources/themes/images/default/pictos/refresh2.png
  265. BIN sencha/resources/themes/images/default/pictos/refresh3.png
  266. BIN sencha/resources/themes/images/default/pictos/refresh5.png
  267. BIN sencha/resources/themes/images/default/pictos/reply.png
  268. BIN sencha/resources/themes/images/default/pictos/replytoall.png
  269. BIN sencha/resources/themes/images/default/pictos/resize.png
  270. BIN sencha/resources/themes/images/default/pictos/resize_black.png
  271. BIN sencha/resources/themes/images/default/pictos/rewind.png
  272. BIN sencha/resources/themes/images/default/pictos/right.png
  273. BIN sencha/resources/themes/images/default/pictos/right2.png
  274. BIN sencha/resources/themes/images/default/pictos/rss.png
  275. BIN sencha/resources/themes/images/default/pictos/rss2.png
  276. BIN sencha/resources/themes/images/default/pictos/rss_black.png
  277. BIN sencha/resources/themes/images/default/pictos/rss_black1.png
  278. BIN sencha/resources/themes/images/default/pictos/rss_black2.png
  279. BIN sencha/resources/themes/images/default/pictos/screens.png
  280. BIN sencha/resources/themes/images/default/pictos/search.png
  281. BIN sencha/resources/themes/images/default/pictos/search1.png
  282. BIN sencha/resources/themes/images/default/pictos/search2.png
  283. BIN sencha/resources/themes/images/default/pictos/search_black.png
  284. BIN sencha/resources/themes/images/default/pictos/server.png
  285. BIN sencha/resources/themes/images/default/pictos/servers.png
  286. BIN sencha/resources/themes/images/default/pictos/settings.png
  287. BIN sencha/resources/themes/images/default/pictos/settings1.png
  288. BIN sencha/resources/themes/images/default/pictos/settings10.png
  289. BIN sencha/resources/themes/images/default/pictos/settings11.png
  290. BIN sencha/resources/themes/images/default/pictos/settings3.png
  291. BIN sencha/resources/themes/images/default/pictos/settings4.png
  292. BIN sencha/resources/themes/images/default/pictos/settings5.png
  293. BIN sencha/resources/themes/images/default/pictos/settings6.png
  294. BIN sencha/resources/themes/images/default/pictos/settings7.png
  295. BIN sencha/resources/themes/images/default/pictos/settings8.png
  296. BIN sencha/resources/themes/images/default/pictos/settings9.png
  297. BIN sencha/resources/themes/images/default/pictos/settings_black.png
  298. BIN sencha/resources/themes/images/default/pictos/share.png
  299. BIN sencha/resources/themes/images/default/pictos/shield1.png
  300. BIN sencha/resources/themes/images/default/pictos/shield2.png
Sorry, we could not display the entire diff because too many files (383) changed.
View
4 README.txt
@@ -1,4 +0,0 @@
-
-HTML5 Mobile Web Version of Messagee sample app
-
-
View
33 app/app.js
@@ -1,33 +0,0 @@
-Ext.regApplication({
- name: 'app',
- appFolder: 'app',
- launch: function() {
- this.launched = true;
- this.mainLaunch();
- },
- mainLaunch: function() {
- if ((on_device && !window.device) || !this.launched) {return;}
- client = new usergrid.Client();
- client.currentOrganization = org_name;
- this.views.viewport = new this.views.Viewport();
- refreshMessagesLoad=true;
-
- if(app.stores.users.first()!= undefined) {
- var user= app.stores.users.first();
-
- appUser= user.username;
- console.log('User found ' + appUser);
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
- } else {
- console.log('User not found');
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'showsettings',
- animation: {type:'slide', direction:'up'}
- });
- }
- }
-});
View
182 app/controllers/MsgController.js
@@ -1,182 +0,0 @@
-var app_name = 'MessageeApp';
-var org_name = 'Apigee';
-
-app.controllers.MsgController = new Ext.Controller({
- loadmessages: function(options) {
- app.views.msgPanel.getStore().proxy.url= client.apiUrl + '/' + org_name + '/' + app_name + '/users/' + appUser + '/feed?pos=end&prev=10&access_token=' + client.accessToken;
- app.views.msgPanel.getStore().load();
-
- if(refreshMessagesLoad) {
- refreshMessages();
- refreshMessagesLoad=false;
- }
- },
- showmessage: function(options) {
- var msg= app.views.msgPanel.getStore().getAt(options.index);
- if(msg.data.lat==undefined) {
- Ext.Msg.alert('Messagee','Message does not contain location.', Ext.emptyFn);
- app.views.msgPanel.getList().getSelectionModel().deselectAll();
- } else {
-
- var pos= new google.maps.LatLng(msg.data.lat, msg.data.lon);
-
- var marker = new google.maps.Marker({
- position: pos,
- title : 'Message Location',
- map: app.views.otherPanel.getMap().map
- });
-
- app.views.otherPanel.getMap().update(pos);
- app.views.viewport.setActiveItem(app.views.otherPanel, options.animation);
- }
- },
- showsettings: function(options) {
- app.views.viewport.setActiveItem(app.views.settingsPanel, options.animation);
- },
- savesettings: function(options) {
- client.loginAppUser(app_name, options.data.username, options.data.password, function(){
- if(options.record.phantom) {
- app.stores.users.create(options.data);
- } else {
- options.record.set(options.data);
- options.record.save();
- }
-
- appUser= client.loggedInUser.username;
-
- /*
- try {
- client.apiRequest('PUT',client.encodePathString('/' + app_name + '/users/' + appUser + '/following/'+ appUser +'-in'), null, '{}', function(res){}, function(res){Ext.Msg.alert('Messagee', 'Auto subscribe failed.', Ext.emptyFn);});
- } catch(e) {
- console.log('HTTP PUT not working');
- }
- */
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
-
- app.views.viewport.setActiveItem(app.views.msgPanel, options.animation);
- },
- function(options) {
- Ext.Msg.alert('Messagee','Invalid Username or Password', Ext.emptyFn);
- });
- },
- /*
- * Creates a new user, then if user creation is sucuessful, will log that user in
- *
- * @param app_name the unique id of the app
- * @param username the username of the user to create
- * @param password the user's password
- */
- newUser: function(options) {
- //attempt to create a new user
- client.createUser(app_name, options.data.username, '', '', options.data.password,
- function(){
- //user was created, so now try to log them in
- client.loginAppUser(app_name, options.data.username, options.data.password,
- function(){
- if(options.record.phantom) {
- app.stores.users.create(options.data);
- } else {
- options.record.set(options.data);
- options.record.save();
- }
-
- appUser= client.loggedInUser.username;
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
-
- app.views.viewport.setActiveItem(app.views.msgPanel, options.animation);
- },
- function(options) {
- Ext.Msg.alert('Messagee','Invalid Username or Password', Ext.emptyFn);
- });
- },
- function(options) {
- Ext.Msg.alert('Messagee','Invalid Username or Password - Account could not be created', Ext.emptyFn);
- });
- },
- backsettings: function(options) {
- app.views.msgPanel.getList().getSelectionModel().deselectAll();
- app.views.viewport.setActiveItem(app.views.msgPanel, options.animation);
- },
- followuser: function() {
- var msg = new Ext.MessageBox().show({
- title: 'Name',
- msg: 'Please enter a username to follow:',
- buttons: Ext.MessageBox.OKCANCEL,
- prompt:{ maxlength : 180, autocapitalize : false },
- modal: true,
- fn: function(btn,text) {
- if(btn=='ok') {
- try {
- console.log(text);
- client.apiRequest('POST',client.encodePathString('/' + org_name + '/' + app_name + '/users/' + appUser + '/following/user/'+ text), null, '{}', function(res){}, function(res){Ext.Msg.alert('Messagee', 'Follow user failed.', Ext.emptyFn);});
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
- } catch(e){
- console.log(e);
- }
- }
- },
- icon: Ext.MessageBox.INFO
- });
- },
- alertuser: function(options) {
- Ext.Msg.alert('Messagee',options.message, Ext.emptyFn);
- },
- newmessage: function(options) {
- app.views.newMsgPanel.reset();
- app.views.viewport.setActiveItem(app.views.newMsgPanel, options.animation);
- },
- send: function(options) {
- var email = '';
- if (client.loggedInUser.email) { email = hex_md5(client.loggedInUser.email); }
- var data= {
- actor: {
- displayName: appUser,
- image : {
- url: "http://www.gravatar.com/avatar/" + email,
- height: 80,
- width: 80
- },
- email: email
- },
- verb: "post",
- content: app.views.newMsgPanel.getText()
- };
-
- navigator.geolocation.getCurrentPosition(function(pos) {
- Ext.apply(data, {lat:pos.coords.latitude, lon: pos.coords.longitude});
-
- client.apiRequest('POST',client.encodePathString('/' + org_name + '/' + app_name + '/users/' + appUser + '/activities'), null, Ext.util.JSON.encode(data),function(res){}, function(res){Ext.Msg.alert('Messagee', 'New message failed.', Ext.emptyFn);});
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
- app.views.viewport.setActiveItem(app.views.msgPanel, options.animation);
- }, function(){
- client.apiRequest('POST',client.encodePathString('/' + org_name + '/' + app_name + '/users/' + appUser + '/activities'), null, Ext.util.JSON.encode(data),function(res){}, function(res){Ext.Msg.alert('Messagee', 'New message failed.', Ext.emptyFn);});
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'loadmessages'
- });
- app.views.viewport.setActiveItem(app.views.msgPanel, options.animation);
- });
- }
-});
-
-function refreshMessages() {
- app.views.msgPanel.getStore().load();
- setTimeout('refreshMessages()', 10000);
-}
View
16 app/models/Activity.js
@@ -1,16 +0,0 @@
-app.models.Activity = Ext.regModel("app.models.Activity", {
- fields: [
- {name: "uuid", type: "string"},
- {name: "type", type: "string"},
- {name: "verb", type: "string"},
- {name: "actor", type: "auto" },
- {name: "content", type: "string"},
- {name: "lat", type: "float"},
- {name: "lon", type: "float"},
- {name: "published", type: "published"},
- ],
- proxy: {
- type:'localstorage',
- id:'feed'
- }
-});
View
11 app/models/Users.js
@@ -1,11 +0,0 @@
-app.models.Users = Ext.regModel("app.models.Users", {
- fields: [
- {name: "username", type: "string"},
- {name: "password", type: "string"}
- ],
-
- proxy: {
- type: 'localstorage',
- id: 'users'
- }
-});
View
3 app/stores/Users.js
@@ -1,3 +0,0 @@
-app.stores.users = new Ext.data.Store({
- model: "app.models.Users"
-});
View
107 app/views/MsgPanel.js
@@ -1,107 +0,0 @@
-var newMessageButton = new Ext.Button({
- iconMask: true,
- iconCls: 'compose',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'newmessage',
- animation: {type:'slide', direction:'down'}
- });
- }
-});
-
-var topToolbar = new Ext.Toolbar({
- dock : 'top',
- ui: 'dark',
- title: 'Messagee',
- items: [{xtype:'spacer'}, newMessageButton]
-});
-
-var store = new Ext.data.Store({
- model: 'app.models.Activity',
- proxy: new Ext.data.HttpProxy({
- method: 'get',
- reader:{
- root: 'entities',
- totalproperty: 'length',
- type: 'json'
- },
- }),
-});
-
-var tpl = new Ext.XTemplate(
- '<div id="tweet_container">',
- '<tpl for=".">',
- '<div class="tweet_data">',
- '<div class="tweet_avatar">',
- '<img width="30" height="30" src="{[this.getActorImage(values)]}"/>',
- '</div>',
- '<div class="tweet_content">',
- '<div class="user">{actor.displayName}</div>&nbsp;',
- '{content}',
- '</div>',
- '<div class="clear"></div>',
- '</div>',
- '</tpl>',
- '</div>', {
- getActorImage: function(values) {
- if (values.actor && values.actor.image && values.actor.image.url) return values.actor.image.url;
- return "http://www.gravatar.com/avatar/?";
- },
-});
-
-var listPanel = new Ext.List({
- store: store,
- loadingText: null,
- scroll: 'vertical',
- itemTpl: tpl,
- listeners: {
- scope:this,
- itemtap: function(view, index, item, e) {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'showmessage',
- index:index
- });
- }
- }
-});
-
-var followButton = new Ext.Button({
- iconMask: true,
- iconCls: 'user',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'followuser'
- });
- }
-});
-
-var settingsButton = new Ext.Button({
- iconMask: true,
- iconCls: 'settings',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'showsettings',
- animation: {type:'slide', direction:'up'}
- });
- }
-});
-
-var bottomToolbar = new Ext.Toolbar({
- dock : 'bottom',
- ui: 'light',
- items: [followButton, {xtype:'spacer'}, settingsButton]
-});
-
-app.views.MsgPanel= Ext.extend(Ext.Panel, {
- layout: 'fit',
- dockedItems: [topToolbar, bottomToolbar],
- items: [listPanel],
- style: 'background: #DDEEF6;',
- getList: function() {return listPanel;},
- getStore: function() {return store;}
-});
-
View
53 app/views/NewMsgPanel.js
@@ -1,53 +0,0 @@
-app.views.NewMsgPanel= Ext.extend(Ext.Panel, {
- layout: 'fit',
- items: [],
- style: 'background: #DDEEF6;',
- initComponent: function() {
- var cancelButton = new Ext.Button({
- ui: 'light',
- text: 'Cancel',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'backsettings',
- animation: {type:'slide', direction:'up'}
- });
- }
- });
-
-
- var msgArea= new Ext.form.TextArea({
- layout: 'fit'
- });
-
- var sendButton = new Ext.Button({
- ui: 'light',
- text: 'Send',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'send',
- animation: {type:'slide', direction:'up'}
- });
- }
- });
-
- var actionToolbar = new Ext.Toolbar({
- dock : 'top',
- ui: 'light',
- title: 'New Message',
- items: [cancelButton,{xtype:'spacer'},sendButton]
- });
-
- var fieldset= new Ext.form.FieldSet({
- items: msgArea,
- title: 'Write up to 150 characters'
- });
-
-
- Ext.apply(this, {dockedItems: actionToolbar, items: fieldset, getText: function() {return msgArea.getValue();}, reset: function() {msgArea.reset();}});
-
- app.views.NewMsgPanel.superclass.initComponent.call(this);
- }
-});
-
View
37 app/views/OtherPanel.js
@@ -1,37 +0,0 @@
-var backButton = new Ext.Button({
- ui: 'back',
- text: 'Back',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'backsettings',
- animation: {type:'slide', direction:'right'}
- }); }
-});
-
-var backToolbar = new Ext.Toolbar({
- dock : 'top',
- ui: 'light',
- items: backButton
-});
-
-var map= new Ext.Map({
- mapOptions: { // Used in rendering map
- zoom: 12,
- useCurrentLocation:true,
- mapTypeId : google.maps.MapTypeId.ROADMAP,
- navigationControl: true,
- navigationControlOptions: {
- style: google.maps.NavigationControlStyle.DEFAULT
- },
- }
-});
-
-app.views.OtherPanel= Ext.extend(Ext.Panel, {
- dockedItems: [backToolbar],
- scroll: 'vertical',
- layout:'fit',
- items: [map],
- getMap: function(){return map;},
- style: 'background: #FFFFFF;'
-});
View
87 app/views/SettingsPanel.js
@@ -1,87 +0,0 @@
-app.views.SettingsPanel = Ext.extend(Ext.form.FormPanel,{
- items: [{
- xtype: 'fieldset',
- title: 'Usergrid Login Information',
- instructions: 'Please enter your username & password.',
- defaults: {
- required: true,
- labelAlign: 'left',
- labelWidth: '40%'
- },
- items: [{
- xtype: 'textfield',
- name : 'username',
- label: 'Username',
- useClearIcon: true,
- autoCapitalize : false
- },{
- xtype: 'passwordfield',
- name : 'password',
- label: 'Password',
- useClearIcon: true,
- autoCapitalize : false
- }]
- }],
- initComponent: function() {
- var cancelButton = new Ext.Button({
- ui: 'decline',
- text: 'Cancel',
- handler: function() {
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'backsettings',
- animation: {type:'slide', direction:'down'}
- });
- }
- });
-
- var okButton = new Ext.Button({
- ui: 'confirm',
- text: 'Sign In',
- handler: this.onOkAction,
- scope:this
- });
-
- var newButton = new Ext.Button({
- ui: 'confirm',
- text: 'New Account',
- handler: this.onNewAction,
- scope:this
- });
-
- var settingsToolbar = new Ext.Toolbar({
- dock : 'bottom',
- ui: 'light',
- items: [cancelButton,{xtype:'spacer'},okButton, newButton]
- });
-
- Ext.apply(this, {dockedItems: [{xtype:'toolbar', dock:'top', title:'User Settings'},settingsToolbar]});
-
- app.views.SettingsPanel.superclass.initComponent.call(this);
- },
- onOkAction: function() {
- var model= this.getRecord();
- if(model== undefined) {
- model= new app.models.Users();
- }
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'savesettings',
- data: this.getValues(),
- record: model,
- animation: {type:'slide', direction:'down'}
- });
- },
- onNewAction: function() {
- var model = new app.models.Users();
-
- Ext.dispatch({
- controller: app.controllers.MsgController,
- action: 'newUser',
- data: this.getValues(),
- record: model,
- animation: {type:'slide', direction:'down'}
- });
- }
-
-});
View
24 app/views/Viewport.js
@@ -1,24 +0,0 @@
-app.views.Viewport = Ext.extend(Ext.Panel, {
- fullscreen: true,
- layout: 'card',
- cardSwitchAnimation: 'slide',
- initComponent: function() {
- //put instances of cards into app.views namespace
- Ext.apply(app.views, {
- msgPanel: new app.views.MsgPanel(),
- otherPanel: new app.views.OtherPanel(),
- settingsPanel: new app.views.SettingsPanel(),
- newMsgPanel: new app.views.NewMsgPanel()
- });
- //put instances of cards into viewport
- Ext.apply(this, {
- items: [
- app.views.msgPanel,
- app.views.otherPanel,
- app.views.settingsPanel,
- app.views.newMsgPanel
- ]
- });
- app.views.Viewport.superclass.initComponent.apply(this, arguments);
- }
-});
View
222 index.html
@@ -1,63 +1,163 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
<html>
- <head>
- <meta name="viewport" content="width=320; user-scalable=no" />
- <meta http-equiv="Content-type" content="text/html; charset=utf-8">
- <title>Messagee</title>
- <link rel="stylesheet" href="sencha/resources/css/sencha-touch.css" type="text/css">
- <style type="text/css">
- #tweet_container {
- margin: 10px;
- padding: 5px;
- -webkit-border-radius: .5em;
- -moz-border-radius: .5em;
- border-radius: .5em;
- border: 1px solid #8BBCD6;
- }
- .clear {
- clear: both;
- }
- .tweet_data {
- padding: 5px;
- }
- .tweet_avatar {
- float: left;
- width: 40px;
- }
- .tweet_content {
- float: left;
- width: 235px;
- line-height: 16px;
- color: #333;
- font-size: 11px;
- }
- .user {
- font-weight: bold;
- color: #8BBCD6;
- text-decoration: none;
- }
- .outlink {
- color: #0099cc;
- text-decoration: none;
- }
- </style>
- <script type="text/javascript" src="sencha/sencha-touch-debug.js"></script>
- <script type="text/javascript" charset="utf-8" src="phonegap-1.0.0.js"></script>
- <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
- <script type="text/javascript" charset="utf-8" src="main.js"></script>
- <script type="text/javascript" src="app/app.js"></script>
- <script type="text/javascript" src="md5.js"></script>
- <script type="text/javascript" src="app/models/Activity.js"></script>
- <script type="text/javascript" src="app/models/Users.js"></script>
- <script type="text/javascript" src="app/stores/Users.js"></script>
- <script type="text/javascript" src="app/controllers/MsgController.js"></script>
- <script type="text/javascript" src="app/views/Viewport.js"></script>
- <script type="text/javascript" src="app/views/MsgPanel.js"></script>
- <script type="text/javascript" src="app/views/OtherPanel.js"></script>
- <script type="text/javascript" src="app/views/SettingsPanel.js"></script>
- <script type="text/javascript" src="app/views/NewMsgPanel.js"></script>
- <script type="text/javascript" src="usergrid.client.js"></script>
- <script type="text/javascript">document.addEventListener("deviceready", app.mainLaunch, false);</script>
- </head>
- <body></body>
+ <head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" />
+ <style type="text/css">.error{ background-color: #ffaaaa; } </style>
+ <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
+ <script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
+ <script src="js/app.js" type="text/javascript"></script>
+ <script src="js/apigee.appSDK.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ //first set the org / app path
+ apigee.ApiClient.init('Apigee', 'messageeapp'); //<== update this line with the org name you signed up with
+
+ </script>
+ </head>
+ <body>
+
+ <div data-role="page" data-theme="b" id="page-login">
+ <div data-role="header" data-theme="b">
+ <h1>Messagee</h1>
+ </div>
+ <div data-role="content">
+ <p><strong>Messagee is a sample messaging app, like Twitter</strong></p>
+ <p>Log in using your Messagee account, or create a new one!</p>
+ <span id="login-section-error"></span>
+ <form name="form-login" id="form-login">
+ <label for="username">Username</label>
+ <input type="text" name="username" id="username" class="span4" />
+ <label for="password">Password</label>
+ <input type="password" name="password" id="password" class="span4" />
+ </form>
+ <div style="width: 50%; float: left">
+ <a href="#login" id="btn-login" data-role="button">Login</a>
+ </div>
+ <div style="width: 50%; float: right">
+ <a href="#page-new-account" id="btn-show-create-new-account" data-role="button" data-role="button" data-rel="dialog" data-transition="pop">New Account</a>
+ </div>
+ </div>
+ <div data-role="footer" data-position="fixed">
+ <h1>&copy; Apigee</h1>
+ </div>
+ </div>
+
+ <div data-role="page" data-theme="b" id="page-new-account">
+ <div data-role="header" data-theme="b">
+ <h1>Messagee</h1>
+ </div>
+ <div data-role="content">
+ <p>Account Info</p>
+ <form name="form-create-new-account" id="form-create-new-account">
+ <label for="new-name">Name</label>
+ <input type="text" name="new-name" id="new-name" class="span4" />
+ <label for="new-email">Email</label>
+ <input type="text" name="new-email" id="new-email" class="span4" />
+ <label for="new-username">Username</label>
+ <input type="text" name="new-username" id="new-username" class="span4" />
+ <label for="new-password">Password</label>
+ <input type="password" name="new-password" id="new-password" class="span4" />
+ </form>
+ <div style="width: 50%; float: left">
+ <button id="btn-create-new-account">Go!</button>
+ </div>
+ <div style="width: 50%; float: right">
+ <a href="#login" id="btn-close-update-account-page" data-role="button">Close</a>
+ </div>
+ </div>
+ </div>
+
+
+ <div data-role="page" data-theme="b" id="page-update-account">
+ <div data-role="header" data-theme="b">
+ <h1>Messagee</h1>
+ </div>
+ <div data-role="content">
+ <div id="user-message-update-account"></div>
+ <p>Account Info</p>
+ <form name="form-create-new-account" id="form-create-new-account">
+ <label for="update-name">Name</label>
+ <input type="text" name="update-name" id="update-name" class="span4" />
+ <label for="update-email">Email</label>
+ <input type="text" name="update-email" id="update-email" class="span4" />
+ <label for="update-username">Username</label>
+ <input type="text" name="update-username" id="update-username" class="span4" />
+ <label for="update-oldpassword">Current Password</label>
+ <input type="password" name="update-oldpassword" id="update-oldpassword" class="span4" />
+ <label for="update-newpassword">New Password</label>
+ <input type="password" name="update-newpassword" id="update-newpassword" class="span4" />
+ </form>
+ <div style="width: 50%; float: left">
+ <button id="btn-update-account">Update</button>
+ </div>
+ <div style="width: 50%; float: right">
+ <a href="#page-messages-list" id="btn-close-update-account-page" data-role="button">Close</a>
+ </div>
+ </div>
+ </div>
+
+
+ <div data-role="page" data-theme="b" id="page-messages-list">
+ <div data-role="header" data-theme="b">
+ <h1>Messagee</h1>
+ <a href="#login" id="btn-logout" data-role="button">Logout</a>
+ <a href="#page-update-account" id="btn-show-page-update-account" data-role="button" data-icon="gear" data-rel="dialog" data-transition="fade">Settings</a>
+ </div>
+ <a href="#page-new-message" id="btn-show-create-message" data-role="button" data-rel="dialog" data-transition="fade">Compose Message</a>
+ <div style="width: 50%; float: left">
+ <a href="#" data-role="button" data-icon="home" id="btn-show-my-feed">My Stream</a>
+ </div>
+ <div style="width: 50%; float: right">
+ <a href="#" data-role="button" data-icon="grid" id="btn-show-full-feed" class="ui-btn-up-c">All Posts</a>
+ </div>
+ <div id="messages-list" data-role="content">
+ No messages yet!
+ </div>
+ <div id="feed-btn-container" data-role="content">
+ <div style="width: 50%; float: left" id="previous-btn-container">
+ <button id="btn-previous" data-role="button" data-icon="arrow-l">Previous</button>
+ </div>
+ <div style="width: 50%; float: right" id="next-btn-container">
+ <button id="btn-next" data-role="button" data-icon="arrow-r" data-iconpos="right">Next</button>
+ </div>
+ </div>
+ <div data-role="footer" data-position="fixed">
+ <h1>&copy; Apigee</h1>
+ </div>
+ </div>
+
+
+ <div data-role="page" data-theme="b" id="page-new-message">
+ <div data-role="header" data-theme="b">
+ <h1>Create a new message</h1>
+ </div>
+ <div data-role="content">
+ <form name="login" id="login-form">
+ <label for="content">Message (150 Chars max)</label>
+ <textarea id="content" class="input-xlarge" rows="3" style="margin: 0px; width: 100%; height: 125px; "></textarea>
+ </form>
+ <div style="width: 50%; float: left">
+ <button id="post-message">Post</button>
+ </div>
+ <div style="width: 50%; float: right">
+ <a href="#page-messages-list" id="btn-close-update-account-page" data-role="button">Cancel</a>
+ </div>
+ </div>
+ </div>
+
+
+ <div data-role="page" data-theme="b" id="page-now-following">
+ <div data-role="header" data-theme="b">
+ <h1>Congratulations!</h1>
+ </div>
+ <div data-role="content">
+ <div id="now-following-text" style="height: 75px;"></div>
+ <a href="#" data-rel="back" data-role="button" data-icon="delete">OK</a>
+ </div>
+ </div>
+
+
+ </body>
</html>
View
2,208 js/apigee.appSDK.js
@@ -0,0 +1,2208 @@
+/**
+ * App SDK is a collection of classes designed to make working with
+ * the Appigee App Services API as easy as possible.
+ * Learn more at http://apigee.com/docs
+ *
+ * Copyright 2012 Apigee Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//define the console.log for IE
+window.console = window.console || {};
+window.console.log = window.console.log || function() {};
+
+//apigee namespace encapsulates this SDK
+window.apigee = window.apigee || {};
+apigee = apigee || {};
+apigee.SDK_VERSION = '0.9.1';
+
+/*
+ * apigee.ApiClient
+ *
+ * A Singleton that is the main client for making calls to the API. Maintains
+ * state between calls for the following items:
+ *
+ * Token
+ * Organization Name
+ * Application Name
+ * Application User userame
+ * Application User email
+ * Application User UUID
+ *
+ * Main methods for making calls to the API are:
+ *
+ * runAppQuery (queryObj)
+ * runManagementQuery(queryObj)
+ *
+ * Create a new apigee.QueryObject and then pass it to either of these
+ * two methods for making calls directly to the API.
+ *
+ * Several convenience methods exist for making app user related calls easier. These are:
+ *
+ * loginAppUser (username, password, successCallback, failureCallback)
+ * updateAppUser(uuid, name, email, username, oldpassword, newpassword, data, successCallback, failureCallback)
+ * createAppUser(name, email, username, password, data, successCallback, failureCallback)
+ *
+ * @class apigee.ApiClient
+ * @author Rod Simpson (rod@apigee.com)
+ *
+ */
+apigee.ApiClient = (function () {
+ //API endpoint
+ var _apiUrl = "http://api.usergrid.com/";
+ //var _apiUrl = "http://ug-developer-testing.elasticbeanstalk.com/";
+ var _orgName = null;
+ var _orgUUID = null;
+ var _appName = null;
+ var _token = null;
+ var _appUserUsername = null;
+ var _appUserName = null;
+ var _appUserEmail = null;
+ var _appUserUUID = null;
+
+ /*
+ * A method to set up the ApiClient with orgname and appname
+ * @method init
+ * @param {string} orgName
+ * @param {string} appName
+ * @return none
+ *
+ */
+ function init(orgName, appName){
+ _orgName = orgName;
+ _appName = appName;
+ }
+
+ /*
+ * A public method to get the organization name to be used by the client
+ * @method getOrganizationName
+ * @return {string} the organization name
+ */
+ function getOrganizationName() {
+ return _orgName;
+ }
+
+ /*
+ * A public method to set the organization name to be used by the client
+ * @method setOrganizationName
+ * @param orgName - the organization name
+ * @return none
+ */
+ function setOrganizationName(orgName) {
+ _orgName = orgName;
+ }
+
+ /*
+ * A public method to get the organization UUID to be used by the client
+ * @method getOrganizationUUID
+ * @return {string} the organization UUID
+ */
+ function getOrganizationUUID() {
+ return _orgUUID;
+ }
+
+ /*
+ * A public method to set the organization UUID to be used by the client
+ * @method setOrganizationUUID
+ * @param orgUUID - the organization UUID
+ * @return none
+ */
+ function setOrganizationUUID(orgUUID) {
+ _orgUUID = orgUUID;
+ }
+
+ /*
+ * A public method to get the application name to be used by the client
+ * @method getApplicationName
+ * @return {string} the application name
+ */
+ function getApplicationName() {
+ return _appName;
+ }
+
+ /*
+ * A public method to set the application name to be used by the client
+ * @method setApplicationName
+ * @param appName - the application name
+ * @return none
+ */
+ function setApplicationName(appName) {
+ _appName = appName;
+ }
+
+ /*
+ * A public method to get the token to be used by the client
+ * @method getToken
+ * @return {string} the current token
+ */
+ function getToken() {
+ return _token;
+ }
+
+ /*
+ * A public method to set the token to be used by the client
+ * @method setToken
+ * @param token - the bearer token
+ * @return none
+ */
+ function setToken(token) {
+ _token = token;
+ }
+
+ /*
+ * A public method to get the app user's username to be used by the client
+ * @method getAppUserUsername
+ * @return {string} the app user's username
+ */
+ function getAppUserUsername() {
+ return _appUserUsername;
+ }
+
+ /*
+ * A public method to set the app user's username to be used by the client
+ * @method setAppUserUsername
+ * @param appUserUsername - the app user's username
+ * @return none
+ */
+ function setAppUserUsername(appUserUsername) {
+ _appUserUsername = appUserUsername;
+ }
+
+ /*
+ * method to get the app user's name to be used by the client
+ * @method getAppUserName
+ * @return {string} the app user's name
+ */
+ function getAppUserFullName() {
+ return _appUserName;
+ }
+
+ /*
+ * A public method to set the app user's name to be used by the client
+ * @method setAppUserName
+ * @param appUserName - the app user's name
+ * @return none
+ */
+ function setAppUserFullName(appUserName) {
+ _appUserName = appUserName;
+ }
+
+ /*
+ * A public method to get the app user's email to be used by the client
+ * @method getAppUserEmail
+ * @return {string} the app user's email
+ */
+ function getAppUserEmail() {
+ return _appUserEmail;
+ }
+
+ /*
+ * A public method to set the app user's email to be used by the client
+ * @method setAppUserEmail
+ * @param appUserEmail - the app user's email
+ * @return none
+ */
+ function setAppUserEmail(appUserEmail) {
+ _appUserEmail = appUserEmail;
+ }
+
+ /*
+ * A public method to get the app user's UUID to be used by the client
+ * @method getAppUserUUID
+ * @return {string} the app users' UUID
+ */
+ function getAppUserUUID() {
+ return _appUserUUID;
+ }
+
+ /*
+ * A public method to set the app user's UUID to be used by the client
+ * @method setAppUserUUID
+ * @param appUserUUID - the app user's UUID
+ * @return none
+ */
+ function setAppUserUUID(appUserUUID) {
+ _appUserUUID = appUserUUID;
+ }
+
+ /*
+ * A public method to return the API URL
+ * @method getApiUrl
+ * @return {string} the API url
+ */
+ function getApiUrl() {
+ return _apiUrl
+ }
+
+ /*
+ * A public method to overide the API url
+ * @method setApiUrl
+ * @return none
+ */
+ function setApiUrl(apiUrl) {
+ _apiUrl = apiUrl;
+ }
+
+ /*
+ * A public method to get the api url of the reset pasword endpoint
+ * @method getResetPasswordUrl
+ * @return {string} the api rul of the reset password endpoint
+ */
+ function getResetPasswordUrl() {
+ this.getApiUrl() + "/management/users/resetpw"
+ }
+
+ /*
+ * A public method to run calls against the app endpoint
+ * @method runAppQuery
+ * @params {object} apigee.QueryObj - {method, path, jsonObj, params, successCallback, failureCallback}
+ * @return none
+ */
+ function runAppQuery (QueryObj) {
+ var endpoint = "/" + this.getOrganizationName() + "/" + this.getApplicationName() + "/";
+ this.processQuery(QueryObj, endpoint);
+ }
+
+ /*
+ * A public method to run calls against the management endpoint
+ * @method runManagementQuery
+ * @params {object} apigee.QueryObj - {method, path, jsonObj, params, successCallback, failureCallback}
+ * @return none
+ */
+ function runManagementQuery (QueryObj) {
+ var endpoint = "/management/";
+ this.processQuery(QueryObj, endpoint)
+ }
+
+ /*
+ * A public method to log in an app user - stores the token for later use
+ * @method loginAppUser
+ * @params {string} username
+ * @params {string} password
+ * @params {function} successCallback
+ * @params {function} failureCallback
+ * @return {response} callback functions return API response object
+ */
+ function loginAppUser (username, password, successCallback, failureCallback) {
+ var self = this;
+ var data = {"username": username, "password": password, "grant_type": "password"};
+ this.runAppQuery(new apigee.QueryObj('GET', 'token', null, data,
+ function (response) {
+ self.setAppUserUsername(response.user.username);
+ self.setAppUserFullName(response.user.name);
+ self.setAppUserFullName(response.user.givenName + response.user.familyName);
+ self.setAppUserEmail(response.user.email);
+ self.setAppUserUUID(response.user.uuid);
+ self.setToken(response.access_token);
+ if (successCallback && typeof(successCallback) == "function") {
+ successCallback(response);
+ }
+ },
+ function (response) {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(response);
+ }
+ }
+ ));
+ }
+
+ /*
+ * A public method to update an app user - currently does password update and user
+ * data update as two separate calls, but this will be changed to one call when the
+ * API is updated to support this
+ * @method updateAppUser
+ * @params {string} uuid
+ * @params {string} name
+ * @params {string} email
+ * @params {string} username
+ * @params {string} oldpassword
+ * @params {string} newpassword
+ * @params {function} successCallback
+ * @params {function} failureCallback
+ * @return {response} callback functions return API response object
+ */
+ function updateAppUser(uuid, name, email, username, oldpassword, newpassword, data, successCallback, failureCallback) {
+ var self = this;
+ var data = data || {}
+ data.username = username;
+ data.email = email;
+ data.name = name;
+
+ var pwdata = {};
+ if (oldpassword) { pwdata.oldpassword = oldpassword; }
+ if (newpassword) { pwdata.newpassword = newpassword; }
+ //Note: we have ticket in to change PUT calls to /users to accept the password change
+ // once that is done, we will remove this call and merge it all into one
+ if (oldpassword && newpassword) {
+ this.runAppQuery(new apigee.QueryObj('PUT', 'users/'+uuid+'/password', pwdata, null,
+ function (response) {
+
+ },
+ function (response) {
+
+ }
+ ));
+ }
+ this.runAppQuery(new apigee.QueryObj('PUT', 'users/'+uuid+'', data, null,
+ function (response) {
+ var user = response.entities[0];
+ self.setAppUserUsername(user.username);
+ self.setAppUserFullName(user.givenName + user.familyName);
+ self.setAppUserEmail(user.email);
+ self.setAppUserUUID(user.uuid);
+ if (successCallback && typeof(successCallback) == "function") {
+ successCallback(response);
+ }
+ },
+ function (response) {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(response);
+ }
+ }
+ ));
+ }
+
+ /**
+ * A public method to create an app user
+ * @method createAppUser
+ * @param {string} name
+ * @param {string} email
+ * @param {string} username
+ * @param {string} password
+ * @param {object} data
+ * @param {function} successCallback
+ * @param {function} failureCallback
+ * @return {response} callback functions return API response object
+ */
+ function createAppUser(name, email, username, password, data, successCallback, failureCallback) {
+ var self = this;
+ var data = data || {}
+ data.username = username;
+ data.password = password;
+ data.email = email;
+ data.name = name;
+ this.runAppQuery(new apigee.QueryObj('POST', 'users', data, null,
+ function (response) {
+ var user = response.entities[0];
+ self.setAppUserUsername(user.username);
+ self.setAppUserFullName(user.givenName + user.familyName);
+ self.setAppUserEmail(user.email);
+ self.setAppUserUUID(user.uuid);
+ if (successCallback && typeof(successCallback) == "function") {
+ successCallback(response);
+ }
+ },
+ function (response) {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(response);
+ }
+ }
+ ));
+ }
+
+ /*
+ * TODO: NOT IMPLEMENTED YET - A method to renew an app user's token
+ * @method renewAppUserToken
+ * @return none
+ */
+ function renewAppUserToken() {
+
+ }
+
+ /**
+ * A public method to log out an app user - clears all user fields from client
+ * @method logoutAppUser
+ * @return none
+ */
+ function logoutAppUser() {
+ this.setAppUserUsername(null);
+ this.setAppUserFullName(null);
+ this.setAppUserEmail(null);
+ this.setAppUserUUID(null);
+ this.setToken(null);
+ }
+
+ /**
+ * A public method to test if a user is logged in - does not guarantee that the token is still valid,
+ * but rather that one exists, and that there is a valid UUID
+ * @method isLoggedInAppUser
+ * @params {object} apigee.QueryObj - {method, path, jsonObj, params, successCallback, failureCallback}
+ * @return {boolean} Returns true the user is logged in (has token and uuid), false if not
+ */
+ function isLoggedInAppUser() {
+ return (this.getToken() && apigee.validation.isUUID(this.getAppUserUUID()));
+ }
+
+ /**
+ * This private method should not be called directly!!
+ * It is the main function that validates and prepares a call to the API
+ * Use runAppQuery or runManagementQuery instead
+ * @method processQuery
+ * @params {object} apigee.QueryObj - {method, path, jsonObj, params, successCallback, failureCallback}
+ * @params {string} endpoint - used to differentiate between management and app queries
+ * @return {response} callback functions return API response object
+ */
+ function processQuery (QueryObj, endpoint) {
+ var curl = "curl";
+ //validate parameters
+ try {
+ //verify that the current rendering platform supports XMLHttpRequest
+ if(typeof XMLHttpRequest === 'undefined') {
+ throw(new Error('Ru-rho! XMLHttpRequest is not supported on this device.'));
+ }
+ //verify that the query object is valid
+ if(!(QueryObj instanceof apigee.QueryObj)) {
+ throw(new Error('QueryObj is not a valid object.'));
+ }
+ //peel the data out of the query object
+ var method = QueryObj.getMethod().toUpperCase();
+ var path = QueryObj.getPath();
+ var jsonObj = QueryObj.getJsonObj() || {};
+ var params = QueryObj.getQueryParams() || {};
+
+ //method - should be GET, POST, PUT, or DELETE only
+ if (method != 'GET' && method != 'POST' && method != 'PUT' && method != 'DELETE') {
+ throw(new Error('Invalid method - should be GET, POST, PUT, or DELETE.'));
+ }
+ //curl - add the method to the command (no need to add anything for GET)
+ if (method == "POST") {curl += " -X POST"; }
+ else if (method == "PUT") { curl += " -X PUT"; }
+ else if (method == "DELETE") { curl += " -X DELETE"; }
+ else { curl += " -X GET"; }
+
+ //curl - append the bearer token if this is not the sandbox app
+ var application_name = this.getApplicationName();
+ if (application_name) {
+ application_name = application_name.toUpperCase();
+ }
+ if (application_name != 'SANDBOX' && this.getToken()) {
+ curl += ' -i -H "Authorization: Bearer ' + this.getToken() + '"';
+ QueryObj.setToken(true);
+ }
+
+ //params - make sure we have a valid json object
+ _params = JSON.stringify(params)
+ if (!JSON.parse(_params)) {
+ throw(new Error('Params object is not valid.'));
+ }
+
+ //add in the cursor if one is available
+ if (QueryObj.getCursor()) {
+ params.cursor = QueryObj.getCursor();
+ } else {
+ delete params.cursor;
+ }
+
+ //strip off the leading slash of the endpoint if there is one
+ endpoint = endpoint.indexOf('/') == 0 ? endpoint.substring(1) : endpoint;
+
+ //add the endpoint to the path
+ path = endpoint + path;
+
+ //make sure path never has more than one / together
+ if (path) {
+ //regex to strip multiple slashes
+ while(path.indexOf('//') != -1){
+ path = path.replace('//', '/');
+ }
+ }
+
+ //add the http:// bit on the front
+ path = this.getApiUrl() + path;
+
+ //curl - append the path
+ curl += ' "' + path;
+
+ //curl - append params to the path for curl prior to adding the timestamp
+ var curl_encoded_params = this.encodeParams(params);
+ if (curl_encoded_params) {
+ curl += "?" + curl_encoded_params;
+ }
+ curl += '"';
+
+ //add in a timestamp for gets and deletes - to avoid caching by the browser
+ if ((method == "GET") || (method == "DELETE")) {
+ params['_'] = new Date().getTime();
+ }
+
+ //append params to the path
+ var encoded_params = this.encodeParams(params);
+ if (encoded_params) {
+ path += "?" + encoded_params;
+ }
+
+ //jsonObj - make sure we have a valid json object
+ jsonObj = JSON.stringify(jsonObj)
+ if (!JSON.parse(jsonObj)) {
+ throw(new Error('JSON object is not valid.'));
+ }
+ if (jsonObj == '{}') {
+ jsonObj = null;
+ } else {
+ //curl - add in the json obj
+ curl += " -d '" + jsonObj + "'";
+ }
+
+ } catch (e) {
+ //parameter was invalid
+ console.log('processQuery - error occured -' + e.message);
+ return false;
+ }
+ //log the curl command to the console
+ console.log(curl);
+ //store the curl command back in the object
+ QueryObj.setCurl(curl);
+
+ //so far so good, so run the query
+ var xD = window.XDomainRequest ? true : false;
+ var xM = window.XMLHttpRequest ? true : false;
+ var xhr;
+
+ if(xD)
+ {
+ xhr = new window.XDomainRequest();
+ if (application_name != 'SANDBOX' && this.getToken()) {
+ if (path.indexOf("?")) {
+ path += '&access_token='+this.getToken();
+ } else {
+ path = '?access_token='+this.getToken();
+ }
+ }
+ xhr.open(method, path, true);
+ }
+ else if (xM)
+ {
+ xhr = new XMLHttpRequest();
+ xhr.open(method, path, true);
+ if (application_name != 'SANDBOX' && this.getToken()) {
+ xhr.setRequestHeader("Authorization", "Bearer " + this.getToken());
+ xhr.withCredentials = true;
+ }
+ } else {
+ xhr = new ActiveXObject("MSXML2.XMLHTTP.3.0");
+ if (application_name != 'SANDBOX' && this.getToken()) {
+ if (path.indexOf("?")) {
+ path += '&access_token='+this.getToken();
+ } else {
+ path = '?access_token='+this.getToken();
+ }
+ }
+ xhr.open(method, path, true);
+ }
+
+ // Handle response.
+ xhr.onerror = function() {
+ //network error
+ clearTimeout(timeout);
+ console.log('API call failed at the network level.');
+ QueryObj.callFailureCallback({'error':'error'});
+ };
+ xhr.onload = function() {
+ //call completed
+ clearTimeout(timeout);
+ response = JSON.parse(xhr.responseText);
+ if (xhr.status != 200 && !xD) {
+ //there was an api error
+ var error = response.error;
+ console.log('API call failed: (status: '+xhr.status+').' + error.type);
+
+ if ( (error.type == "auth_expired_session_token") ||
+ (error.type == "auth_missing_credentials") ||
+ (error.type == "auth_invalid")) {
+ //this error type means the user is not authorized. If a logout function is defined, call it
+ if (apigee.console.logout) {
+ apigee.console.logout();
+ return;
+ }
+ }
+ //otherwise, just call the failure callback
+ QueryObj.callFailureCallback(response);
+ return;
+ } else {
+ //success
+
+ //query completed succesfully, so store cursor
+ var cursor = response.cursor || null;
+ QueryObj.saveCursor(cursor);
+ //then call the original callback
+ QueryObj.callSuccessCallback(response);
+ }
+ };
+ var timeout = setTimeout(function() { xhr.abort(); }, 10000);
+
+ xhr.send(jsonObj);
+ }
+
+ /**
+ * Private helper method to encode the query string parameters
+ * @method encodeParams
+ * @params {object} params - an object of name value pairs that will be urlencoded
+ * @return {string} Returns the encoded string
+ */
+ function encodeParams (params) {
+ tail = [];
+ var item = [];
+ if (params instanceof Array) {
+ for (i in params) {
+ item = params[i];
+ if ((item instanceof Array) && (item.length > 1)) {
+ tail.push(item[0] + "=" + encodeURIComponent(item[1]));
+ }
+ }
+ } else {
+ for (var key in params) {
+ if (params.hasOwnProperty(key)) {
+ var value = params[key];
+ if (value instanceof Array) {
+ for (i in value) {
+ item = value[i];
+ tail.push(key + "=" + encodeURIComponent(item));
+ }
+ } else {
+ tail.push(key + "=" + encodeURIComponent(value));
+ }
+ }
+ }
+ }
+ return tail.join("&");
+ }
+
+ return {
+ init:init,
+ getOrganizationName:getOrganizationName,
+ setOrganizationName:setOrganizationName,
+ getOrganizationUUID:getOrganizationUUID,
+ setOrganizationUUID:setOrganizationUUID,
+ getApplicationName:getApplicationName,
+ setApplicationName:setApplicationName,
+ getToken:getToken,
+ setToken:setToken,
+ getAppUserUsername:getAppUserUsername,
+ setAppUserUsername:setAppUserUsername,
+ getAppUserFullName:getAppUserFullName,
+ setAppUserFullName:setAppUserFullName,
+ getAppUserEmail:getAppUserEmail,
+ setAppUserEmail:setAppUserEmail,
+ getAppUserUUID:getAppUserUUID,
+ setAppUserUUID:setAppUserUUID,
+ getApiUrl:getApiUrl,
+ setApiUrl:setApiUrl,
+ getResetPasswordUrl:getResetPasswordUrl,
+ runAppQuery:runAppQuery,
+ runManagementQuery:runManagementQuery,
+ loginAppUser:loginAppUser,
+ createAppUser:createAppUser,
+ updateAppUser:updateAppUser,
+ renewAppUserToken:renewAppUserToken,
+ logoutAppUser:logoutAppUser,
+ isLoggedInAppUser:isLoggedInAppUser,
+ processQuery:processQuery,
+ encodeParams:encodeParams
+ }
+})();
+
+
+/**
+ * validation is a Singleton that provides methods for validating common field types
+ *
+ * @class apigee.validation
+ * @author Rod Simpson (rod@apigee.com)
+**/
+apigee.validation = apigee.validation || {};
+
+apigee.validation = (function () {
+
+ var usernameRegex = new RegExp("^([0-9a-zA-Z\.\-])+$");
+ var nameRegex = new RegExp("^([0-9a-zA-Z@#$%^&!?;:.,'\"~*-=+_\[\\](){}/\\ |])+$");
+ var emailRegex = new RegExp("^(([0-9a-zA-Z]+[_\+.-]?)+@[0-9a-zA-Z]+[0-9,a-z,A-Z,.,-]*(.){1}[a-zA-Z]{2,4})+$");
+ var passwordRegex = new RegExp("^([0-9a-zA-Z@#$%^&!?<>;:.,'\"~*-=+_\[\\](){}/\\ |])+$");
+ var pathRegex = new RegExp("^([0-9a-z./-])+$");
+ var titleRegex = new RegExp("^([0-9a-zA-Z.!-?/])+$");
+
+ /**
+ * Tests the string against the allowed chars regex
+ * @public
+ * @method validateUsername
+ * @param {string} username - The string to test
+ * @param {function} failureCallback - (optional), the function to call on a failure
+ * @return {boolean} Returns true if string passes regex, false if not
+ */
+ function validateUsername(username, failureCallback) {
+ if (usernameRegex.test(username) && checkLength(username, 4, 80)) {
+ return true;
+ } else {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(this.getUsernameAllowedChars());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns the regex of allowed chars
+ * @public
+ * @method getUsernameAllowedChars
+ * @return {string} Returns a string with the allowed chars
+ */
+ function getUsernameAllowedChars(){
+ return 'Length: min 6, max 80. Allowed: A-Z, a-z, 0-9, dot, and dash';
+ }
+
+ /**
+ * Tests the string against the allowed chars regex
+ * @public
+ * @method validateName
+ * @param {string} name - The string to test
+ * @param {function} failureCallback - (optional), the function to call on a failure
+ * @return {boolean} Returns true if string passes regex, false if not
+ */
+ function validateName(name, failureCallback) {
+ if (nameRegex.test(name) && checkLength(name, 5, 16)) {
+ return true;
+ } else {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(this.getNameAllowedChars());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns the regex of allowed chars
+ * @public
+ * @method getNameAllowedChars
+ * @return {string} Returns a string with the allowed chars
+ */
+ function getNameAllowedChars(){
+ return 'Length: min 4, max 80. Allowed: A-Z, a-z, 0-9, ~ @ # % ^ & * ( ) - _ = + [ ] { } \\ | ; : \' " , . / ? !';
+ }
+
+ /**
+ * Tests the string against the allowed chars regex
+ * @public
+ * @method validatePassword
+ * @param {string} password - The string to test
+ * @param {function} failureCallback - (optional), the function to call on a failure
+ * @return {boolean} Returns true if string passes regex, false if not
+ */
+ function validatePassword(password, failureCallback) {
+ if (passwordRegex.test(password) && checkLength(password, 5, 16)) {
+ return true;
+ } else {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(this.getPasswordAllowedChars());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns the regex of allowed chars
+ * @public
+ * @method getPasswordAllowedChars
+ * @return {string} Returns a string with the allowed chars
+ */
+ function getPasswordAllowedChars(){
+ return 'Length: min 5, max 16. Allowed: A-Z, a-z, 0-9, ~ @ # % ^ & * ( ) - _ = + [ ] { } \\ | ; : \' " , . < > / ? !';
+ }
+
+ /**
+ * Tests the string against the allowed chars regex
+ * @public
+ * @method validateEmail
+ * @param {string} email - The string to test
+ * @param {function} failureCallback - (optional), the function to call on a failure
+ * @return {boolean} Returns true if string passes regex, false if not
+ */
+ function validateEmail(email, failureCallback) {
+ if (emailRegex.test(email) && checkLength(email, 4, 80)) {
+ return true;
+ } else {
+ if (failureCallback && typeof(failureCallback) == "function") {
+ failureCallback(this.getEmailAllowedChars());
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns the regex of allowed chars
+ * @public
+ * @method getEmailAllowedChars
+ * @return {string} Returns a string with the allowed chars
+ */
+ function getEmailAllowedChars(){
+ return 'Email must be in standard form: e.g. example@apigee.com';
+ }