Elgg  Version 3.0
Ajax.js
Go to the documentation of this file.
1 define(function (require) {
2  var $ = require('jquery');
3  var elgg = require('elgg');
4  var spinner = require('elgg/spinner');
5 
6  var site_url = elgg.get_site_url(),
7  action_base = site_url + 'action/',
8  fragment_pattern = /#.*$/,
9  query_pattern = /\?.*$/,
10  leading_slash_pattern = /^\//,
11  slashes_pattern = /(^\/|\/$)/g;
12 
20  function Ajax(use_spinner) {
21 
22  use_spinner = elgg.isNullOrUndefined(use_spinner) ? true : !!use_spinner;
23 
24  var that = this;
25  var spinner_starts = 0;
26 
44  function fetch(options, hook_type) {
45  var orig_options,
46  params,
47  jqXHR,
48  metadata_extracted = false,
49  error_displayed = false;
50 
57  function extract_metadata(data, status_code) {
58 
59  status_code = status_code || 200;
60 
61  if (!metadata_extracted) {
62  var m = data._elgg_msgs;
63  if (m && m.error) {
64  data.error = m.error;
65  }
66 
67  if (data.error) {
68  elgg.register_error(data.error);
69  error_displayed = true;
70  }
71 
72  if (data.error || status_code !== 200) {
73  data.status = -1;
74  } else {
75  data.status = 0;
76  }
77 
78  m && m.success && elgg.system_message(m.success);
79  delete data._elgg_msgs;
80 
81  var deps = data._elgg_deps;
82  deps && deps.length && Ajax._require(deps);
83  delete data._elgg_deps;
84 
85  metadata_extracted = true;
86  }
87  }
88 
94  that._fetch_args = {
95  options: options,
96  hook_type: hook_type
97  };
98 
99  hook_type = hook_type || '';
100 
101  if (!$.isPlainObject(options)) {
102  throw new Error('options must be a plain object with key "url"');
103  }
104  if (!options.url && hook_type !== 'path:') {
105  throw new Error('options must be a plain object with key "url"');
106  }
107 
108  // ease hook filtering by making these keys always available
109  if (options.data === undefined) {
110  options.data = {};
111  }
112  if ($.isPlainObject(options.data)) {
113  options.data = options.data || {};
114  } else if (options.data instanceof FormData) {
115  options.processData = false;
116  options.contentType = false;
117  } else {
118  if (typeof options.data !== 'string') {
119  throw new Error('if defined, options.data must be a plain object or string');
120  }
121  }
122 
123  options.dataType = 'json';
124 
125  // copy of original options
126  orig_options = $.extend({}, options);
127 
128  params = {
129  options: options
130  };
131  if (hook_type && typeof options.data !== 'string') {
132  options.data = elgg.trigger_hook(Ajax.REQUEST_DATA_HOOK, hook_type, params, options.data);
133  }
134 
135  // we do this here because hook may have made data non-empty, in which case we'd need to
136  // default to POST
137  if (!options.method) {
138  options.method = 'GET';
139  if (options.data && !$.isEmptyObject(options.data)) {
140  options.method = 'POST';
141  }
142  }
143 
144  if (use_spinner) {
145  options.beforeSend = function () {
146  orig_options.beforeSend && orig_options.beforeSend.apply(null, arguments);
147  spinner_starts++;
148  spinner.start();
149  };
150  options.complete = function () {
151  spinner_starts--;
152  if (spinner_starts < 1) {
153  spinner.stop();
154  }
155  orig_options.complete && orig_options.complete.apply(null, arguments);
156  };
157  }
158 
159  if (!options.error) {
160  options.error = function (jqXHR, textStatus, errorThrown) {
161  if (!jqXHR.getAllResponseHeaders()) {
162  // user aborts (like refresh or navigate) do not have headers
163  return;
164  }
165 
166  try {
167  var data = $.parseJSON(jqXHR.responseText);
168  if ($.isPlainObject(data)) {
169  extract_metadata(data, jqXHR.status);
170  }
171  } catch (e) {
172  if (window.console) {
173  console.warn(e.message);
174  }
175  }
176 
177  if (!error_displayed) {
178  elgg.register_error(elgg.echo('ajax:error'));
179  }
180  };
181  }
182 
183  options.dataFilter = function (data, type) {
184  if (type !== 'json') {
185  return data;
186  }
187 
188  data = $.parseJSON(data);
189 
190  extract_metadata(data, 200);
191 
192  var params = {
193  options: orig_options
194  };
195  if (hook_type) {
196  data = elgg.trigger_hook(Ajax.RESPONSE_DATA_HOOK, hook_type, params, data);
197  }
198 
199  jqXHR.AjaxData = data;
200 
201  return JSON.stringify(data.value);
202  };
203 
204  options.url = elgg.normalize_url(options.url);
205  options.headers = {
206  'X-Elgg-Ajax-API': '2'
207  };
208 
214  that._ajax_options = options;
215 
216  jqXHR = $.ajax(options);
217 
218  return jqXHR;
219  }
220 
230  this.path = function (path, options) {
231  elgg.assertTypeOf('string', path);
232 
233  // https://example.org/elgg/foo/?arg=1#bar => foo/?arg=1
234  if (path.indexOf(site_url) === 0) {
235  path = path.substr(site_url.length);
236  }
237  path = path.replace(fragment_pattern, '');
238 
239  assertNotUrl(path);
240 
241  options = options || {};
242  options.url = path;
243 
244  // /foo/?arg=1 => foo
245  path = path.replace(query_pattern, '').replace(slashes_pattern, '');
246 
247  return fetch(options, 'path:' + path);
248  };
249 
259  this.view = function (view, options) {
260  elgg.assertTypeOf('string', view);
261  if (view === '') {
262  throw new Error('view cannot be empty');
263  }
264 
265  assertNotUrl(view);
266 
267  options = options || {};
268  options.url = 'ajax/view/' + view;
269  options.method = options.method || 'GET';
270 
271  // remove query
272  view = view.replace(query_pattern, '').replace(slashes_pattern, '');
273 
274  return fetch(options, 'view:' + view);
275  };
276 
286  this.form = function (action, options) {
287  elgg.assertTypeOf('string', action);
288  if (action === '') {
289  throw new Error('action cannot be empty');
290  }
291 
292  action = action.replace(leading_slash_pattern, '').replace(fragment_pattern, '');
293 
294  assertNotUrl(action);
295 
296  options = options || {};
297  options.url = 'ajax/form/' + action;
298  options.method = options.method || 'GET';
299 
300  // remove query
301  action = action.replace(query_pattern, '').replace(slashes_pattern, '');
302 
303  return fetch(options, 'form:' + action);
304  };
305 
315  this.action = function (action, options) {
316  elgg.assertTypeOf('string', action);
317  if (action === '') {
318  throw new Error('action cannot be empty');
319  }
320 
321  // https://example.org/elgg/action/foo/?arg=1#bar => foo/?arg=1
322  if (action.indexOf(action_base) === 0) {
323  action = action.substr(action_base.length);
324  }
325  action = action.replace(leading_slash_pattern, '').replace(fragment_pattern, '');
326 
327  assertNotUrl(action);
328 
329  options = options || {};
330  options.data = options.data || {};
331 
332  // add tokens?
333  var m = action.match(/\?(.+)$/);
334  if (m && /(^|&)__elgg_ts=/.test(m[1])) {
335  // token will be in the URL
336  } else {
337  options.data = elgg.security.addToken(options.data);
338  }
339 
340  options.method = options.method || 'POST';
341  options.url = 'action/' + action;
342 
343  // /foo/?arg=1 => foo
344  action = action.replace(query_pattern, '').replace(slashes_pattern, '');
345 
346  return fetch(options, 'action:' + action);
347  };
348 
355  this.objectify = function (el) {
356 
357  /*
358  * Triggering an event to allow preparation of the form to happen.
359  * Plugins like CKEditor can use this to populate the fields with actual values.
360  */
361  $(el).trigger('elgg-ajax-objectify');
362 
363  return new FormData($(el)[0]);
364  };
365 
372  this.forward = function(destination) {
373  spinner_starts++;
374  spinner.start();
375  elgg.forward(destination);
376  };
377  }
378 
384  function assertNotUrl(arg) {
385  if (/^https?:/.test(arg)) {
386  throw new Error('elgg/Ajax cannot be used with external URLs');
387  }
388  }
389 
394  Ajax.REQUEST_DATA_HOOK = 'ajax_request_data';
395 
402  Ajax.RESPONSE_DATA_HOOK = 'ajax_response_data';
403 
407  Ajax._require = require;
408 
409  return Ajax;
410 });
411 
$CONFIG view
The current view type.
Definition: config.php:135
elgg isPlainObject
Check if the value is a "plain" object (i.e., created by {} or new Object())
Definition: elgglib.js:75
elgg forward
Meant to mimic the php forward() function by simply redirecting the user to another page...
Definition: elgglib.js:417
$CONFIG path
Legacy documentation for the old $CONFIG object.
Definition: config.php:17
elgg require
Throw an error if the required package isn&#39;t present.
Definition: elgglib.js:164
define(function(require){var $=require('jquery');var elgg=require('elgg');var spinner=require('elgg/spinner');var Ajax=require('elgg/Ajax');var ajax=new Ajax();function init(){initPluginReordering();$(document).on('click', '.elgg-admin-plugins-categories a', filterPluginCategory);$(document).on('click', '.elgg-plugins-toggle', toggleAllPlugins);$(document).on('click', '.elgg-plugin-state-change', toggleSinglePlugin);$(document).on('mouseenter', '.elgg-plugin-details-screenshots.elgg-plugin-screenshot', showPluginScreenshot);};function freezePlugins(){$('#elgg-plugin-list-cover').css('display', 'block');};function unfreezePlugins(){$('#elgg-plugin-list-cover').css('display', 'none');};function initPluginReordering(){$('#elgg-plugin-list > ul').sortable({items: 'li:has(>.elgg-state-draggable)', handle: '.elgg-body', forcePlaceholderSize:true, placeholder: 'elgg-plugin-placeholder', opacity:0.8, revert:500, stop:movePlugin});};function toggleSinglePlugin(e){freezePlugins();e.preventDefault();ajax.action(this.href).done(function(output, statusText, jqXHR){if(jqXHR.AjaxData.status==-1){location.reload();return;}ajax.path('admin_plugins_refresh').done(function(output){$('#elgg-plugin-list').html(output.list);$('.elgg-sidebar').html(output.sidebar);$(".elgg-admin-plugins-categories > li.elgg-state-selected > a").trigger('click');initPluginReordering();unfreezePlugins();});});};function toggleAllPlugins(e){e.preventDefault();if(!confirm(elgg.echo('question:areyousure'))){return;}freezePlugins();var guids=[], state=$(this).data('desiredState'), find_state=state== 'active'? 'inactive': 'active';$('.elgg-plugin.elgg-state-'+find_state+ ':visible').each(function(){var guid=$(this).data('guid');if(guid){guids.push(guid);}});if(!guids.length){elgg.register_error(elgg.echo('admin:plugins:already:'+state));unfreezePlugins();return;}spinner.start();var $form=$('< form method="post"/>');$form.prop('action', elgg.security.addToken(this.href));$form.append('< input type="hidden"name="guids"value="' + guids.join(',') + '"/>');$form.appendTo("body").submit();};function movePlugin(e, ui){freezePlugins();var pluginGuid=ui.item.attr('id');pluginGuid=pluginGuid.replace('elgg-object-', '');elgg.action('admin/plugins/set_priority',{data:{plugin_guid:pluginGuid, priority:ui.item.index()+1}, success:function(){var priorityDep=new RegExp(elgg.echo('ElggPlugin:Dependencies:Priority'));ui.item.siblings().andSelf().each(function(){if(priorityDep.test($(this).find('.elgg-dependency-requires').text())){updatePluginView($(this));}});unfreezePlugins();}});};function updatePluginView(pluginView){var pluginGuid=pluginView.attr('id');pluginGuid=pluginGuid.replace('elgg-object-', '');elgg.get({url:elgg.config.wwwroot+"ajax/view/object/plugin/full", dataType:"html", cache:false, data:{guid:pluginGuid, display_reordering:true}, success:function(htmlData){if(htmlData.length > 0){pluginView.html(htmlData);}}});};function filterPluginCategory(e){e.preventDefault();$(".elgg-admin-plugins-categories > li").removeClass("elgg-state-selected");$(".elgg-plugin").hide();$(".elgg-plugin-category-"+$(this).attr("rel")).show();$(this).closest('li').addClass("elgg-state-selected");};function showPluginScreenshot(){$(this).parent().find(".elgg-plugin-screenshot").removeClass("elgg-state-selected");$(this).addClass("elgg-state-selected");$(".elgg-plugin-details-screenshots > div > img").hide();$(".elgg-plugin-details-screenshots > div > img[rel='"+$(this).attr("rel")+"']").show();};init();})
elgg action
Definition: ajax.js:200
var elgg
Definition: elgglib.js:4