You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

855 lines
28 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*
  2. * # Semantic - API
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributor
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. $.api = $.fn.api = function(parameters) {
  13. var
  14. // use window context if none specified
  15. $allModules = $.isFunction(this)
  16. ? $(window)
  17. : $(this),
  18. moduleSelector = $allModules.selector || '',
  19. time = new Date().getTime(),
  20. performance = [],
  21. query = arguments[0],
  22. methodInvoked = (typeof query == 'string'),
  23. queryArguments = [].slice.call(arguments, 1),
  24. returnedValue
  25. ;
  26. $allModules
  27. .each(function() {
  28. var
  29. settings = ( $.isPlainObject(parameters) )
  30. ? $.extend(true, {}, $.fn.api.settings, parameters)
  31. : $.extend({}, $.fn.api.settings),
  32. // internal aliases
  33. namespace = settings.namespace,
  34. metadata = settings.metadata,
  35. selector = settings.selector,
  36. error = settings.error,
  37. className = settings.className,
  38. // define namespaces for modules
  39. eventNamespace = '.' + namespace,
  40. moduleNamespace = 'module-' + namespace,
  41. // element that creates request
  42. $module = $(this),
  43. $form = $module.closest(selector.form),
  44. // context used for state
  45. $context = (settings.stateContext)
  46. ? $(settings.stateContext)
  47. : $module,
  48. // request details
  49. ajaxSettings,
  50. requestSettings,
  51. url,
  52. data,
  53. // standard module
  54. element = this,
  55. context = $context.get(),
  56. instance = $module.data(moduleNamespace),
  57. module
  58. ;
  59. module = {
  60. initialize: function() {
  61. var
  62. triggerEvent = module.get.event()
  63. ;
  64. // bind events
  65. if(!methodInvoked) {
  66. if( triggerEvent ) {
  67. module.debug('Attaching API events to element', triggerEvent);
  68. $module
  69. .on(triggerEvent + eventNamespace, module.event.trigger)
  70. ;
  71. }
  72. else if(settings.on == 'now') {
  73. module.debug('Querying API now', triggerEvent);
  74. module.query();
  75. }
  76. }
  77. module.instantiate();
  78. },
  79. instantiate: function() {
  80. module.verbose('Storing instance of module', module);
  81. instance = module;
  82. $module
  83. .data(moduleNamespace, instance)
  84. ;
  85. },
  86. destroy: function() {
  87. module.verbose('Destroying previous module for', element);
  88. $module
  89. .removeData(moduleNamespace)
  90. .off(eventNamespace)
  91. ;
  92. },
  93. query: function() {
  94. if(module.is.disabled()) {
  95. module.debug('Element is disabled API request aborted');
  96. return;
  97. }
  98. // determine if an api event already occurred
  99. if(module.is.loading() && settings.throttle === 0 ) {
  100. module.debug('Cancelling request, previous request is still pending');
  101. return;
  102. }
  103. // pass element metadata to url (value, text)
  104. if(settings.defaultData) {
  105. $.extend(true, settings.urlData, module.get.defaultData());
  106. }
  107. // Add form content
  108. if(settings.serializeForm !== false || $context.is('form')) {
  109. if(settings.serializeForm == 'json') {
  110. $.extend(true, settings.data, module.get.formData());
  111. }
  112. else {
  113. settings.data = module.get.formData();
  114. }
  115. }
  116. // call beforesend and get any settings changes
  117. requestSettings = module.get.settings();
  118. // check if beforesend cancelled request
  119. if(requestSettings === false) {
  120. module.error(error.beforeSend);
  121. return;
  122. }
  123. if(settings.url) {
  124. // override with url if specified
  125. module.debug('Using specified url', url);
  126. url = module.add.urlData( settings.url );
  127. }
  128. else {
  129. // otherwise find url from api endpoints
  130. url = module.add.urlData( module.get.templateURL() );
  131. module.debug('Added URL Data to url', url);
  132. }
  133. // exit conditions reached, missing url parameters
  134. if( !url ) {
  135. if($module.is('form')) {
  136. module.debug('No url or action specified, defaulting to form action');
  137. url = $module.attr('action');
  138. }
  139. else {
  140. module.error(error.missingURL, settings.action);
  141. return;
  142. }
  143. }
  144. // add loading state
  145. module.set.loading();
  146. // look for jQuery ajax parameters in settings
  147. ajaxSettings = $.extend(true, {}, settings, {
  148. type : settings.method || settings.type,
  149. data : data,
  150. url : settings.base + url,
  151. beforeSend : settings.beforeXHR,
  152. success : function() {},
  153. failure : function() {},
  154. complete : function() {}
  155. });
  156. module.debug('Querying URL', ajaxSettings.url);
  157. module.debug('Sending data', data, ajaxSettings.method);
  158. module.verbose('Using AJAX settings', ajaxSettings);
  159. if( module.is.loading() ) {
  160. // throttle additional requests
  161. module.timer = setTimeout(function() {
  162. module.request = module.create.request();
  163. module.xhr = module.create.xhr();
  164. settings.onRequest.call(context, module.request, module.xhr);
  165. }, settings.throttle);
  166. }
  167. else {
  168. // immediately on first request
  169. module.request = module.create.request();
  170. module.xhr = module.create.xhr();
  171. settings.onRequest.call(context, module.request, module.xhr);
  172. }
  173. },
  174. is: {
  175. disabled: function() {
  176. return ($module.filter(settings.filter).length > 0);
  177. },
  178. loading: function() {
  179. return (module.request && module.request.state() == 'pending');
  180. }
  181. },
  182. was: {
  183. succesful: function() {
  184. return (module.request && module.request.state() == 'resolved');
  185. },
  186. failure: function() {
  187. return (module.request && module.request.state() == 'rejected');
  188. },
  189. complete: function() {
  190. return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') );
  191. }
  192. },
  193. add: {
  194. urlData: function(url, urlData) {
  195. var
  196. requiredVariables,
  197. optionalVariables
  198. ;
  199. if(url) {
  200. requiredVariables = url.match(settings.regExp.required);
  201. optionalVariables = url.match(settings.regExp.optional);
  202. urlData = urlData || settings.urlData;
  203. if(requiredVariables) {
  204. module.debug('Looking for required URL variables', requiredVariables);
  205. $.each(requiredVariables, function(index, templatedString) {
  206. var
  207. // allow legacy {$var} style
  208. variable = (templatedString.indexOf('$') !== -1)
  209. ? templatedString.substr(2, templatedString.length - 3)
  210. : templatedString.substr(1, templatedString.length - 2),
  211. value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
  212. ? urlData[variable]
  213. : ($module.data(variable) !== undefined)
  214. ? $module.data(variable)
  215. : ($context.data(variable) !== undefined)
  216. ? $context.data(variable)
  217. : urlData[variable]
  218. ;
  219. // remove value
  220. if(value === undefined) {
  221. module.error(error.requiredParameter, variable, url);
  222. url = false;
  223. return false;
  224. }
  225. else {
  226. module.verbose('Found required variable', variable, value);
  227. url = url.replace(templatedString, value);
  228. }
  229. });
  230. }
  231. if(optionalVariables) {
  232. module.debug('Looking for optional URL variables', requiredVariables);
  233. $.each(optionalVariables, function(index, templatedString) {
  234. var
  235. // allow legacy {/$var} style
  236. variable = (templatedString.indexOf('$') !== -1)
  237. ? templatedString.substr(3, templatedString.length - 4)
  238. : templatedString.substr(2, templatedString.length - 3),
  239. value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
  240. ? urlData[variable]
  241. : ($module.data(variable) !== undefined)
  242. ? $module.data(variable)
  243. : ($context.data(variable) !== undefined)
  244. ? $context.data(variable)
  245. : urlData[variable]
  246. ;
  247. // optional replacement
  248. if(value !== undefined) {
  249. module.verbose('Optional variable Found', variable, value);
  250. url = url.replace(templatedString, value);
  251. }
  252. else {
  253. module.verbose('Optional variable not found', variable);
  254. // remove preceding slash if set
  255. if(url.indexOf('/' + templatedString) !== -1) {
  256. url = url.replace('/' + templatedString, '');
  257. }
  258. else {
  259. url = url.replace(templatedString, '');
  260. }
  261. }
  262. });
  263. }
  264. }
  265. return url;
  266. }
  267. },
  268. event: {
  269. trigger: function(event) {
  270. module.query();
  271. if(event.type == 'submit' || event.type == 'click') {
  272. event.preventDefault();
  273. }
  274. },
  275. xhr: {
  276. always: function() {
  277. // calculate if loading time was below minimum threshold
  278. },
  279. done: function(response) {
  280. var
  281. context = this,
  282. elapsedTime = (new Date().getTime() - time),
  283. timeLeft = (settings.loadingDuration - elapsedTime)
  284. ;
  285. timeLeft = (timeLeft > 0)
  286. ? timeLeft
  287. : 0
  288. ;
  289. setTimeout(function() {
  290. module.request.resolveWith(context, [response]);
  291. }, timeLeft);
  292. },
  293. fail: function(xhr, status, httpMessage) {
  294. var
  295. context = this,
  296. elapsedTime = (new Date().getTime() - time),
  297. timeLeft = (settings.loadingDuration - elapsedTime)
  298. ;
  299. timeLeft = (timeLeft > 0)
  300. ? timeLeft
  301. : 0
  302. ;
  303. // page triggers abort on navigation, dont show error
  304. setTimeout(function() {
  305. if(status !== 'abort') {
  306. module.request.rejectWith(context, [xhr, status, httpMessage]);
  307. }
  308. else {
  309. module.reset();
  310. }
  311. }, timeLeft);
  312. }
  313. },
  314. request: {
  315. complete: function(response) {
  316. module.remove.loading();
  317. settings.onComplete.call(context, response, $module);
  318. },
  319. done: function(response) {
  320. module.debug('API Response Received', response);
  321. if(settings.dataType == 'json') {
  322. if( $.isFunction(settings.successTest) ) {
  323. module.debug('Checking JSON returned success', settings.successTest, response);
  324. if( settings.successTest(response) ) {
  325. settings.onSuccess.call(context, response, $module);
  326. }
  327. else {
  328. module.debug('JSON test specified by user and response failed', response);
  329. settings.onFailure.call(context, response, $module);
  330. }
  331. }
  332. else {
  333. settings.onSuccess.call(context, response, $module);
  334. }
  335. }
  336. else {
  337. settings.onSuccess.call(context, response, $module);
  338. }
  339. },
  340. error: function(xhr, status, httpMessage) {
  341. var
  342. errorMessage = (settings.error[status] !== undefined)
  343. ? settings.error[status]
  344. : httpMessage,
  345. response
  346. ;
  347. // let em know unless request aborted
  348. if(xhr !== undefined) {
  349. // readyState 4 = done, anything less is not really sent
  350. if(xhr.readyState !== undefined && xhr.readyState == 4) {
  351. // if http status code returned and json returned error, look for it
  352. if( xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') {
  353. module.error(error.statusMessage + httpMessage, ajaxSettings.url);
  354. }
  355. else {
  356. if(status == 'error' && settings.dataType == 'json') {
  357. try {
  358. response = $.parseJSON(xhr.responseText);
  359. if(response && response.error !== undefined) {
  360. errorMessage = response.error;
  361. }
  362. }
  363. catch(e) {
  364. module.error(error.JSONParse);
  365. }
  366. }
  367. }
  368. module.remove.loading();
  369. module.set.error();
  370. // show error state only for duration specified in settings
  371. if(settings.errorDuration) {
  372. setTimeout(module.remove.error, settings.errorDuration);
  373. }
  374. module.debug('API Request error:', errorMessage);
  375. settings.onError.call(context, errorMessage, $module);
  376. }
  377. else {
  378. settings.onAbort.call(context, errorMessage, $module);
  379. module.debug('Request Aborted (Most likely caused by page change or CORS Policy)', status, httpMessage);
  380. }
  381. }
  382. }
  383. }
  384. },
  385. create: {
  386. request: function() {
  387. return $.Deferred()
  388. .always(module.event.request.complete)
  389. .done(module.event.request.done)
  390. .fail(module.event.request.error)
  391. ;
  392. },
  393. xhr: function() {
  394. return $.ajax(ajaxSettings)
  395. .always(module.event.xhr.always)
  396. .done(module.event.xhr.done)
  397. .fail(module.event.xhr.fail)
  398. ;
  399. }
  400. },
  401. set: {
  402. error: function() {
  403. module.verbose('Adding error state to element', $context);
  404. $context.addClass(className.error);
  405. },
  406. loading: function() {
  407. module.verbose('Adding loading state to element', $context);
  408. $context.addClass(className.loading);
  409. }
  410. },
  411. remove: {
  412. error: function() {
  413. module.verbose('Removing error state from element', $context);
  414. $context.removeClass(className.error);
  415. },
  416. loading: function() {
  417. module.verbose('Removing loading state from element', $context);
  418. $context.removeClass(className.loading);
  419. }
  420. },
  421. get: {
  422. request: function() {
  423. return module.request || false;
  424. },
  425. xhr: function() {
  426. return module.xhr || false;
  427. },
  428. settings: function() {
  429. var
  430. runSettings
  431. ;
  432. runSettings = settings.beforeSend.call($module, settings);
  433. if(runSettings) {
  434. if(runSettings.success !== undefined) {
  435. module.debug('Legacy success callback detected', runSettings);
  436. module.error(error.legacyParameters, runSettings.success);
  437. runSettings.onSuccess = runSettings.success;
  438. }
  439. if(runSettings.failure !== undefined) {
  440. module.debug('Legacy failure callback detected', runSettings);
  441. module.error(error.legacyParameters, runSettings.failure);
  442. runSettings.onFailure = runSettings.failure;
  443. }
  444. if(runSettings.complete !== undefined) {
  445. module.debug('Legacy complete callback detected', runSettings);
  446. module.error(error.legacyParameters, runSettings.complete);
  447. runSettings.onComplete = runSettings.complete;
  448. }
  449. }
  450. if(runSettings === undefined) {
  451. module.error(error.noReturnedValue);
  452. }
  453. return (runSettings !== undefined)
  454. ? runSettings
  455. : settings
  456. ;
  457. },
  458. defaultData: function() {
  459. var
  460. data = {}
  461. ;
  462. if( !$.isWindow(element) ) {
  463. if( $module.is('input') ) {
  464. data.value = $module.val();
  465. }
  466. else if( $module.is('form') ) {
  467. }
  468. else {
  469. data.text = $module.text();
  470. }
  471. }
  472. return data;
  473. },
  474. event: function() {
  475. if( $.isWindow(element) || settings.on == 'now' ) {
  476. module.debug('API called without element, no events attached');
  477. return false;
  478. }
  479. else if(settings.on == 'auto') {
  480. if( $module.is('input') ) {
  481. return (element.oninput !== undefined)
  482. ? 'input'
  483. : (element.onpropertychange !== undefined)
  484. ? 'propertychange'
  485. : 'keyup'
  486. ;
  487. }
  488. else if( $module.is('form') ) {
  489. return 'submit';
  490. }
  491. else {
  492. return 'click';
  493. }
  494. }
  495. else {
  496. return settings.on;
  497. }
  498. },
  499. formData: function() {
  500. var
  501. formData
  502. ;
  503. if($(this).serializeObject() !== undefined) {
  504. formData = $form.serializeObject();
  505. }
  506. else {
  507. module.error(error.missingSerialize);
  508. formData = $form.serialize();
  509. }
  510. module.debug('Retrieved form data', formData);
  511. return formData;
  512. },
  513. templateURL: function(action) {
  514. var
  515. url
  516. ;
  517. action = action || $module.data(metadata.action) || settings.action || false;
  518. if(action) {
  519. module.debug('Looking up url for action', action, settings.api);
  520. if(settings.api[action] !== undefined) {
  521. url = settings.api[action];
  522. module.debug('Found template url', url);
  523. }
  524. else {
  525. module.error(error.missingAction, settings.action, settings.api);
  526. }
  527. }
  528. return url;
  529. }
  530. },
  531. abort: function() {
  532. var
  533. xhr = module.get.xhr()
  534. ;
  535. if( xhr && xhr.state() !== 'resolved') {
  536. module.debug('Cancelling API request');
  537. xhr.abort();
  538. module.request.rejectWith(settings.apiSettings);
  539. }
  540. },
  541. // reset state
  542. reset: function() {
  543. module.remove.error();
  544. module.remove.loading();
  545. },
  546. setting: function(name, value) {
  547. module.debug('Changing setting', name, value);
  548. if( $.isPlainObject(name) ) {
  549. $.extend(true, settings, name);
  550. }
  551. else if(value !== undefined) {
  552. settings[name] = value;
  553. }
  554. else {
  555. return settings[name];
  556. }
  557. },
  558. internal: function(name, value) {
  559. if( $.isPlainObject(name) ) {
  560. $.extend(true, module, name);
  561. }
  562. else if(value !== undefined) {
  563. module[name] = value;
  564. }
  565. else {
  566. return module[name];
  567. }
  568. },
  569. debug: function() {
  570. if(settings.debug) {
  571. if(settings.performance) {
  572. module.performance.log(arguments);
  573. }
  574. else {
  575. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  576. module.debug.apply(console, arguments);
  577. }
  578. }
  579. },
  580. verbose: function() {
  581. if(settings.verbose && settings.debug) {
  582. if(settings.performance) {
  583. module.performance.log(arguments);
  584. }
  585. else {
  586. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  587. module.verbose.apply(console, arguments);
  588. }
  589. }
  590. },
  591. error: function() {
  592. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  593. module.error.apply(console, arguments);
  594. },
  595. performance: {
  596. log: function(message) {
  597. var
  598. currentTime,
  599. executionTime,
  600. previousTime
  601. ;
  602. if(settings.performance) {
  603. currentTime = new Date().getTime();
  604. previousTime = time || currentTime;
  605. executionTime = currentTime - previousTime;
  606. time = currentTime;
  607. performance.push({
  608. 'Name' : message[0],
  609. 'Arguments' : [].slice.call(message, 1) || '',
  610. //'Element' : element,
  611. 'Execution Time' : executionTime
  612. });
  613. }
  614. clearTimeout(module.performance.timer);
  615. module.performance.timer = setTimeout(module.performance.display, 100);
  616. },
  617. display: function() {
  618. var
  619. title = settings.name + ':',
  620. totalTime = 0
  621. ;
  622. time = false;
  623. clearTimeout(module.performance.timer);
  624. $.each(performance, function(index, data) {
  625. totalTime += data['Execution Time'];
  626. });
  627. title += ' ' + totalTime + 'ms';
  628. if(moduleSelector) {
  629. title += ' \'' + moduleSelector + '\'';
  630. }
  631. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  632. console.groupCollapsed(title);
  633. if(console.table) {
  634. console.table(performance);
  635. }
  636. else {
  637. $.each(performance, function(index, data) {
  638. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  639. });
  640. }
  641. console.groupEnd();
  642. }
  643. performance = [];
  644. }
  645. },
  646. invoke: function(query, passedArguments, context) {
  647. var
  648. object = instance,
  649. maxDepth,
  650. found,
  651. response
  652. ;
  653. passedArguments = passedArguments || queryArguments;
  654. context = element || context;
  655. if(typeof query == 'string' && object !== undefined) {
  656. query = query.split(/[\. ]/);
  657. maxDepth = query.length - 1;
  658. $.each(query, function(depth, value) {
  659. var camelCaseValue = (depth != maxDepth)
  660. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  661. : query
  662. ;
  663. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  664. object = object[camelCaseValue];
  665. }
  666. else if( object[camelCaseValue] !== undefined ) {
  667. found = object[camelCaseValue];
  668. return false;
  669. }
  670. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  671. object = object[value];
  672. }
  673. else if( object[value] !== undefined ) {
  674. found = object[value];
  675. return false;
  676. }
  677. else {
  678. module.error(error.method, query);
  679. return false;
  680. }
  681. });
  682. }
  683. if ( $.isFunction( found ) ) {
  684. response = found.apply(context, passedArguments);
  685. }
  686. else if(found !== undefined) {
  687. response = found;
  688. }
  689. if($.isArray(returnedValue)) {
  690. returnedValue.push(response);
  691. }
  692. else if(returnedValue !== undefined) {
  693. returnedValue = [returnedValue, response];
  694. }
  695. else if(response !== undefined) {
  696. returnedValue = response;
  697. }
  698. return found;
  699. }
  700. };
  701. if(methodInvoked) {
  702. if(instance === undefined) {
  703. module.initialize();
  704. }
  705. module.invoke(query);
  706. }
  707. else {
  708. if(instance !== undefined) {
  709. instance.invoke('destroy');
  710. }
  711. module.initialize();
  712. }
  713. })
  714. ;
  715. return (returnedValue !== undefined)
  716. ? returnedValue
  717. : this
  718. ;
  719. };
  720. $.api.settings = {
  721. name : 'API',
  722. namespace : 'api',
  723. debug : true,
  724. verbose : false,
  725. performance : true,
  726. // event binding
  727. on : 'auto',
  728. filter : '.disabled',
  729. stateContext : false,
  730. // state
  731. loadingDuration : 0,
  732. errorDuration : 2000,
  733. // templating
  734. action : false,
  735. url : false,
  736. base : '',
  737. // data
  738. urlData : {},
  739. // ui
  740. defaultData : true,
  741. serializeForm : false,
  742. throttle : 0,
  743. // jQ ajax
  744. method : 'get',
  745. data : {},
  746. dataType : 'json',
  747. // callbacks
  748. beforeSend : function(settings) { return settings; },
  749. beforeXHR : function(xhr) {},
  750. onRequest : function(promise, xhr) {},
  751. onSuccess : function(response, $module) {},
  752. onComplete : function(response, $module) {},
  753. onFailure : function(errorMessage, $module) {},
  754. onError : function(errorMessage, $module) {},
  755. onAbort : function(errorMessage, $module) {},
  756. successTest : false,
  757. // errors
  758. error : {
  759. beforeSend : 'The before send function has aborted the request',
  760. error : 'There was an error with your request',
  761. exitConditions : 'API Request Aborted. Exit conditions met',
  762. JSONParse : 'JSON could not be parsed during error handling',
  763. legacyParameters : 'You are using legacy API success callback names',
  764. method : 'The method you called is not defined',
  765. missingAction : 'API action used but no url was defined',
  766. missingSerialize : 'Required dependency jquery-serialize-object missing, using basic serialize',
  767. missingURL : 'No URL specified for api event',
  768. noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.',
  769. parseError : 'There was an error parsing your request',
  770. requiredParameter : 'Missing a required URL parameter: ',
  771. statusMessage : 'Server gave an error: ',
  772. timeout : 'Your request timed out'
  773. },
  774. regExp : {
  775. required: /\{\$*[A-z0-9]+\}/g,
  776. optional: /\{\/\$*[A-z0-9]+\}/g,
  777. },
  778. className: {
  779. loading : 'loading',
  780. error : 'error'
  781. },
  782. selector: {
  783. form: 'form'
  784. },
  785. metadata: {
  786. action : 'action'
  787. }
  788. };
  789. $.api.settings.api = {};
  790. })( jQuery, window , document );