[SPIP] v3.2.11 -> v3.2.12
[lhc/web/www.git] / www / prive / javascript / ajaxCallback.js
1 jQuery.spip=jQuery.spip || {};
2 jQuery.spip.log = function(){
3 if (jQuery.spip.debug && window.console && window.console.log)
4 window.console.log.apply(this,arguments);
5 }
6 // A plugin that wraps all ajax calls introducing a fixed callback function on ajax complete
7 if(!jQuery.spip.load_handlers) {
8 jQuery.spip.load_handlers = new Array();
9
10 /**
11 * OnAjaxLoad allow to
12 * add a function to the list of those
13 * to be executed on ajax load complete
14 *
15 * most of time function f is applied on the loaded data
16 * if not known, the whole document is targetted
17 *
18 * @param function f
19 */
20 function onAjaxLoad(f) {
21 jQuery.spip.load_handlers.push(f);
22 };
23
24 /**
25 * Call the functions that have been added to onAjaxLoad
26 * @param root
27 */
28 jQuery.spip.triggerAjaxLoad = function (root) {
29 jQuery.spip.log('triggerAjaxLoad');
30 jQuery.spip.log(root);
31 for ( var i = 0; i < jQuery.spip.load_handlers.length; i++ )
32 jQuery.spip.load_handlers[i].apply( root );
33 };
34
35 jQuery.spip.intercepted={};
36
37 // intercept jQuery.fn.load
38 jQuery.spip.intercepted.load = jQuery.fn.load;
39 jQuery.fn.load = function( url, params, callback ) {
40 if ( typeof url !== "string") {
41 return jQuery.spip.intercepted.load.apply( this, arguments );
42 }
43
44 callback = callback || function(){};
45
46 // If the second parameter was provided
47 if ( params ) {
48 // If it's a function
49 if ( params.constructor == Function ) {
50 // We assume that it's the callback
51 callback = params;
52 params = null;
53 }
54 }
55 params = jQuery.extend(params, {triggerAjaxLoad:false}); // prevent $.ajax to triggerAjaxLoad
56 var callback2 = function() {jQuery.spip.log('jQuery.load');jQuery.spip.triggerAjaxLoad(this);callback.apply(this,arguments);};
57 return jQuery.spip.intercepted.load.apply(this,[url, params, callback2]);
58 };
59
60 // intercept jQuery.fn.ajaxSubmit
61 jQuery.spip.intercepted.ajaxSubmit = jQuery.fn.ajaxSubmit;
62 jQuery.fn.ajaxSubmit = function(options){
63 // find the first parent that will not be removed by formulaire_dyn_ajax
64 // or take the whole document
65 options = options || {};
66 if (typeof options.onAjaxLoad=="undefined" || options.onAjaxLoad!=false) {
67 var me=jQuery(this).parents('div.ajax');
68 if (me.length)
69 me=me.parent();
70 else
71 me = document;
72 if (typeof options=='function')
73 options = { success: options };
74 var callback = options.success || function(){};
75 options.success = function(){callback.apply(this,arguments);jQuery.spip.log('jQuery.ajaxSubmit');jQuery.spip.triggerAjaxLoad(me);}
76 }
77 return jQuery.spip.intercepted.ajaxSubmit.apply(this,[options]);
78 }
79
80 // intercept jQuery.ajax
81 jQuery.spip.intercepted.ajax = jQuery.ajax;
82 jQuery.ajax = function(url, settings) {
83 if (typeof settings == 'undefined') {
84 settings = {};
85 if (typeof url == 'object') {
86 settings = url;
87 url = null;
88 }
89 }
90 if (typeof url == 'string') {
91 settings['url'] = url;
92 }
93 // if triggerAjaxLoad is prevented, finish without faking callback
94 if (settings.data && settings.data['triggerAjaxLoad'] === false) {
95 settings.data['triggerAjaxLoad'] = null;
96 return jQuery.spip.intercepted.ajax(settings);
97 }
98 var s = jQuery.extend(true, {}, jQuery.ajaxSettings, settings);
99 var callbackContext = s.context || s;
100 try {
101 if (jQuery.ajax.caller==jQuery.spip.intercepted.load || jQuery.ajax.caller==jQuery.spip.intercepted.ajaxSubmit)
102 return jQuery.spip.intercepted.ajax(settings);
103 }
104 catch (err){}
105 var orig_complete = s.complete || function() {};
106 settings.complete = function(res,status) {
107 // Do not fire OnAjaxLoad if the dataType is not html
108 var dataType = settings.dataType;
109 var ct = (res && (typeof res.getResponseHeader == 'function'))
110 ? res.getResponseHeader("content-type"): '';
111 var xml = !dataType && ct && ct.indexOf("xml") >= 0;
112 orig_complete.call( callbackContext, res, status);
113 if((!dataType && !xml) || dataType == "html") {
114 jQuery.spip.log('jQuery.ajax');
115 if (typeof s.onAjaxLoad=="undefined" || s.onAjaxLoad!=false)
116 jQuery.spip.triggerAjaxLoad(s.ajaxTarget?s.ajaxTarget:document);
117 }
118 };
119 return jQuery.spip.intercepted.ajax(settings);
120 };
121
122 }
123
124 /* jQuery.browser */
125 jQuery.uaMatch = function( ua ) {
126 ua = ua.toLowerCase();
127
128 var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
129 /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
130 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
131 /(msie) ([\w.]+)/.exec( ua ) ||
132 ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
133 [];
134
135 return {
136 browser: match[ 1 ] || "",
137 version: match[ 2 ] || "0"
138 };
139 };
140
141 // Don't clobber any existing jQuery.browser in case it's different
142 if ( !jQuery.browser ) {
143 matched = jQuery.uaMatch( navigator.userAgent );
144 browser = {};
145
146 if ( matched.browser ) {
147 browser[ matched.browser ] = true;
148 browser.version = matched.version;
149 }
150
151 // Chrome is Webkit, but Webkit is also Safari.
152 if ( browser.chrome ) {
153 browser.webkit = true;
154 } else if ( browser.webkit ) {
155 browser.safari = true;
156 }
157
158 jQuery.browser = browser;
159 }
160
161 // jQuery.getScript cache par defaut
162 jQuery.getScript = function(url,callback){
163 return $.ajax({
164 url: url,
165 dataType: "script",
166 success: callback,
167 cache: true
168 });
169 }
170
171 /**
172 * if not fully visible, scroll the page to position
173 * target block at the top of page
174 * if force = true, allways scroll
175 *
176 * @param bool force
177 */
178 jQuery.fn.positionner = function(force, setfocus) {
179 var offset = jQuery(this).offset();
180 var hauteur = parseInt(jQuery(this).css('height'));
181 var scrolltop = self['pageYOffset'] ||
182 jQuery.boxModel && document.documentElement[ 'scrollTop' ] ||
183 document.body[ 'scrollTop' ];
184 var h = jQuery(window).height();
185 var scroll=0;
186
187 if (force || (offset && offset['top'] - 5 <= scrolltop))
188 scroll = offset['top'] - 5;
189 else if (offset && offset['top'] + hauteur - h + 5 > scrolltop)
190 scroll = Math.min(offset['top'] - 5, offset['top'] + hauteur - h + 15);
191 if (scroll)
192 jQuery('html,body')
193 .animate({scrollTop: scroll}, 300);
194
195 // positionner le curseur dans la premiere zone de saisie
196 if (setfocus!==false)
197 jQuery(jQuery('*', this).filter('input[type=text],textarea')[0]).focus();
198 return this; // don't break the chain
199 }
200
201 // deux fonctions pour rendre l'ajax compatible Jaws
202 jQuery.spip.virtualbuffer_id='spip_virtualbufferupdate';
203 jQuery.spip.initReaderBuffer = function(){
204 if (jQuery('#'+jQuery.spip.virtualbuffer_id).length) return;
205 jQuery('body').append('<p style="float:left;width:0;height:0;position:absolute;left:-5000px;top:-5000px;"><input type="hidden" name="'+jQuery.spip.virtualbuffer_id+'" id="'+jQuery.spip.virtualbuffer_id+'" value="0" /></p>');
206 }
207 jQuery.spip.updateReaderBuffer = function(){
208 var i = jQuery('#'+jQuery.spip.virtualbuffer_id);
209 if (!i.length) return;
210 // incrementons l'input hidden, ce qui a pour effet de forcer le rafraichissement du
211 // buffer du lecteur d'ecran (au moins dans Jaws)
212 i.val(parseInt(i.val())+1);
213 }
214
215 jQuery.fn.formulaire_setARIA = function(){
216 if (!this.closest('.ariaformprop').length){
217 // eviter une double execution du js au moment de sa reinsertion dans le DOM par wrap()
218 // cf http://bugs.jquery.com/ticket/7447
219 this.find('script').remove();
220 this.wrap('<div class="ariaformprop" aria-live="polite" aria-atomic="true" aria-relevant="additions"></div>');
221 // dans un formulaire, le screen reader relit tout a chaque saisie d'un caractere si on est en aria-live
222 jQuery('form',this).not('[aria-live]').attr('aria-live','off');
223 }
224 return this;
225 }
226 /**
227 * rechargement ajax d'un formulaire dynamique implemente par formulaires/xxx.html
228 * @param target
229 */
230 jQuery.fn.formulaire_dyn_ajax = function(target) {
231 if (this.length)
232 jQuery.spip.initReaderBuffer();
233 return this.each(function() {
234 var scrollwhensubmit = !jQuery(this).is('.noscroll');
235 var cible = target || this;
236 jQuery(cible).formulaire_setARIA();
237 jQuery('form:not(.noajax):not(.bouton_action_post)', this).each(function(){
238 var leform = this;
239 var leclk,leclk_x,leclk_y;
240 var onError = function(xhr, status, error, $form){
241 jQuery(leform).ajaxFormUnbind().find('input[name="var_ajax"]').remove();
242 var msg = "Erreur";
243 if (typeof(error_on_ajaxform)!=="undefined") msg = error_on_ajaxform;
244 jQuery(leform).prepend("<p class='error ajax-error none'>"+msg+"</p>").find('.ajax-error').show('fast');
245 jQuery(cible).closest('.ariaformprop').endLoading(true);
246 }
247 jQuery(this).prepend("<input type='hidden' name='var_ajax' value='form' />")
248 .ajaxForm({
249 beforeSubmit: function(){
250 // memoriser le bouton clique, en cas de repost non ajax
251 leclk = leform.clk;
252 var scrollwhensubmit_button = true;
253 if (leclk) {
254 scrollwhensubmit_button = !jQuery(leclk).is('.noscroll');
255 var n = leclk.name;
256 if (n && !leclk.disabled && leclk.type == "image") {
257 leclk_x = leform.clk_x;
258 leclk_y = leform.clk_y;
259 }
260 }
261 jQuery(cible).wrap('<div />');
262 cible = jQuery(cible).parent();
263 jQuery(cible).closest('.ariaformprop').animateLoading();
264 if (scrollwhensubmit && scrollwhensubmit_button) {
265 jQuery(cible).positionner(false,false);
266 }
267 },
268 error: onError,
269 success: function(c, status, xhr , $form){
270 if (c.match(/^\s*noajax\s*$/)){
271 // le serveur ne veut pas traiter ce formulaire en ajax
272 // on resubmit sans ajax
273 jQuery("input[name=var_ajax]",leform).remove();
274 // si on a memorise le nom et la valeur du bouton clique
275 // les reinjecter dans le dom sous forme de input hidden
276 // pour que le serveur les recoive
277 if (leclk){
278 var n = leclk.name;
279 if (n && !leclk.disabled) {
280 jQuery(leform).prepend("<input type='hidden' name='"+n+"' value='"+leclk.value+"' />");
281 if (leclk.type == "image") {
282 jQuery(leform).prepend("<input type='hidden' name='"+n+".x' value='"+leform.clk_x+"' />");
283 jQuery(leform).prepend("<input type='hidden' name='"+n+".y' value='"+leform.clk_y+"' />");
284 }
285 }
286 }
287 jQuery(leform).ajaxFormUnbind().submit();
288 }
289 else {
290 if (!c.length || c.indexOf("ajax-form-is-ok")==-1)
291 return onError.apply(this,[status, xhr , $form]);
292 // commencons par vider le cache des urls, si jamais un js au retour
293 // essaye tout de suite de suivre un lien en cache
294 // dans le doute sur la validite du cache il vaut mieux l'invalider
295 var preloaded = jQuery.spip.preloaded_urls;
296 jQuery.spip.preloaded_urls = {};
297 jQuery(cible).html(c);
298 var a = jQuery('a:first',cible).eq(0);
299 var d = jQuery('div.ajax',cible);
300 if (!d.length){
301 // si pas .ajax dans le form, remettre la classe sur le div que l'on a insere
302 jQuery(cible).addClass('ajax');
303 if (!scrollwhensubmit)
304 jQuery(cible).addClass('noscroll');
305 }
306 else {
307 // sinon nettoyer les br ajaxie
308 d.siblings('br.bugajaxie').remove();
309 // desemboiter d'un niveau pour manger le div que l'on a insere
310 cible = jQuery(":first",cible);
311 cible.unwrap();
312 }
313 // chercher une ancre en debut de html pour positionner la page
314 if (a.length
315 && a.is('a[name=ajax_ancre]')
316 && jQuery(a.attr('href'),cible).length){
317 a = a.attr('href');
318 if (jQuery(a,cible).length)
319 setTimeout(function(){
320 jQuery(a,cible).positionner(false);
321 //a = a.split('#');
322 //window.location.hash = a[1];
323 },10);
324 jQuery(cible).closest('.ariaformprop').endLoading(true);
325 }
326 else{
327 //jQuery(cible).positionner(false);
328 if (a.length && a.is('a[name=ajax_redirect]')){
329 a = a.get(0).href;
330 setTimeout(function(){
331 var cur = window.location.href.split('#');
332 document.location.replace(a);
333 // regarder si c'est juste un changement d'ancre : dans ce cas il faut reload
334 // (le faire systematiquement provoque des bugs)
335 if (cur[0]==a.split('#')[0]){
336 window.location.reload();
337 }
338 },10);
339 // ne pas arreter l'etat loading, puisqu'on redirige !
340 // mais le relancer car l'image loading a pu disparaitre
341 jQuery(cible).closest('.ariaformprop').animateLoading();
342 }
343 else {
344 jQuery(cible).closest('.ariaformprop').endLoading(true);
345 }
346 }
347 // si jamais le formulaire n'a pas un retour OK, retablissons le cache
348 // car a priori on a pas fait d'operation en base de donnee
349 if (!jQuery('.reponse_formulaire_ok',cible).length)
350 jQuery.spip.preloaded_urls = preloaded;
351 // mettre a jour le buffer du navigateur pour aider jaws et autres readers
352 // a supprimer ?
353 jQuery.spip.updateReaderBuffer();
354 }
355 }/*,
356 iframe: jQuery.browser.msie*/
357 })
358 // previent qu'on n'ajaxera pas deux fois le meme formulaire en cas de ajaxload
359 // mais le marquer comme ayant l'ajax au cas ou on reinjecte du contenu ajax dedans
360 .addClass('noajax hasajax');
361 });
362 });
363 }
364
365 jQuery.fn.formulaire_verifier = function(callback, champ){
366 var erreurs = {'message_erreur':'form non ajax'};
367 var me=this;
368 // si on est aussi en train de submit pour de vrai, abandonner
369 if (jQuery(me).closest('.ariaformprop').attr('aria-busy')!='true') {
370 if (jQuery(me).is('form.hasajax')){
371 jQuery(me).ajaxSubmit({
372 dataType:"json",
373 data:{formulaire_action_verifier_json:true},
374 success:function(errs){
375 var args = [errs, champ]
376 // si on est aussi en train de submit pour de vrai, abandonner
377 if (jQuery(me).closest('.ariaformprop').attr('aria-busy')!='true')
378 callback.apply(me,args);
379 }
380 });
381 }
382 else
383 callback.apply(me,[erreurs, champ]);
384 }
385 return this;
386 }
387
388 jQuery.fn.formulaire_activer_verif_auto = function(callback){
389 callback = callback || formulaire_actualiser_erreurs;
390 var me = jQuery(this).closest('.ariaformprop');
391 var check = function(){
392 var name=jQuery(this).attr('name');
393 // declencher apres 50ms pour ne pas double submit sur sequence saisie+submit
394 setTimeout(function(){me.find('form').formulaire_verifier(callback,name);},50);
395 }
396 var activer = function(){
397 if (me.find('form').attr('data-verifjson')!='on'){
398 me
399 .find('form')
400 .attr('data-verifjson','on')
401 .find('input,select,textarea')
402 .on('change', check);
403 }
404 }
405 jQuery(activer);
406 onAjaxLoad(function(){setTimeout(activer,150);});
407 }
408
409 function formulaire_actualiser_erreurs(erreurs){
410 var parent = jQuery(this).closest('.formulaire_spip');
411 if (!parent.length) return;
412 // d'abord effacer tous les messages d'erreurs
413 parent.find('.reponse_formulaire,.erreur_message').fadeOut().remove();
414 parent.find('.erreur').removeClass('erreur');
415 // ensuite afficher les nouveaux messages d'erreur
416 if (erreurs['message_ok'])
417 parent.find('form').before('<p class="reponse_formulaire reponse_formulaire_ok">'+erreurs['message_ok']+'</p>');
418 if (erreurs['message_erreur'])
419 parent.find('form').before('<p class="reponse_formulaire reponse_formulaire_erreur">'+erreurs['message_erreur']+'</p>');
420 for (var k in erreurs){
421 var saisie = parent.find('.editer_'+k);
422 if (saisie.length) {
423 saisie.addClass('erreur');
424 saisie.find('label').after('<span class="erreur_message">'+erreurs[k]+'</span>');
425 }
426 }
427 }
428
429
430 // permettre d'utiliser onclick='return confirm('etes vous sur?');' sur un lien ajax
431 var ajax_confirm=true;
432 var ajax_confirm_date=0;
433 var spip_confirm = window.confirm;
434 function _confirm(message){
435 ajax_confirm = spip_confirm(message);
436 if (!ajax_confirm) {
437 var d = new Date();
438 ajax_confirm_date = d.getTime();
439 }
440 return ajax_confirm;
441 }
442 window.confirm = _confirm;
443
444 /**
445 * rechargement ajax d'une noisette implementee par {ajax}
446 * selecteur personalise, sera defini par defaut a '.pagination a,a.ajax'
447 */
448 var ajaxbloc_selecteur;
449
450 /**
451 * mise en cache des url. Il suffit de vider cete variable pour vider le cache
452 */
453 jQuery.spip.preloaded_urls = {};
454
455 /**
456 * Afficher dans la page
457 * le html d'un bloc ajax charge
458 * @param object blocfrag
459 * @param string c
460 * @param string href
461 * @param bool history
462 */
463 jQuery.spip.on_ajax_loaded = function(blocfrag,c,href,history) {
464 history = history || (history==null);
465 if (typeof href == undefined || href==null)
466 history = false;
467 if (history)
468 jQuery.spip.setHistoryState(blocfrag);
469
470 if (jQuery(blocfrag).attr('data-loaded-callback')){
471 var callback = eval(jQuery(blocfrag).attr('data-loaded-callback'));
472 callback.call(blocfrag, c, href, history);
473 }
474 else {
475 jQuery(blocfrag)
476 .html(c)
477 .endLoading();
478 }
479
480 if (typeof href != undefined)
481 jQuery(blocfrag).attr('data-url',href);
482 if (history) {
483 jQuery.spip.pushHistoryState(href);
484 jQuery.spip.setHistoryState(blocfrag);
485 }
486
487 var a = jQuery('a:first',jQuery(blocfrag)).eq(0);
488 if (a.length
489 && a.is('a[name=ajax_ancre]')
490 && jQuery(a.attr('href'),blocfrag).length){
491 a = a.attr('href');
492 jQuery(a,blocfrag).positionner(false);
493 }
494 jQuery.spip.log('on_ajax_loaded');
495 jQuery.spip.triggerAjaxLoad(blocfrag);
496 // si le fragment ajax est dans un form ajax,
497 // il faut remettre a jour les evenements attaches
498 // car le fragment peut comporter des submit ou button
499 a = jQuery(blocfrag).parents('form.hasajax')
500 if (a.length)
501 a.eq(0).removeClass('noajax').parents('div.ajax').formulaire_dyn_ajax();
502 jQuery.spip.updateReaderBuffer();
503 }
504
505 jQuery.spip.on_ajax_failed = function(blocfrag,statusCode,href,history) {
506 // marquer le bloc invalide
507 jQuery(blocfrag).addClass('invalid');
508 // si c'est une erreur 400 on a perdu la signature ajax
509 //console.log("AJAX Erreur");
510 //console.log(statusCode);
511 history = history || (history==null);
512 // quelle que soit l'erreur, on redirige si c'était la nouvelle URL principale de la page
513 if (history) {
514 //console.log("On redirige : " + href);
515 window.location.href = href;
516 }
517 }
518
519 jQuery.spip.stateId=0;
520 jQuery.spip.setHistoryState = function(blocfrag){
521 if (!window.history.replaceState) return;
522 // attribuer un id au bloc si il n'en a pas
523 if (!blocfrag.attr('id')){
524 while (jQuery('#ghsid'+jQuery.spip.stateId).length)
525 jQuery.spip.stateId++;
526 blocfrag.attr('id','ghsid'+jQuery.spip.stateId);
527 }
528 var href= blocfrag.attr('data-url') || blocfrag.attr('data-origin');
529 href = jQuery("<"+"a href='"+href+"'></a>").get(0).href;
530 var state={
531 id:blocfrag.attr('id'),
532 href: href
533 };
534 var ajaxid = blocfrag.attr('class').match(/\bajax-id-[\w-]+\b/);
535 if (ajaxid && ajaxid.length)
536 state["ajaxid"] = ajaxid[0];
537 // on remplace la variable qui decrit l'etat courant
538 // initialement vide
539 // -> elle servira a revenir dans l'etat courant
540 window.history.replaceState(state,window.document.title, window.document.location);
541 }
542
543 jQuery.spip.pushHistoryState = function(href, title){
544 if (!window.history.pushState)
545 return false;
546 window.history.pushState({}, title, href);
547 }
548
549 window.onpopstate = function(popState){
550 if (popState.state && popState.state.href){
551 var blocfrag=false;
552 if (popState.state.id){
553 blocfrag=jQuery('#'+popState.state.id);
554 }
555 if ((!blocfrag || !blocfrag.length) && popState.state.ajaxid){
556 blocfrag=jQuery('.ajaxbloc.'+popState.state.ajaxid);
557 }
558 if (blocfrag && blocfrag.length==1) {
559 jQuery.spip.ajaxClick(blocfrag,popState.state.href,{history:false});
560 return true;
561 }
562 // si on revient apres avoir rompu la chaine ajax, on a pu perdre l'id #ghsidxx ajoute en JS
563 // dans ce cas on redirige hors ajax
564 else {
565 window.location.href = popState.state.href;
566 }
567 }
568 }
569
570 /**
571 * Charger un bloc ajax represente par l'objet jQuery blocajax qui le pointe
572 * avec la requete ajax url, qui represente le lien href
573 * @param object blocfrag
574 * bloc cible
575 * @param string url
576 * url pour la requete ajax
577 * @param string href
578 * url du lien clique
579 * @param object options
580 * bool force : pour forcer la requete sans utiliser le cache
581 * function callback : callback au retour du chargement
582 * bool history : prendre en charge l'histrisation dans l'url
583 */
584 jQuery.spip.loadAjax = function(blocfrag,url, href, options){
585 var force = options.force || false;
586 if (jQuery(blocfrag).attr('data-loading-callback')){
587 var callback = eval(jQuery(blocfrag).attr('data-loading-callback'));
588 callback.call(blocfrag,url,href,options);
589 }
590 else {
591 jQuery(blocfrag).animateLoading();
592 }
593 if (jQuery.spip.preloaded_urls[url] && !force) {
594 // si on est deja en train de charger ce fragment, revenir plus tard
595 if (jQuery.spip.preloaded_urls[url]=="<!--loading-->"){
596 setTimeout(function(){jQuery.spip.loadAjax(blocfrag,url,href,options);},100);
597 return;
598 }
599 jQuery.spip.on_ajax_loaded(blocfrag,jQuery.spip.preloaded_urls[url],href,options.history);
600 } else {
601 var d = new Date();
602 jQuery.spip.preloaded_urls[url] = "<!--loading-->";
603 jQuery.ajax({
604 url: parametre_url(url,'var_t',d.getTime()),
605 onAjaxLoad:false,
606 success: function(c){
607 jQuery.spip.on_ajax_loaded(blocfrag,c,href,options.history);
608 jQuery.spip.preloaded_urls[url] = c;
609 if (options.callback && typeof options.callback == "function")
610 options.callback.apply(blocfrag);
611 },
612 error: function(e){
613 jQuery.spip.preloaded_urls[url]='';
614 jQuery.spip.on_ajax_failed(blocfrag,e.status,href,options.history);
615 }
616 });
617 }
618 }
619
620 /**
621 * Calculer l'url ajax a partir de l'url du lien
622 * et de la variable d'environnement du bloc ajax
623 * passe aussi l'ancre eventuelle sous forme d'une variable
624 * pour que le serveur puisse la prendre en compte
625 * et la propager jusqu'a la reponse
626 * sous la forme d'un lien cache
627 *
628 * @param string href
629 * @param string ajax_env
630 */
631 jQuery.spip.makeAjaxUrl = function(href,ajax_env,origin){
632 var url = href.split('#');
633 url[0] = parametre_url(url[0],'var_ajax',1);
634 url[0] = parametre_url(url[0],'var_ajax_env',ajax_env);
635
636 // les arguments de origin qui ne sont pas dans href doivent etre explicitement fournis vides dans url
637 if (origin){
638 var p=origin.indexOf('?');
639 if (p!==-1){
640 // recuperer la base
641 var args = origin.substring(p+1).split('&');
642 var val;
643 var arg;
644 for(var n=0;n<args.length;n++){
645 arg = args[n].split('=');
646 arg = arg[0];
647 p = arg.indexOf('[');
648 if (p!==-1)
649 arg = arg.substring(0,p);
650 val = parametre_url(href,arg);
651 if (typeof val=="undefined" || val==null)
652 url[0] = url[0] + '&' + arg + '=';
653 }
654 }
655 }
656
657 if (url[1])
658 url[0] = parametre_url(url[0],'var_ajax_ancre',url[1]);
659 return url[0];
660 }
661
662 /**
663 * fonction appelee sur l'evenement ajaxReload d'un bloc ajax
664 * que l'on declenche quand on veut forcer sa mise a jour
665 *
666 * @param object blocfrag
667 * @param object options
668 * callback : fonction appelee apres le rechargement
669 * href : url to load instead of origin url
670 * args : arguments passes a l'url rechargee (permet une modif du contexte)
671 * history : bool to specify if navigation history is modified by reload or not (false if not provided)
672 */
673 jQuery.spip.ajaxReload = function(blocfrag, options){
674 var ajax_env = blocfrag.attr('data-ajax-env');
675 if (!ajax_env || ajax_env==undefined) return;
676 var href = options.href || blocfrag.attr('data-url') || blocfrag.attr('data-origin');
677 if (href && typeof href != undefined){
678 options = options || {};
679 var callback=options.callback || null;
680 var history=options.history || false;
681 var args = options.args || {};
682 for (var key in args)
683 href = parametre_url(href,key,args[key]==undefined?'':args[key],'&',args[key]==undefined?false:true);
684 var url = jQuery.spip.makeAjaxUrl(href,ajax_env,blocfrag.attr('data-origin'));
685 // recharger sans historisation dans l'url
686 jQuery.spip.loadAjax(blocfrag, url, href, {force:true, callback:callback, history:history});
687 return true;
688 }
689 }
690
691 /**
692 * fonction appelee sur l'evenement click d'un lien ajax
693 *
694 * @param object blocfrag
695 * objet jQuery qui cible le bloc ajax contenant
696 * @param string href
697 * url du lien a suivre
698 * @param object options
699 * force : pour interdire l'utilisation du cache
700 * history : pour interdire la mise en historique
701 */
702 jQuery.spip.ajaxClick = function(blocfrag, href, options){
703 var ajax_env = blocfrag.attr('data-ajax-env');
704 if (!ajax_env || ajax_env==undefined) return;
705 if (!ajax_confirm) {
706 // on rearme pour le prochain clic
707 ajax_confirm=true;
708 var d = new Date();
709 // seule une annulation par confirm() dans les 2 secondes precedentes est prise en compte
710 if ((d.getTime()-ajax_confirm_date)<=2)
711 return false;
712 }
713 var url = jQuery.spip.makeAjaxUrl(href,ajax_env,blocfrag.attr('data-origin'));
714 jQuery.spip.loadAjax(blocfrag, url, href, options);
715 return false;
716 }
717
718 /**
719 * Implementer le comportemant des liens ajax
720 * et boutons post ajax qui se comportent
721 * comme un lien ajax
722 */
723 jQuery.fn.ajaxbloc = function() {
724 // hack accessibilite vieille version de JAWS
725 // a supprimer ?
726 if (this.length)
727 jQuery.spip.initReaderBuffer();
728 if (ajaxbloc_selecteur==undefined)
729 ajaxbloc_selecteur = '.pagination a,a.ajax';
730
731 return this.each(function() {
732 // traiter les enfants d'abord :
733 // un lien ajax provoque le rechargement
734 // du plus petit bloc ajax le contenant
735 jQuery('div.ajaxbloc',this).ajaxbloc();
736 var blocfrag = jQuery(this);
737
738 var ajax_env = blocfrag.attr('data-ajax-env');
739 if (!ajax_env || ajax_env==undefined) return;
740
741 blocfrag.not('.bind-ajaxReload').on('ajaxReload', function(event, options){
742 if (jQuery.spip.ajaxReload(blocfrag,options))
743 // don't trig reload of parent blocks
744 event.stopPropagation();
745 }).addClass('bind-ajaxReload')
746 .attr('aria-live','polite').attr('aria-atomic','true');
747
748 // dans un formulaire, le screen reader relit tout a chaque saisie d'un caractere si on est en aria-live
749 // mettre un aria-live="off" sur les forms inclus dans ce bloc aria-live="polite"
750 jQuery('form',this).not('[aria-live]').attr('aria-live','off');
751
752 jQuery(ajaxbloc_selecteur,this).not('.noajax,.bind-ajax')
753 .click(function(){return jQuery.spip.ajaxClick(blocfrag,this.href,{force:jQuery(this).is('.nocache'),history:!(jQuery(this).is('.nohistory')||jQuery(this).closest('.box_modalbox').length)});})
754 .addClass('bind-ajax')
755 .filter('.preload').each(function(){
756 var href = this.href;
757 var url = jQuery.spip.makeAjaxUrl(href,ajax_env,blocfrag.attr('data-origin'));
758 if (!jQuery.spip.preloaded_urls[url]) {
759 jQuery.spip.preloaded_urls[url] = '<!--loading-->';
760 jQuery.ajax({url:url,onAjaxLoad:false,success:function(r){jQuery.spip.preloaded_urls[url]=r;},error:function(){jQuery.spip.preloaded_urls[url]='';}});
761 }
762 }); // previent qu'on ajax pas deux fois le meme lien
763
764 // ajaxer les boutons actions qui sont techniquement des form minimaux
765 // mais se comportent comme des liens
766 jQuery('form.bouton_action_post.ajax', this).not('.noajax,.bind-ajax').each(function(){
767 var leform = this;
768 var url = jQuery(this).attr('action').split('#');
769 var scrollwhensubmit = (!jQuery(this).is('.noscroll') && !jQuery('.submit',this).is('.noscroll'));
770 jQuery(this)
771 .prepend("<input type='hidden' name='var_ajax' value='1' /><input type='hidden' name='var_ajax_env' value='"+(ajax_env)+"' />"+(url[1]?"<input type='hidden' name='var_ajax_ancre' value='"+url[1]+"' />":""))
772 .ajaxForm({
773 beforeSubmit: function(){
774 jQuery(blocfrag).animateLoading();
775 if (scrollwhensubmit) {
776 jQuery(blocfrag).positionner(false);
777 }
778 },
779 onAjaxLoad:false,
780 success: function(c){
781 jQuery.spip.on_ajax_loaded(blocfrag,c);
782 jQuery.spip.preloaded_urls = {}; // on vide le cache des urls car on a fait une action en bdd
783 },
784 error: function(e){
785 jQuery.spip.preloaded_urls = {}; // on vide le cache des urls car on a fait une action en bdd
786 var href = parametre_url(url, 'redirect');
787 if (!href) {
788 href = window.location.href;
789 }
790 // si c'est une erreur 400 c'est un fragment ajax invalide, il faut rediriger vers href
791 jQuery.spip.on_ajax_failed(blocfrag,e.status,href,e.status === 400);
792 }/*,
793 iframe: jQuery.browser.msie*/
794 })
795 .addClass('bind-ajax'); // previent qu'on n'ajaxera pas deux fois le meme formulaire en cas de ajaxload
796 });
797 });
798 };
799
800 /**
801 * Suivre un lien en simulant le click sur le lien
802 * Si le lien est ajax, on se contente de declencher l'evenement click()
803 * Si le lien est non ajax, on finit en remplacant l'url de la page
804 */
805 jQuery.fn.followLink = function(){
806 $(this).click();
807 if (!$(this).is('.bind-ajax'))
808 window.location.href = $(this).get(0).href;
809 return this;
810 }
811 /**
812 * Recharger un bloc ajax pour le mettre a jour
813 * ajaxid est l'id passe en argument de INCLURE{ajax=ajaxid}
814 * options permet de definir une callbackk ou de passer des arguments a l'url
815 * au rechargement
816 * ajaxReload peut s'utiliser en passant un id :
817 * ajaxReload('xx');
818 * ou sur un objet jQuery
819 * jQuery(this).ajaxReload();
820 * Dans ce dernier cas, le plus petit conteneur ajax est recharge
821 *
822 * @param string ajaxid
823 * @param object options
824 * callback : callback after reloading
825 * href : url to load instead of origin url
826 * args : {arg:value,...} to pass tu the url
827 * history : bool to specify if navigation history is modified by reload or not (false if not provided)
828 */
829 function ajaxReload(ajaxid, options){
830 jQuery('div.ajaxbloc.ajax-id-'+ajaxid).ajaxReload(options);
831 }
832
833 /**
834 * Variante jQuery de ajaxReload pour la syntaxe
835 * jQuery(..).ajaxReload();
836 * cf doc ci-dessus
837 * @param options
838 */
839 jQuery.fn.ajaxReload = function(options){
840 options = options||{};
841 // just trigg the event, as it will bubble up the DOM
842 jQuery(this).trigger('ajaxReload', [options]);
843 return this; // don't break the chain
844 }
845
846 /**
847 * animation du bloc cible pour faire patienter
848 *
849 */
850 jQuery.fn.animateLoading = function() {
851 this.attr('aria-busy','true').addClass('loading').children().css('opacity', 0.5);
852 if (typeof ajax_image_searching != 'undefined'){
853 var i = (this).find('.image_loading');
854 if (i.length) i.eq(0).html(ajax_image_searching);
855 else this.prepend('<span class="image_loading">'+ajax_image_searching+'</span>');
856 }
857 return this; // don't break the chain
858 }
859 // compatibilite avec ancien nommage
860 jQuery.fn.animeajax = jQuery.fn.animateLoading;
861
862 /**
863 * Fin de l'animation
864 * l'argument permet de forcer le raz du contenu si il est inchange
865 * @param hard
866 */
867 jQuery.fn.endLoading = function(hard) {
868 hard = hard || false;
869 this.attr('aria-busy','false').removeClass('loading');
870 if (hard){
871 this.children().css('opacity', '');
872 this.find('.image_loading').html('');
873 }
874 return this; // don't break the chain
875 }
876
877 /**
878 * animation d'un item que l'on supprime :
879 * ajout de la classe remove avec un background tire de cette classe
880 * puis fading vers opacity 0
881 * quand l'element est masque, on retire les classes et css inline
882 *
883 * @param function callback
884 *
885 */
886 jQuery.fn.animateRemove = function(callback){
887 if (this.length){
888 var me=this;
889 var color = $("<div class='remove'></div>").css('background-color');
890 var sel=$(this);
891 // if target is a tr, include td childrens cause background color on tr doesn't works in a lot of browsers
892 if (sel.is('tr'))
893 sel = sel.add('>td',sel);
894 sel.addClass('remove').css({backgroundColor: color}).animate({opacity: "0.0"}, 'fast', function(){
895 sel.removeClass('remove').css({backgroundColor: ''});
896 if (callback)
897 callback.apply(me);
898 });
899 }
900 return this; // don't break the chain
901 }
902
903
904 /**
905 * animation d'un item que l'on ajoute :
906 * ajout de la classe append
907 * fading vers opacity 1 avec background herite de la classe append,
908 * puis suppression progressive du background pour revenir a la valeur heritee
909 *
910 * @param function callback
911 */
912 jQuery.fn.animateAppend = function(callback){
913 if (this.length){
914 var me=this;
915 // recuperer la couleur portee par la classe append (permet une personalisation)
916 var color = $("<div class='append'></div>").css('background-color');
917 var origin = $(this).css('background-color') || '#ffffff';
918 // pis aller
919 if (origin=='transparent') origin='#ffffff';
920 var sel=$(this);
921 // if target is a tr, include td childrens cause background color on tr doesn't works in a lot of browsers
922 if (sel.is('tr'))
923 sel = sel.add('>td',sel);
924 sel.css('opacity','0.0').addClass('append').css({backgroundColor: color}).animate({opacity: "1.0"}, 1000,function(){
925 sel.animate({backgroundColor: origin}, 3000,function(){
926 sel.removeClass('append').css({backgroundColor: ''});
927 if (callback)
928 callback.apply(me);
929 });
930 });
931 }
932 return this; // don't break the chain
933 }
934
935 /**
936 * Equivalent js de parametre_url php de spip
937 *
938 * Exemples :
939 * parametre_url(url,suite,18) (ajout)
940 * parametre_url(url,suite,'') (supprime)
941 * parametre_url(url,suite) (lit la valeur suite)
942 * parametre_url(url,suite[],1) (tableau valeurs multiples)
943 * @param url
944 * url
945 * @param c
946 * champ
947 * @param v
948 * valeur
949 * @param sep
950 * separateur '&' par defaut
951 * @param force_vide
952 * si true et v='' insere &k= dans l'url au lieu de supprimer le k (false par defaut)
953 * permet de vider une valeur dans une requete ajax (dans un reload)
954 */
955 function parametre_url(url,c,v,sep,force_vide){
956 // Si l'URL n'est pas une chaine, on ne peut pas travailler dessus et on quitte
957 if (typeof(url) == 'undefined'){
958 url = '';
959 }
960
961 var p;
962 // lever l'#ancre
963 var ancre='';
964 var a='./';
965 var args=[];
966 p = url.indexOf('#');
967 if (p!=-1) {
968 ancre=url.substring(p);
969 url = url.substring(0,p);
970 }
971
972 // eclater
973 p=url.indexOf('?');
974 if (p!==-1){
975 // recuperer la base
976 if (p>0) a=url.substring(0,p);
977 args = url.substring(p+1).split('&');
978 }
979 else
980 a=url;
981 var regexp = new RegExp('^(' + c.replace('[]','\\[\\]') + '\\[?\\]?)(=.*)?$');
982 var ajouts = [];
983 var u = (typeof(v)!=='object')?encodeURIComponent(v):v;
984 var na = [];
985 var v_read = null;
986 // lire les variables et agir
987 for(var n=0;n<args.length;n++){
988 var val = args[n];
989 try {
990 val = decodeURIComponent(val);
991 } catch(e) {}
992 var r=val.match(regexp);
993 if (r && r.length){
994 if (v==null){
995 // c'est un tableau, on memorise les valeurs
996 if (r[1].substr(-2) == '[]') {
997 if (!v_read) v_read = [];
998 v_read.push((r.length>2 && typeof r[2]!=='undefined')?r[2].substring(1):'');
999 }
1000 // c'est un scalaire, on retourne direct
1001 else {
1002 return (r.length>2 && typeof r[2]!=='undefined')?r[2].substring(1):'';
1003 }
1004 }
1005 // suppression
1006 else if (!v.length) {
1007 }
1008 // Ajout. Pour une variable, remplacer au meme endroit,
1009 // pour un tableau ce sera fait dans la prochaine boucle
1010 else if (r[1].substr(-2) != '[]') {
1011 na.push(r[1]+'='+u);
1012 ajouts.push(r[1]);
1013 }
1014 /* Pour les tableaux ont laisse tomber les valeurs de départ, on
1015 remplira à l'étape suivante */
1016 // else na.push(args[n]);
1017 }
1018 else
1019 na.push(args[n]);
1020 }
1021
1022 if (v==null) return v_read; // rien de trouve ou un tableau
1023 // traiter les parametres pas encore trouves
1024 if (v || v.length || force_vide) {
1025 ajouts = "="+ajouts.join("=")+"=";
1026 var all=c.split('|');
1027 for (n=0;n<all.length;n++){
1028 if (ajouts.search("="+all[n]+"=")==-1){
1029 if (typeof(v)!=='object'){
1030 na.push(all[n] +'='+ u);
1031 }
1032 else {
1033 var id = ((all[n].substring(-2)=='[]')?all[n]:all[n]+"[]");
1034 for(p=0;p<v.length;p++)
1035 na.push(id +'='+ encodeURIComponent(v[p]));
1036 }
1037 }
1038 }
1039 }
1040
1041 // recomposer l'adresse
1042 if (na.length){
1043 if (!sep) sep='&';
1044 a = a+"?"+na.join(sep);
1045 }
1046
1047 return a + ancre;
1048 }
1049
1050
1051
1052 // Ajaxer les formulaires qui le demandent, au demarrage
1053 if (!window.var_zajax_content)
1054 window.var_zajax_content = 'contenu';
1055 jQuery(function() {
1056 jQuery('form:not(.bouton_action_post)').parents('div.ajax')
1057 .formulaire_dyn_ajax();
1058 jQuery('div.ajaxbloc').ajaxbloc();
1059 jQuery("input[placeholder]:text").placeholderLabel();
1060 jQuery('a.popin').click(function(){if (jQuery.modalbox) jQuery.modalbox(parametre_url(this.href,"var_zajax",jQuery(this).attr('data-var_zajax')?jQuery(this).attr('data-var_zajax'):var_zajax_content));return false;});
1061 });
1062
1063 // ... et a chaque fois que le DOM change
1064 onAjaxLoad(function() {
1065 if (jQuery){
1066 jQuery('form:not(.bouton_action_post)', this).parents('div.ajax')
1067 .formulaire_dyn_ajax();
1068 if (jQuery(this).is('div.ajaxbloc'))
1069 jQuery(this).ajaxbloc();
1070 else if (jQuery(this).closest('div.ajaxbloc').length)
1071 jQuery(this).closest('div.ajaxbloc').ajaxbloc();
1072 else
1073 jQuery('div.ajaxbloc', this).ajaxbloc();
1074 jQuery("input[placeholder]:text",this).placeholderLabel();
1075 jQuery('a.popin',this).click(function(){if (jQuery.modalbox) jQuery.modalbox(parametre_url(this.href,"var_zajax",jQuery(this).attr('data-var_zajax')?jQuery(this).attr('data-var_zajax'):var_zajax_content));return false;});
1076 }
1077 });
1078