PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/DART/lib/com.wikia.ad.dart.js

https://github.com/federico-lox/Titanium-modules
JavaScript | 366 lines | 231 code | 70 blank | 65 comment | 62 complexity | 10b9b815983837888cc62265e9276ed8 MD5 | raw file
  1. /**
  2. * DART Server-side tagging for Titanium Mobile
  3. * @version 1.0
  4. * @author Federico "Lox" Lucignano <federico(at)wikia-inc.com>
  5. *
  6. * @namespace com.wikia.ad.dart
  7. *
  8. * @see https://internal.wikia-inc.com/img_auth.php/2/23/DoubleClick_for_Mobile_tagging.pdf
  9. * @see https://internal.wikia-inc.com/img_auth.php/c/ca/DFM_Mobile_params.pdf
  10. */
  11. (function(){
  12. /**
  13. * @private
  14. */
  15. var ns = this,
  16. /**
  17. * @const
  18. */
  19. NS_NAME = 'com.wikia.ad.dart',
  20. BASE_URL = 'http://ad.mo.doubleclick.net/DARTProxy/mobile.handler';
  21. function checkStringParameter(value){
  22. return (value && typeof value == 'string' && value != '');
  23. }
  24. function getEventString(/* ... */){
  25. var tokens = ['DART'];
  26. for(var x = 0, y = arguments.length; x < y; x++){
  27. tokens.push(arguments[x]);
  28. }
  29. return tokens.join(':');
  30. }
  31. function debug(prefix, data){
  32. Ti.API.debug('[' + NS_NAME + '.' + prefix + '] ' + data);
  33. }
  34. /**
  35. * @public
  36. */
  37. ns.TagTypes = {
  38. IMAGE: 'i',
  39. TEXT: 't'
  40. };
  41. /**
  42. * @example
  43. * var dart = require('com.wikia.ad.dart');
  44. * try{
  45. * var tag = new dart.Tag(dart.TagTypes.IMAGE, {
  46. * k: 'mysite.com;sz=200x30',
  47. * forecast: 1
  48. * });
  49. *
  50. * tag.addEventListener('success', function(data){
  51. * var tag = data.source;
  52. * Ti.API.info('Tag Data: ' + data.value + ' (' + data.target + ')');
  53. * });
  54. *
  55. * tag.addEventListener('error', function(data){
  56. * var tag = data.source;
  57. * Ti.API.error('DART Error (' + data.code + '): ' + data.message);
  58. * });
  59. *
  60. * tag.request();
  61. * }catch(e){
  62. * Ti.API.error(String(e));
  63. * }
  64. */
  65. ns.Tag = function(tagType, params){
  66. /**
  67. * @private
  68. */
  69. var that = this;//used in the xhr event handlers
  70. this.__guid = Ti.Platform.createUUID();
  71. this.__data = params;
  72. this.__xhr = null;
  73. this.__target = null;
  74. this.__value = null;
  75. this.__getEventString = function(eventName){
  76. return getEventString('tag', eventName, this.__guid);
  77. }
  78. this.__xhrOnLoad = function(){
  79. //in this function "this" is an HTTPClient instance
  80. if(this.status == 200){
  81. //DART always returns 200 OK, the actual error is in the response headers
  82. var errorCode = this.getResponseHeader('Error_code'),
  83. errorMessage = this.getResponseHeader('Error_message'),
  84. targetMatches,
  85. valueMatches;
  86. if(!errorCode || errorCode == 'success'){
  87. targetMatches = this.responseText.match(/<a href="([^>"]+)">/mi);
  88. switch(that.getType()){
  89. case ns.TagTypes.IMAGE:
  90. valueMatches = this.responseText.match(/<img src="([^">]+)"/mi);
  91. break;
  92. case ns.TagTypes.TEXT:
  93. valueMatches = this.responseText.match(/>([^<]+)<\/a>/mi);
  94. break;
  95. }
  96. if(targetMatches && targetMatches.length > 0 && valueMatches && valueMatches.length > 0){
  97. that.__target = targetMatches[1];
  98. that.__value = valueMatches[1];
  99. errorCode = null;
  100. errorMessage = null;
  101. that.__debug('successfully downloaded data from the server: ' + that.__value + '(' + that.__target + ')');
  102. that.fireEvent('success', {source: that, target: that.__target, value: that.__value});
  103. }else{
  104. errorCode = 'incomplete_data';
  105. errorMessage = 'Server returned incomplete data';
  106. }
  107. }
  108. }else{
  109. errorCode = 'status_' + this.status;
  110. errorMessage = 'Server error';
  111. }
  112. if(errorCode || errorMessage){
  113. that.__debug('error: ' + errorMessage + ' (' + errorCode + ')');
  114. that.fireEvent('error', {source: that, code: errorCode, message: errorMessage});
  115. }
  116. };
  117. this.__xhrOnError = function(){
  118. //in this function "this" is an HTTPClient instance
  119. that.fireEvent('error', {source: that, code: this.status, message: 'Server error'});
  120. };
  121. this.__debug = function(data){
  122. debug('Tag:' + this.__guid, data);
  123. };
  124. /**
  125. * @public
  126. */
  127. this.toString = function(){
  128. var strData = BASE_URL + '?',
  129. count = 0,
  130. paramName;
  131. for(paramName in this.__data){
  132. strData += ((count) ? '&' : '') + paramName + '=' + this.__data[paramName];
  133. count++;
  134. }
  135. return strData;
  136. };
  137. this.getType = function(){
  138. return this.getParameter('t');
  139. };
  140. this.setType = function(tagType){
  141. this.setParameter('t', tagType);
  142. };
  143. this.getTarget = function(){
  144. return this.__target;
  145. };
  146. this.getValue = function(){
  147. return this.__value;
  148. };
  149. this.getParameter = function(name){
  150. return this.__data[name];
  151. };
  152. this.setParameter = function(name, value){
  153. switch(name){
  154. case 't':
  155. //missing or wrong type
  156. if(value != ns.TagTypes.IMAGE && value != ns.TagTypes.TEXT)
  157. throw new ns.paramsError('Tag type (t)', value);
  158. break;
  159. case 'k':
  160. //missing target
  161. if(!checkStringParameter(value))
  162. throw new ns.paramsError(name, value);
  163. //missing size
  164. if(!/sz=[^;]/.test(value))
  165. throw new ns.paramsError('sz', undefined);
  166. break;
  167. }
  168. if(value === null || String(value) === '')
  169. return;
  170. this.__data[name] = value;
  171. };
  172. this.addEventListener = function(eventName, callback){
  173. if(eventName && typeof eventName == 'string' && typeof callback == 'function'){
  174. Ti.Network.addEventListener(this.__getEventString(eventName), callback);
  175. }
  176. };
  177. this.removeEventListener = function(eventName, callback){
  178. if(eventName && typeof eventName == 'string' && typeof callback == 'function'){
  179. Ti.Network.removeEventListener(this.__getEventString(eventName), callback);
  180. }
  181. };
  182. this.fireEvent = function(eventName, data){
  183. if(eventName && typeof eventName == 'string'){
  184. Ti.Network.fireEvent(this.__getEventString(eventName), data||{});
  185. }
  186. };
  187. this.isBusy = function(){
  188. return (this.__xhr) ? (this.__xhr.readyState > 0 && this.__xhr.readyState < 4) : false;
  189. }
  190. this.request = function(){
  191. if(!this.__xhr){
  192. this.__xhr = Ti.Network.createHTTPClient({
  193. onload: this.__xhrOnLoad,
  194. onerror: this.__xhrOnError
  195. });
  196. }
  197. if(!this.isBusy()){
  198. var url = this.toString();
  199. this.__debug('sending request to ' + url);
  200. //due to a bug in Titanium 1.7.2/3 HTTPClient.send is not pushing request parameters for GET requests on iOS
  201. //falling back to using a full URL with querystring data in the open method for now
  202. this.__xhr.open('GET', url);
  203. this.__xhr.send();
  204. }else
  205. this.__debug('Request canceled, one is already running.');
  206. };
  207. /**
  208. * @init
  209. */
  210. params = params || {};
  211. //mandatory
  212. params.k = params.k || null
  213. params.t = tagType;
  214. //optional
  215. params.r = 'h';//XHTML format
  216. params.sdh = 0;//Do not include document headers in the response markup
  217. params.u = Ti.Platform.id;//user identifier for frequency capping
  218. for(var name in params){
  219. this.setParameter(name, params[name]);
  220. }
  221. };
  222. ns.createImageView = function(options){
  223. options = options || {};
  224. options.dartTag = options.dartTag || new ns.Tag(ns.TagTypes.IMAGE, options.tagParameters);
  225. options.autoload = options.autoload || true;
  226. options.load= function(){
  227. this.dartTag.request();
  228. };
  229. var img = Ti.UI.createImageView(options);
  230. img.dartTag.addEventListener('success', function(data){
  231. img.image = data.value;
  232. img.addEventListener('click', function(){
  233. Ti.Platform.openURL(data.target);
  234. });
  235. img.fireEvent('success', data);
  236. });
  237. img.dartTag.addEventListener('error', function(data){
  238. if(img.defaultAsset){
  239. img.image = img.defaultAsset;
  240. if(img.defaultTarget){
  241. img.addEventListener('click', function(){
  242. Ti.Platform.openURL(img.defaultTarget);
  243. });
  244. }
  245. }
  246. img.fireEvent('error', data);
  247. });
  248. if(options.autoload && Ti.Network.online)
  249. img.load();
  250. else if(!Ti.Network.online)
  251. img.dartTag.fireEvent('error', {source: img.dartTag, code: 'offline', message: 'No internet connection'});
  252. return img;
  253. };
  254. ns.createTextView = function(options){
  255. options = options || {};
  256. options.dartTag = options.dartTag || new ns.Tag(ns.TagTypes.TEXT, options.tagParameters);
  257. options.autoload = options.autoload || true;
  258. options.load= function(){
  259. this.dartTag.request();
  260. };
  261. var lbl = Ti.UI.createLabel(options);
  262. lbl.dartTag.addEventListener('success', function(data){
  263. lbl.text = data.value;
  264. lbl.addEventListener('click', function(){
  265. Ti.Platform.openURL(data.target);
  266. });
  267. lbl.fireEvent('success', data);
  268. });
  269. lbl.dartTag.addEventListener('error', function(data){
  270. if(lbl.defaultText){
  271. lbl.text = lbl.defaultText;
  272. if(lbl.defaultTarget){
  273. lbl.addEventListener('click', function(){
  274. Ti.Platform.openURL(lbl.defaultTarget);
  275. });
  276. }
  277. }
  278. lbl.fireEvent('error', data);
  279. });
  280. if(options.autoload && Ti.Network.online)
  281. lbl.load();
  282. else if(!Ti.Network.online)
  283. lbl.dartTag.fireEvent('error', {source: lbl.dartTag, code: 'offline', message: 'No internet connection'});
  284. return lbl;
  285. };
  286. ns.paramsError = function(paramName, paramValue){
  287. /**
  288. * @public
  289. */
  290. this.message = 'The "' + paramName + '" prameter was passed with an unexpected value: "' + paramValue + '".';
  291. this.toString = function(){
  292. return this.message;
  293. };
  294. };
  295. }).apply(exports);