1/* Zotonic basic Javascript library 2---------------------------------------------------------- 3 4@package: Zotonic 2009 5@Author: Tim Benniks <[email protected]> 6@Author: Marc Worrell <[email protected]> 7 8Copyright 2009-2014 Tim Benniks, Marc Worrell 9 10Licensed under the Apache License, Version 2.0 (the "License"); 11you may not use this file except in compliance with the License. 12You may obtain a copy of the License at 13 14http://www.apache.org/licenses/LICENSE-2.0 15 16Unless required by applicable law or agreed to in writing, software 17distributed under the License is distributed on an "AS IS" BASIS, 18WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19See the License for the specific language governing permissions and 20limitations under the License. 21 22Based on nitrogen.js which is copyright 2008-2009 Rusty Klophaus 23 24---------------------------------------------------------- */ 25 26// Client state 27var z_language = "en"; 28var z_ua = "desktop"; 29var z_pageid = ''; 30var z_userid; 31var z_editor; 32 33// Session state 34var z_session_valid = false; 35var z_session_restart_count = 0; 36var z_session_reload_check = false; 37 38// Transport to/from server 39var z_websocket_host; 40var z_ws = false; 41var z_ws_pong_count = 0; 42var z_ws_ping_timeout; 43var z_ws_ping_interval; 44var z_comet; 45var z_comet_poll_timeout; 46var z_comet_reconnect_timeout = 1000; 47var z_comet_poll_count = 0; 48var z_stream_starter; 49var z_stream_start_timeout; 50var z_default_form_postback = false; 51var z_page_unloading = false; 52var z_transport_check_timer; 53var z_transport_queue = []; 54var z_transport_acks = []; 55var z_transport_delegates = { 56 javascript: z_transport_delegate_javascript, 57 session: z_transport_session_status, 58 reload: z_session_invalid_dialog 59}; 60var z_transport_retransmission_enabled = false; 61var z_force_unload_beacon = false; 62var z_init_postback_forms_timeout = false; 63 64var TRANSPORT_TIMEOUT = 30000; 65var TRANSPORT_TRIES = 3; 66 67var WEBSOCKET_PING_INTERVAL = 20000; // Send ping messages every 20 seconds 68var ACTIVITY_PERIOD = 5000; // Inactive if silent for 5 seconds 69 70// Misc state 71var z_spinner_show_ct = 0; // Set when performing an AJAX callback 72var z_last_active = 0; 73var z_input_updater = false; 74var z_drag_tag = []; 75var z_registered_events = {}; 76var z_on_visible_checks = []; 77var z_on_visible_timer; 78var z_unique_id_counter = 0; 79 80 81function z_set_page_id( page_id, user_id ) 82{ 83 ubf.add_spec('z_msg_v1', [ 84 "qos", "dup", "msg_id", "timestamp", "content_type", "delegate", 85 "push_queue", "ua_class", "session_id", "page_id", 86 "data" 87 ]); 88 ubf.add_spec('z_msg_ack', [ 89 "qos", "msg_id", "push_queue", "session_id", "page_id", "result" 90 ]); 91 ubf.add_spec('postback_notify', [ 92 "message", "trigger", "target", "data" 93 ]); 94 ubf.add_spec('postback_event', [ 95 "postback", "trigger", "target", 96 "triggervalue", "data" 97 ]); 98 ubf.add_spec('session_state', [ 99 "page_id", "user_id" 100 ]); 101 ubf.add_spec('auth_change', [ 102 "page_id" 103 ]); 104 ubf.add_spec("unload_beacon", [ 105 "session_id", 106 "page_id", 107 ]); 108 ubf.add_spec("rsc_update_done", [ 109 "action", "id", "pre_is_a", "post_is_a", 110 "pre_props", "post_props" // Always empty 111 ]); 112 ubf.add_spec("media_replace_file", [ 113 "id", 114 "medium" // Always empty 115 ]); 116 ubf.add_spec("edge_insert", [ 117 "subject_id", "predicate", "object_id", "edge_id" 118 ]); 119 ubf.add_spec("edge_delete", [ 120 "subject_id", "predicate", "object_id", "edge_id" 121 ]); 122 ubf.add_spec("edge_update", [ 123 "subject_id", "predicate", "object_id", "edge_id" 124 ]); 125 ubf.add_spec('q', [ 126 "q" 127 ]); 128 129 z_activity_init(); 130 131 if (z_pageid != page_id) { 132 z_session_valid = true; 133 z_pageid = page_id; 134 z_userid = user_id; 135 136 if (typeof pubzub == "object") { 137 setTimeout(function() { pubzub.publish("~pagesession/pageinit", page_id); }, 10); 138 } 139 } 140 $(window).bind("pageshow", function(event) { 141 // After back button on iOS / Safari 142 if (typeof event.originalEvent == 'object' && event.originalEvent.persisted) { 143 z_page_unloading = false;
144 setTimeout(function() { 145 z_stream_onreload(); 146 }, 10); 147 } 148 }); 149 $(window).bind('beforeunload', function() { 150 z_page_unloading = true; 151 152 // Keep the connection open, but because the unloading flag is set 153 // the connection will not be automatically restored if it is dropped. 154 155 setTimeout(function() { 156 z_page_unloading = false; 157 }, 10000); 158 }); 159 $(window).bind('unload', function() { 160 var msg = { 161 "_record": "unload_beacon", 162 "page_id": z_pageid, 163 "session_id": window.z_sid || undefined 164 }; 165 166 // Stop the websocket. This prevents a connection interrupted error. 167 z_websocket_stop(); 168 169 // Abort an open comet connection 170 if (z_comet) { 171 try { z_comet.abort(); } catch(e) { } 172 z_comet = undefined; 173 } 174 175 if(navigator.sendBeacon) { 176 navigator.sendBeacon("/beacon", ubf.encode(msg)); 177 return; 178 } 179 180 // If the browser doesn't have the beacon api, and we are forced to send the 181 // unload beacon we have to send it via a synchronous ajax request. 182 if(z_force_unload_beacon) { 183 $.ajax({url: "/beacon", 184 type: "post", 185 data: ubf.encode(msg), 186 dataType: "text", 187 contentType: "text/x-ubf", 188 async: false}); 189 } 190 }); 191} 192 193/* Non modal dialogs 194---------------------------------------------------------- */ 195 196function z_dialog_open(options) 197{ 198 $.dialogAdd(options); 199} 200 201function z_dialog_close() 202{ 203 $.dialogClose(); 204} 205 206function z_dialog_confirm(options) 207{ 208 var html, 209 backdrop; 210 211 if (typeof options.backdrop == 'undefined') { 212 backdrop = options.backdrop 213 } else { 214 backdrop = true; 215 } 216 html = '<div class="confirm">' + options.text + '</div>' 217 + '<div class="modal-footer">' 218 + '<button class="btn btn-default z-dialog-cancel-button">' 219 + (options.cancel||z_translate('Cancel')) 220 + '</button>' 221 + '<button class="btn btn-primary z-dialog-ok-button">' 222 + (options.ok||z_translate('OK')) 223 + '</button>' 224 + '</div>'; 225 $.dialogAdd({ 226 title: (options.title||z_translate('Confirm')), 227 text: html, 228 width: (options.width), 229 backdrop: backdrop 230 }); 231 $(".z-dialog-cancel-button").click(function() { z_dialog_close(); }); 232 $(".z-dialog-ok-button").click(function() { 233 z_dialog_close(); 234 if (options.on_confirm) options.on_confirm(); 235 }); 236} 237 238function z_dialog_alert(options) 239{ 240 var html, 241 backdrop; 242 243 if (typeof options.backdrop == 'undefined') { 244 backdrop = options.backdrop 245 } else { 246 backdrop = true; 247 } 248 html = '<div class="confirm">' + options.text + '</div>' 249 + '<div class="modal-footer">' 250 + '<button class="btn btn-primary z-dialog-ok-button">' 251 + (options.ok||z_translate('OK')) 252 + '</button>' 253 + '</div>'; 254 $.dialogAdd({ 255 title: (options.title||z_translate('Alert')), 256 text: html, 257 width: (options.width), 258 backdrop: backdrop 259 }); 260 $(".z-dialog-ok-button").click(function() { 261 z_dialog_close(); 262 if (options.on_confirm) options.on_confirm(); 263 }); 264} 265 266function z_dialog_overlay_open(options) 267{
268 var $overlay = $('.modal-overlay'); 269 if ($overlay.length > 0) { 270 $overlay 271 .html(options.html) 272 .attr('class', 'modal-overlay') 273 .show(); 274 } else { 275 html = '<div class="modal-overlay">' + 276 '<a href="#close" class="modal-overlay-close" onclick="return z_dialog_overlay_close()">×</a>' + 277 options.html + 278 '</div>'; 279 $('body').append(html); 280 } 281 if (options.class) { 282 $('.modal-overlay').addClass(options.class); 283 } 284} 285 286function z_dialog_overlay_close() 287{ 288 $('.modal-overlay').remove(); 289 return false; 290} 291 292/* Growl messages 293---------------------------------------------------------- */ 294 295function z_growl_add(message, stay, type) 296{ 297 stay = stay || false; 298 type = type || 'notice'; 299 300 $.noticeAdd( 301 { 302 text: message, 303 stay: stay, 304 type: type 305 }); 306 307 if(type == 'error' && window.console) 308 { 309 console.error(message); 310 } 311} 312 313function z_growl_close() 314{ 315 $.noticeRemove($('.notice-item-wrapper'), 400); 316} 317 318 319/* Registered events for javascript triggered actions/postbacks 320---------------------------------------------------------- */ 321 322function z_event_register(name, func) 323{ 324 z_registered_events[name] = func; 325} 326 327function z_event_remove(name) 328{ 329 delete z_registered_events[name]; 330} 331 332function z_event(name, extraParams) 333{ 334 if (z_registered_events[name]) 335 { 336 z_registered_events[name](ensure_name_value(extraParams)); 337 } 338 else if (window.console) 339 { 340 console.log("z_event: no registered event named: '"+name+"'"); 341 } 342} 343 344/* Call the server side notifier for {postback_notify, Message, Context} 345---------------------------------------------------------- */ 346 347function z_notify(message, extraParams) 348{ 349 var trigger_id = ''; 350 var params = extraParams || []; 351 352 if (typeof params == 'object' && params.z_trigger_id !== undefined) { 353 trigger_id = params.z_trigger_id; 354 delete params.z_trigger_id; 355 } 356 var notify = { 357 _record: "postback_notify", 358 message: message, 359 trigger: trigger_id, 360 target: params.z_target_id || undefined, 361 data: { 362 _record: 'q', 363 q: ensure_name_value(params) || [] 364 } 365 }; 366 var delegate = params.z_delegate || 'notify'; 367 var options = { 368 trigger_id: trigger_id 369 }; 370 if (trigger_id) { 371 options.ack = function(_ack_msg, _options) { 372 z_unmask(trigger_id); 373 }; 374 } 375 return z_transport(delegate, 'ubf', notify, options); 376} 377 378 379/* Session handling and restarts 380---------------------------------------------------------- */ 381 382 383function z_session_restart(invalid_page_id) 384{ 385 if (z_session_valid) { 386 z_session_valid = false; 387 z_session_restart_count = 0; 388 } 389 setTimeout(function() { z_session_restart_check(invalid_page_id); }, 50); 390} 391 392function z_session_restart_check(invalid_page_id) 393{
394 if (z_pageid == invalid_page_id) { 395 if (z_spinner_show_ct === 0) { 396 if (z_session_restart_count == 3 || !z_pageid) { 397 z_session_invalid_reload(z_pageid); 398 } else { 399 z_session_restart_count++; 400 z_transport('session', 'ubf', 'ensure', {is_expect_cookie: true}); 401 } 402 } else { 403 setTimeout(function() { z_session_restart_check(invalid_page_id); }, 200); 404 } 405 } 406} 407 408function z_session_status_ok(page_id, user_id) 409{ 410 if (page_id != z_pageid || user_id != z_userid) { 411 var status = { 412 status: "restart", 413 user_id: user_id, 414 page_id: page_id, 415 prev_user_id: z_userid, 416 prev_page_id: z_pageid 417 }; 418 419 z_pageid = page_id; 420 z_userid = user_id; 421 422 z_transport_queue = []; 423 z_transport_acks = []; 424 425 // checks pubzub registry for the local "session" topic 426 // if any handlers then publish the new user to the topic 427 // if no handlers then the default reload dialog is shown 428 if (typeof pubzub == "object" && pubzub.subscribers("~pagesession/session").length > 0) { 429 z_session_valid = true; 430 pubzub.publish("~pagesession/session", status); 431 z_stream_restart(); 432 } else { 433 z_session_invalid_reload(z_pageid, status); 434 } 435 } 436} 437 438function z_session_invalid_reload(page_id, status) 439{ 440 if (page_id == z_pageid) { 441 if (z_spinner_show_ct === 0 && !z_page_unloading) { 442 z_transport_delegates.reload(status); 443 } else { 444 setTimeout(function() { 445 z_session_invalid_reload(page_id, status); 446 }, 1000); 447 } 448 } 449} 450 451// Default action for delegates.reload 452function z_session_invalid_dialog() 453{ 454 var is_editing = false; 455 456 z_editor_save($('body')); 457 $('textarea').each(function() { 458 is_editing = is_editing || ($(this).val() !== ""); 459 }); 460 461 if (is_editing) { 462 z_dialog_confirm({ 463 title: z_translate("Reload"), 464 text: "<p>" + 465 z_translate("Your session has expired or is invalid. Reload the page to continue.") + 466 "</p>", 467 ok: z_translate("Reload"), 468 on_confirm: function() { z_reload(); } 469 }); 470 } else { 471 z_reload(); 472 } 473} 474 475/* Track activity, for stopping inactive sessions 476---------------------------------------------------------- */ 477 478function z_activity_init() 479{ 480 /* Use passive event capturing when it is supported */ 481 var passive_if_supported = false; 482 483 try { 484 window.addEventListener("test", null, 485 Object.defineProperty({}, "passive", { 486 get: function() { 487 passive_if_supported = { 488 passive: false 489 }; 490 } 491 })); 492 } catch(err) { 493 } 494 495 z_last_active = 0; 496 z_activity_event(); 497 498 document.addEventListener("visibilitychange", z_activity_event, passive_if_supported); 499 document.addEventListener("scroll", z_activity_event, passive_if_supported); 500 document.addEventListener("keydown", z_activity_event, passive_if_supported); 501 document.addEventListener("mousemove", z_activity_event, passive_if_supported); 502 document.addEventListener("click", z_activity_event, passive_if_supported); 503 document.addEventListener("focus", z_activity_event, passive_if_supported); 504} 505 506function z_activity_ignore() { 507 document.removeEventListener("visibilitychange", z_activity_event); 508 document.removeEventListener("scroll", z_activity_event); 509 document.removeEventListener("keydown", z_activity_event); 510 document.removeEventListener("mousemove", z_activity_event); 511 document.removeEventListener("click", z_activity_event); 512 document.removeEventListener("focus", z_activity_event); 513 514 z_last_active = null; 515} 516 517function z_activity_event() 518{ 519 if (!document.hidden) { 520 z_last_active = Date.now(); 521 } 522} 523 524function z_is_active(period) 525{ 526 period = period || ACTIVITY_PERIOD; 527 var now = Date.now(); 528 529 /* Return true when we are ignoring activity monitoring */ 530 if(z_last_active === null) return true; 531 532 533 return z_last_active > now - period; 534} 535 536 537/* Transport between user-agent and server 538---------------------------------------------------------- */ 539 540// Register a handler for incoming data (aka delegates) 541function z_transport_delegate_register(name, func) 542{ 543 z_transport_delegates[name] = func; 544} 545 546// Called for 'session' transport delegates, handles the session status 547function z_transport_session_status(data, msg) 548{ 549 switch (data) 550 {
551 case 'session_invalid': 552 if (window.z_sid) { 553 window.z_sid = undefined; 554 } 555 if (z_session_reload_check) { 556 z_reload(); 557 } else { 558 z_session_reload_check = false; 559 z_session_restart(z_pageid); 560 } 561 break; 562 case 'page_invalid': 563 if (z_session_reload_check) { 564 z_reload(); 565 } else { 566 z_session_reload_check = false; 567 z_session_restart(z_pageid); 568 } 569 break; 570 case 'ok': 571 z_session_valid = true; 572 if (z_session_reload_check) { 573 z_session_reload_check = false; 574 z_stream_restart(); 575 } 576 break; 577 default: 578 if (typeof data == 'object') { 579 switch (data._record) { 580 case 'session_state': 581 z_session_status_ok(data.page_id, data.user_id); 582 break; 583 case 'auth_change': 584 if (data.page_id == z_pageid) { 585 // The user-id of the session is changed. 586 // A new session cookie might still be on its way, so wait a bit 587 setTimeout(function() { 588 z_session_restart(z_pageid); 589 }, 1000); 590 } 591 break; 592 default: 593 console.log("Transport, unknown session status ", data); 594 break; 595 } 596 } else { 597 console.log("Transport, unknown session status ", data); 598 } 599 break; 600 } 601} 602 603 604// Queue any data to be transported to the server 605function z_transport(delegate, content_type, data, options) 606{ 607 var msg_id = z_unique_id(true); 608 609 if (!z_pageid) { 610 z_transport_wait(msg_id, delegate, content_type, data, options); 611 return msg_id; 612 } else { 613 return z_transport_do(msg_id, delegate, content_type, data, options); 614 } 615} 616 617function z_transport_wait(msg_id, delegate, content_type, data, options) 618{ 619 if (!z_pageid) { 620 setTimeout(function() { 621 z_transport_wait(msg_id, delegate, content_type, data, options); 622 }, 100); 623 } else { 624 return z_transport_do(msg_id, delegate, content_type, data, options); 625 } 626} 627 628 629function z_transport_do(msg_id, delegate, content_type, data, options) 630{ 631 var timestamp = new Date().getTime(); 632 633 options = options || {}; 634 options.transport = options.transport || ''; 635 636 if (typeof options.qos == 'undefined') { 637 if (options.ack) { 638 options.qos = 1; 639 } else { 640 options.qos = 0; 641 } 642 } 643 var msg = { 644 "_record": "z_msg_v1", 645 "qos": options.qos, 646 "dup": false, 647 "msg_id": msg_id, 648 "timestamp": timestamp, 649 "content_type": z_transport_content_type(content_type), 650 "delegate": z_transport_delegate(delegate), 651 "ua_class": ubf.constant(z_ua), 652 "page_id": z_pageid, 653 "session_id": window.z_sid || undefined, 654 "data": data 655 }; 656 657 options.timeout = options.timeout || TRANSPORT_TIMEOUT; 658 if (options.qos > 0 && options.transport !== 'form') { 659 var t = setTimeout(function() { 660 z_transport_timeout(msg_id); 661 }, options.timeout); 662 z_transport_acks[msg_id] = { 663 msg: msg, 664 msg_id: msg_id, 665 options: options, 666 timestamp: timestamp, 667 timeout_timer: t, 668 timeout_count: 0, 669 is_queued: true 670 }; 671 } 672 673 if (options.transport == 'form') { 674 z_transport_form({ 675 msg: msg, 676 msg_id: msg_id, 677 options: options 678 }); 679 } else { 680 z_transport_queue.push({ 681 msg: msg, 682 msg_id: msg_id, 683 options: options 684 }); 685 z_transport_check(); 686 } 687 return msg_id; 688} 689 690// Map some special content types to an atom 691function z_transport_content_type(content_type) 692{ 693 switch (content_type || 'ubf') 694 { 695 case 'ubf': return ubf.constant('ubf'); 696 case 'json': return ubf.constant('json'); 697 case 'form': return ubf.constant('form'); 698 case 'javascript': return ubf.constant('javascript'); 699 case 'text': return ubf.constant('text'); 700 default: return content_type; 701 } 702} 703 704// Map some special delegates to an atom 705function z_transport_delegate(delegate) 706{ 707 switch (delegate) 708 { 709 case 'mqtt': return ubf.constant('mqtt'); 710 case 'notify': return ubf.constant('notify'); 711 case 'postback': return ubf.constant('postback'); 712 case 'session': return ubf.constant('session'); 713 case '$ping': return ubf.constant('$ping'); 714 default: return delegate; 715 } 716} 717 718// Ensure that a transport is scheduled for fetching data queued at the server 719function z_transport_ensure() 720{ 721 if (z_transport_queue.length === 0 && !z_websocket_is_connected()) { 722 z_transport('$ping'); 723 } 724} 725 726function z_transport_incoming(data) 727{ 728 if (data !== undefined && data.length > 0) { 729 var msgs = ubf.decode(data); 730 731 if (typeof msgs == 'object' && msgs.ubf_type == ubf.LIST) { 732 for (var i=0; i<msgs.length; i++) { 733 z_transport_incoming_msg(msgs[i]); 734 } 735 } else { 736 z_transport_incoming_msg(msgs); 737 } 738 } 739} 740 741function z_transport_incoming_msg(msg) 742{ 743 switch (msg._record) 744 { 745 case 'z_msg_v1': 746 z_transport_maybe_ack(msg); 747 var data = z_transport_incoming_data_decode(msg.content_type.valueOf(), msg.data); 748 var fun = z_transport_delegates[msg.delegate.valueOf()]; 749 if (typeof fun == 'function') { 750 fun(data, msg); 751 } else { 752 console.log("No delegate registered for ",msg); 753 } 754 break; 755 case 'z_msg_ack': 756 if (!z_websocket_pong(msg) && typeof z_transport_acks[msg.msg_id] == 'object') { 757 var ack = z_transport_acks[msg.msg_id]; 758 delete z_transport_acks[msg.msg_id]; 759 760 if (ack.timeout_timer) { 761 clearTimeout(ack.timeout_timer); 762 } 763 if (typeof ack.options.ack == 'function') { 764 ack.options.ack(msg, ack.options); 765 } 766 } 767 break; 768 default:
769 console.log("Don't know where to delegate incoming message ", msg); 770 break; 771 } 772} 773 774function z_transport_delegate_javascript(data, _msg) 775{ 776 if (z_init_postback_forms_timeout) { 777 clearTimeout(z_init_postback_forms_timeout); 778 } 779 eval(data); 780 z_init_postback_forms_timeout = setTimeout(function() { 781 z_init_postback_forms_timeout = false; 782 z_init_postback_forms(); 783 }, 10); 784} 785 786function z_transport_maybe_ack(msg) 787{ 788 if (msg.qos >= 1) { 789 var ack = { 790 "_record": "z_msg_ack", 791 "qos": msg.qos, 792 "msg_id": msg.msg_id, 793 "push_queue": msg.push_queue, 794 "session_id": window.z_sid || undefined, 795 "page_id": msg.page_id || z_pageid 796 }; 797 z_transport_queue.push({ 798 msg: ack, 799 msg_id: msg.msg_id, 800 options: {} 801 }); 802 z_transport_check(); 803 } 804} 805 806// If a transport times-out whilst in transit then it is reposted 807function z_transport_timeout(msg_id) 808{ 809 z_log_error("Transport timeout for message: "+msg_id, "zotonic-1.0.js", 0, null); 810 811 if (typeof z_transport_acks[msg_id] == 'object') { 812 if (z_transport_acks[msg_id].timeout_count++ < TRANSPORT_TRIES) { 813 // Requeue the request (if it is not waiting in the queue) 814 if (!z_transport_acks[msg_id].is_queued && z_transport_retransmission_enabled) { 815 z_transport_acks[msg_id].msg.dup = true; 816 z_transport_queue.push({ 817 msg: z_transport_acks[msg_id].msg, 818 msg_id: msg_id, 819 options: z_transport_acks[msg_id].options || {} 820 }); 821 z_transport_acks[msg_id].is_queued = true; 822 } 823 z_transport_acks[msg_id].timeout_timer = setTimeout(function() { 824 z_transport_timeout(msg_id); 825 }, z_transport_acks[msg_id].options.timeout); 826 } else { 827 // Final timeout, remove from all queues 828 if (z_transport_acks[msg_id].fail) { 829 z_transport_acks[msg_id].fail(msg_id, z_transport_acks[msg_id].options); 830 } 831 if (z_transport_acks[msg_id].is_queued) { 832 for (var i=0; i<z_transport_queue.length; i++) { 833 if (z_transport_queue[i].msg_id == msg_id) { 834 z_transport_queue.splice(i,i); 835 break; 836 } 837 } 838 } 839 delete z_transport_acks[msg_id]; 840 } 841 } 842} 843 844function z_transport_incoming_data_decode(type, data) 845{ 846 switch (type) 847 { 848 case 'ubf': 849 // Decoded by decoding the z_msg_v1 record 850 return data; 851 case 'json': 852 return $.parseJSON(data.valueOf()); 853 case 'javascript': 854 return data.valueOf(); 855 case 'form': 856 return $.parseQuery(data.valueOf()); 857 case 'text': 858 return data.valueOf(); 859 default: 860 console.log("Unknown message data format: ", type, data); 861 return data; 862 } 863} 864 865 866// Queue form data to be transported to the server 867// This is called by the server generated javascript and jquery triggered postback events. 868// 'transport' is one of: '', 'ajax', 'form' 869function z_queue_postback(trigger_id, postback, extraParams, noTriggerValue, transport, optPostForm) 870{ 871 var triggervalue = ''; 872 var trigger; 873 874 if (transport === true) { 875 transport = 'ajax'; 876 } 877 if (trigger_id) { 878 trigger = $('#'+trigger_id).get(0); 879 } 880 if (trigger && !noTriggerValue) { 881 if ($(trigger).is(":checkbox") || $(trigger).is(":radio")) { 882 if ($(trigger).is(":checked")) { 883 triggervalue = $(trigger).val() || 'on'; 884 } 885 } else { 886 var nodeName = trigger.nodeName.toLowerCase(); 887 if (nodeName == 'input' || nodeName == 'button' || nodeName == 'textarea' || nodeName == 'select') { 888 triggervalue = $(trigger).val() || ''; 889 } 890 } 891 } 892 extraParams = extraParams || []; 893 // extraParams.push({name: 'triggervalue', value: triggervalue}); 894 895 var pb_event = { 896 _record: "postback_event", 897 postback: postback, 898 trigger: trigger_id, 899 target: extraParams.target_id || undefined, 900 triggervalue: triggervalue, 901 data: { 902 _record: 'q', 903 q: ensure_name_value(extraParams) || [] 904 } 905 }; 906 907 if (!transport) { 908 if ((trigger_id == "logon_form") || (trigger && $(trigger).hasClass("setcookie"))) { 909 transport = 'ajax'; 910 } 911 } 912 913 // logon_form and .setcookie forms are always posted, as they will set cookies. 914 var options = { 915 transport: transport, 916 trigger_id: trigger_id, 917 post_form: optPostForm 918 }; 919 if (trigger_id) { 920 options.ack = function(_ack_msg, _options) { 921 z_unmask(trigger_id); 922 }; 923 } 924 z_transport('postback', 'ubf', pb_event, options); 925} 926 927function z_postback_opt_qs(extraParams) 928{ 929 if (typeof extraParams == 'object' && extraParams instanceof Array) { 930 return { 931 _record: "q", 932 q: ensure_name_value(extraParams) 933 }; 934 } else { 935 return extraParams; 936 } 937} 938 939function z_transport_check() 940{ 941 if (z_transport_queue.length > 0) 942 { 943 // Delay transport messages till the z_pageid is initialized. 944 if (z_pageid !== '') { 945 var qmsg = z_transport_queue.shift(); 946 947 if (z_transport_acks[qmsg.msg_id]) { 948 z_transport_acks[qmsg.msg_id].is_queued = false;
949 } 950 if (!qmsg.page_id) { 951 qmsg.page_id = z_pageid; 952 } 953 z_do_transport(qmsg); 954 } else if (!z_transport_check_timer) { 955 z_transport_check_timer = setTimeout(function() { z_transport_check_timer = undefined; z_transport_check(); }, 50); 956 } 957 } 958} 959 960function z_do_transport(qmsg) 961{ 962 var data = ubf.encode(qmsg.msg); 963 if (qmsg.options.transport == 'ajax' || !z_websocket_is_connected() || !z_pageid) { 964 z_ajax(qmsg.options, data); 965 } else { 966 z_ws.send(data); 967 } 968} 969 970function z_ajax(options, data) 971{ 972 z_start_spinner(); 973 $.ajax({ 974 url: '/postback', 975 type: 'post', 976 data: data, 977 dataType: 'ubf text', 978 accepts: {ubf: 'text/x-ubf'}, 979 converters: {"text ubf": window.String}, 980 contentType: 'text/x-ubf', 981 async: true, 982 success: function(received_data, textStatus) 983 { 984 try 985 { 986 z_transport_incoming(received_data); 987 z_unmask(options.trigger_id); 988 } 989 catch(e) 990 { 991 console.log("Error evaluating ajax return value: ", received_data); 992 $.misc.error("Error evaluating ajax return value: " + received_data, e); 993 } 994 setTimeout(function() { z_stop_spinner(); z_transport_check(); }, 0); 995 }, 996 error: function(xmlHttpRequest, textStatus, errorThrown) 997 { 998 z_stop_spinner(); 999 z_unmask_error(options.trigger_id); 1000 if (!z_page_unloading) { 1001 if (textStatus == 'error') { 1002 $.misc.error("Error fetching data from server."); 1003 } else { 1004 $.misc.error("Error fetching data from server: " + textStatus); 1005 } 1006 } 1007 } 1008 }); 1009} 1010 1011function z_fetch_cookies() 1012{ 1013 $.ajax({ 1014 url: '/z_session/cookies', 1015 type: 'post', 1016 dataType: 'text' 1017 }); 1018} 1019 1020function z_unmask(id) 1021{ 1022 if (id) 1023 { 1024 var trigger; 1025 if (id.charAt(0) == ' ') { 1026 trigger = $(id); 1027 } else { 1028 trigger = $('#'+id); 1029 } 1030 trigger.each(function() { try { $(this).unmask(); } catch (e) {}}); 1031 trigger.each(function() { $(this).removeClass("z_error_upload"); }); 1032 } 1033} 1034 1035function z_unmask_error(id) 1036{ 1037 if (id) 1038 { 1039 var trigger; 1040 if (id.charAt(0) == ' ') { 1041 trigger = $(id); 1042 } else { 1043 trigger = $('#'+id); 1044 } 1045 z_unmask(id); 1046 trigger.each(function() { try { $(this).unmask(); } catch (e) {}}); 1047 trigger.each(function() { $(this).addClass("z_error_upload"); }); 1048 } 1049} 1050 1051 1052function z_progress(id, value) 1053{ 1054 if (id) 1055 { 1056 var trigger = $('#'+id).get(0); 1057 1058 if (trigger.nodeName.toLowerCase() == 'form') 1059 { 1060 try { $(trigger).maskProgress(value); } catch (e) {} 1061 } 1062 } 1063} 1064 1065function z_reload(args) 1066{ 1067 var page = $('#logon_form input[name="page"]'); 1068 z_start_spinner(); 1069 if (page.length > 0 && page.val() !== "" && page.val() !== '#reload') { 1070 window.location.href = window.location.protocol+"//"+window.location.host+page.val(); 1071 } else { 1072 if (typeof args == "undefined") 1073 window.location.reload(true); 1074 else { 1075 var qs = ensure_name_value(args); 1076 var href; 1077 1078 if (qs.length == 1 && typeof args.z_language == "string") { 1079 if ( window.location.pathname.substring(0,2+z_language.length) == "/"+z_language+"/") { 1080 href = window.location.protocol+"//"+window.location.host 1081 +"/"+args.z_language+"/" 1082 +window.location.pathname.substring(2+z_language.length); 1083 } else { 1084 href = window.location.protocol+"//"+window.location.host 1085 +"/"+args.z_language 1086 +window.location.pathname; 1087 } 1088 window.location.href = href + window.location.search; 1089 } else { 1090 href = window.location.protocol+"//"+window.location.host+window.location.pathname; 1091 if (window.location.search == "") { 1092 window.location.href = href + '?' + $.param(qs); 1093 } else { 1094 var loc_qs = $.parseQuery(); 1095 for (var prop in loc_qs) { 1096 if (typeof loc_qs[prop] != "undefined" && typeof args[prop] == "undefined") 1097 qs.push({name: prop, value: loc_qs[prop]}); 1098 } 1099 window.location.href = href + "?" + $.param(qs); 1100 } 1101 } 1102 } 1103 } 1104} 1105 1106/* translations 1107---------------------------------------------------------- */ 1108 1109function z_translate(text) 1110{ 1111 if (typeof z_translations != "undefined" && typeof z_translations[text] != "undefined") 1112 return z_translations[text]; 1113 return text; 1114} 1115 1116function z_translation_set(text, trans) 1117{ 1118 if (typeof z_translations == "undefined") { 1119 z_translations = {}; 1120 } 1121 z_translations[text] = trans; 1122} 1123 1124 1125/* Render text as html nodes 1126---------------------------------------------------------- */ 1127 1128function z_text_to_nodes(text) 1129{ 1130 var text1 = $.trim(text); 1131 1132 if (text1 === "") { 1133 return $(""); 1134 } else { 1135 var $ns; 1136 if (text1.charAt(0) == "<" && text1.charAt(text1.length-1) == ">") { 1137 $ns = $(text); 1138 } else { 1139 $ns = $("<span></span>"+text+"<span></span>").slice(1,-1); 1140 } 1141 return $ns.filter(function(i) { return $ns[i].nodeType != 3 || $ns[i].nodeValue.trim() !== ""; }); 1142 } 1143} 1144 1145/* WYSYWIG editor 1146---------------------------------------------------------- */ 1147 1148function z_editor_init() 1149{ 1150 if (z_editor !== undefined) { 1151 z_editor.init(); 1152 } 1153} 1154 1155function z_editor_add(element) 1156{ 1157 if (z_editor !== undefined) { 1158 var $element = (typeof element == "string") ? $(element) : element; 1159 z_editor.add($element); 1160 } 1161} 1162 1163function z_editor_save(element) 1164{ 1165 if (z_editor !== undefined) { 1166 var $element = (typeof element == "string") ? $(element) : element; 1167 z_editor.save($element); 1168 } 1169} 1170 1171function z_editor_remove(element) 1172{ 1173 if (z_editor !== undefined) { 1174 var $element = (typeof element == "string") ? $(element) : element; 1175 z_editor.remove($element); 1176 } 1177} 1178 1179/* Support legacy code */ 1180 1181function z_tinymce_init() 1182{ 1183 z_editor_init(); 1184} 1185 1186function z_tinymce_add($element) 1187{ 1188 z_editor_add($element); 1189} 1190 1191function z_tinymce_save($element) 1192{ 1193 z_editor_save($element); 1194} 1195 1196function z_tinymce_remove($element) 1197{ 1198 z_editor_remove($element); 1199} 1200 1201/* Comet long poll or WebSockets connection 1202---------------------------------------------------------- */ 1203 1204function z_stream_start(_host, websocket_host) 1205{ 1206 if (!z_session_valid) { 1207 setTimeout(function() { 1208 z_stream_start(_host, websocket_host); 1209 }, 100); 1210 } else { 1211 z_websocket_host = websocket_host || window.location.host; 1212 z_stream_restart(); 1213 } 1214} 1215 1216function z_stream_onreload() 1217{ 1218 z_websocket_stop(); 1219 1220 if (z_comet) { 1221 try { z_comet.abort(); } catch(e) { } 1222 z_comet = undefined; 1223 }
1224 z_session_reload_check = true; 1225 z_page_unloading = false; 1226 z_transport('session', 'ubf', 'check', { transport: 'ajax' }); 1227} 1228 1229function z_stream_restart() 1230{ 1231 if (z_websocket_host) { 1232 z_timeout_comet_poll_ajax(1000); 1233 if ("WebSocket" in window) { 1234 setTimeout(function() { z_websocket_start(); }, 200); 1235 } 1236 } 1237} 1238 1239function z_stream_is_connected() 1240{ 1241 return z_websocket_is_connected() || z_comet_is_connected(); 1242} 1243 1244function z_comet_poll_ajax() 1245{ 1246 // Do not start a new poll when there is already a poll running. 1247 if (z_comet) return; 1248 1249 if (z_ws_pong_count === 0 && z_session_valid && !z_page_unloading) 1250 { 1251 z_comet_poll_count++; 1252 var msg = ubf.encode({ 1253 "_record": "z_msg_v1", 1254 "qos": 0, 1255 "dup" : false, 1256 "msg_id": '$comet-'+z_pageid+'-'+z_comet_poll_count, 1257 "timestamp": new Date().getTime(), 1258 "content_type": ubf.constant("ubf"), 1259 "delegate": ubf.constant('$comet'), 1260 "ua_class": ubf.constant(z_ua), 1261 "page_id": z_pageid, 1262 "session_id": window.z_sid || undefined, 1263 "data": { 1264 count: z_comet_poll_count, 1265 is_active: z_is_active() 1266 } 1267 }); 1268 z_comet = $.ajax({ 1269 url: window.location.protocol + '//' + window.location.host + '/comet', 1270 type:'post', 1271 data: msg, 1272 dataType: 'ubf text', 1273 accepts: {ubf: "text/x-ubf"}, 1274 converters: {"text ubf": window.String}, 1275 contentType: 'text/x-ubf', 1276 statusCode: { 1277 /* Handle incoming data */ 1278 200: function(data, _textStatus) { 1279 z_transport_handle_push_data(data); 1280 z_timeout_comet_poll_ajax(100); 1281 }, 1282 204: function() { 1283 z_timeout_comet_poll_ajax(1000); 1284 } 1285 }, 1286 error: function(xmlHttpRequest, textStatus, errorThrown) { 1287 setTimeout(function() { z_comet_poll_ajax(); }, z_comet_reconnect_timeout); 1288 if(z_comet_reconnect_timeout < 60000) 1289 z_comet_reconnect_timeout = z_comet_reconnect_timeout * 2; 1290 } 1291 }).done(function() { 1292 z_comet = undefined; 1293 }); 1294 } 1295 else 1296 { 1297 z_timeout_comet_poll_ajax(5000); 1298 } 1299} 1300 1301 1302function z_comet_is_connected() 1303{ 1304 return z_comet && z_comet.readyState != 0; 1305} 1306 1307function z_timeout_comet_poll_ajax(timeout) 1308{ 1309 if (z_comet_poll_timeout) { 1310 clearTimeout(z_comet_poll_timeout); 1311 } 1312 z_comet_poll_timeout = setTimeout(function() { 1313 z_comet_poll_timeout = false; 1314 z_comet_reconnect_timeout = 1000; 1315 z_comet_poll_ajax(); 1316 }, timeout); 1317} 1318 1319 1320function z_transport_handle_push_data(data) 1321{ 1322 try 1323 { 1324 z_transport_incoming(data); 1325 } 1326 catch (e) 1327 { 1328 console.log("Error evaluating push return value: ", data); 1329 $.misc.error("Error evaluating push return value: " + data, e); 1330 } 1331} 1332 1333 1334function z_websocket_start() 1335{ 1336 // Do not start a new websocket when there is already a websocket. 1337 if(z_ws) return; 1338 1339 var protocol = "ws:"; 1340 if (window.location.protocol == "https:") { 1341 protocol = "wss:"; 1342 } 1343 1344 try { 1345 z_ws = new WebSocket(protocol+"//"+z_websocket_host+"/websocket"); 1346 } catch (e) { 1347 z_ws_pong_count = 0; 1348 } 1349 1350 z_ws.onopen = z_websocket_ping; 1351 z_ws.onerror = z_websocket_restart; 1352 z_ws.onclose = z_websocket_restart; 1353 1354 z_ws.onmessage = function (evt) { 1355 z_transport_handle_push_data(evt.data); 1356 setTimeout(function() { z_transport_check(); }, 0); 1357 }; 1358} 1359 1360function z_websocket_stop() 1361{ 1362 if (!z_ws) return; 1363 1364 z_ws.onclose = undefined; 1365 z_ws.onerror = undefined; 1366 z_ws.onmessage = undefined; 1367 1368 try { 1369 z_ws.close(); 1370 } catch(e) { 1371 // closing an already closed ws can raise exceptions. 1372 } 1373 z_ws = undefined; 1374} 1375 1376function z_websocket_ping() 1377{ 1378 z_clear_ws_ping_timeout(); 1379 z_ws_ping_timeout = setTimeout(z_websocket_restart, 5000); 1380 1381 if (z_ws && z_ws.readyState == 1) { 1382 var msg = ubf.encode({ 1383 "_record": "z_msg_v1", 1384 "qos": 1, 1385 "dup" : false, 1386 "msg_id": '$ws-'+z_pageid, 1387 "timestamp": new Date().getTime(), 1388 "content_type": ubf.constant("ubf"), 1389 "delegate": ubf.constant('$ping'), 1390 "ua_class": ubf.constant(z_ua), 1391 "page_id": z_pageid, 1392 "session_id": window.z_sid || undefined, 1393 "data": { 1394 count: z_ws_pong_count, 1395 is_active: z_is_active(WEBSOCKET_PING_INTERVAL) 1396 } 1397 }); 1398 z_ws.send(msg); 1399 } 1400} 1401 1402function z_clear_ws_ping_timeout() 1403{ 1404 if (z_ws_ping_timeout) { 1405 clearTimeout(z_ws_ping_timeout); 1406 z_ws_ping_timeout = undefined; 1407 } 1408} 1409 1410function z_clear_ws_ping_interval() 1411{ 1412 if (z_ws_ping_interval) { 1413 clearTimeout(z_ws_ping_interval); 1414 z_ws_ping_interval = undefined; 1415 } 1416} 1417 1418function z_websocket_pong( msg ) 1419{ 1420 if (msg.msg_id == '$ws-'+z_pageid) { 1421 z_clear_ws_ping_timeout(); 1422 1423 z_clear_ws_ping_interval(); 1424 z_ws_ping_interval = setTimeout(z_websocket_ping, WEBSOCKET_PING_INTERVAL); 1425 1426 z_ws_pong_count++; 1427 1428 return true; 1429 } 1430 1431 return false;
1432} 1433 1434function z_websocket_is_connected() 1435{ 1436 return z_ws && z_ws.readyState == 1 && z_ws_pong_count > 0; 1437} 1438 1439function z_websocket_restart(e) 1440{ 1441 z_clear_ws_ping_timeout(); 1442 z_clear_ws_ping_interval(); 1443 1444 z_websocket_stop(); 1445 1446 if (z_ws_pong_count > 0 && z_session_valid && !z_page_unloading) { 1447 z_ws_pong_count = 0; 1448 z_websocket_start(); 1449 } 1450} 1451 1452 1453/* Utility functions 1454---------------------------------------------------------- */ 1455 1456// Should an event be canceled or passed through. 1457function z_opt_cancel(obj) 1458{ 1459 if(typeof obj.nodeName == 'undefined') 1460 return false; 1461 1462 var nodeName = obj.nodeName.toLowerCase(); 1463 var nodeType = $(obj).attr("type"); 1464 1465 if (nodeName == 'input' && (nodeType == 'checkbox' || nodeType == 'radio')) 1466 { 1467 return true; 1468 } 1469 else 1470 { 1471 return false; 1472 } 1473} 1474 1475function z_is_enter_key(event) 1476{ 1477 return (event && event.keyCode == 13); 1478} 1479 1480 1481function z_has_flash() 1482{ 1483 if (navigator.plugins && navigator.plugins.length>0) { 1484 var type = 'application/x-shockwave-flash'; 1485 var mimeTypes = navigator.mimeTypes; 1486 return (mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin); 1487 } else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript) { 1488 try { 1489 obj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 1490 return true; 1491 } catch(err) { 1492 return false; 1493 } 1494 } 1495 return false; 1496} 1497 1498 1499function z_ensure_id(elt) 1500{ 1501 var id = $(elt).attr('id'); 1502 if (id === undefined || id === "") { 1503 id = z_unique_id(); 1504 $(elt).attr('id', id); 1505 } 1506 return id; 1507} 1508 1509function z_unique_id(no_dom_check) 1510{ 1511 var id; 1512 do { 1513 id = '-z-' + z_unique_id_counter++; 1514 } while (!no_dom_check && $('#'+id).length > 0); 1515 return id; 1516} 1517 1518 1519/* Spinner, show when waiting for a postback 1520---------------------------------------------------------- */ 1521 1522function z_start_spinner() 1523{ 1524 if (z_spinner_show_ct++ === 0) 1525 { 1526 $(document.body).addClass('wait'); 1527 $('#spinner').fadeIn(100); 1528 } 1529} 1530 1531function z_stop_spinner() 1532{ 1533 if (--z_spinner_show_ct === 0) 1534 { 1535 $('#spinner').fadeOut(100); 1536 $(document.body).removeClass('wait'); 1537 } 1538 else if (z_spinner_show_ct < 0) { 1539 z_spinner_show_ct = 0; 1540 } 1541} 1542 1543 1544/* Drag & drop interface to the postback 1545---------------------------------------------------------- */ 1546 1547function z_draggable(dragObj, dragOptions, dragTag) 1548{ 1549 dragObj.draggable(dragOptions).data("z_drag_tag", dragTag); 1550 z_drag_tag[dragObj.attr('id')] = dragTag; 1551} 1552 1553function z_droppable(dropObj, dropOptions, dropPostbackInfo) 1554{ 1555 dropOptions.greedy = true; 1556 dropOptions.drop = function(ev, ui) 1557 { 1558 var dragTag = $(ui.draggable[0]).data("z_drag_tag"); 1559 var dragItem = new Array({name: 'drag_item', value: dragTag}); 1560 z_queue_postback(this.id, dropPostbackInfo, dragItem, true); 1561 }; 1562 1563 $(dropObj).droppable(dropOptions); 1564} 1565 1566 1567/* Sorter and sortables interface to the postback 1568---------------------------------------------------------- */ 1569 1570function z_sortable(sortableObj, sortTag) 1571{ 1572 sortableObj.data("z_sort_tag", sortTag); 1573} 1574 1575function z_sorter(sortBlock, sortOptions, sortPostbackInfo) 1576{ 1577 sortOptions.update = function() 1578 { 1579 var sortItems = ""; 1580 1581 for (var i = 0; i < this.childNodes.length; i++) 1582 { 1583 var sortTag = $(this.childNodes[i]).data("z_sort_tag"); 1584 if (sortTag) 1585 { 1586 if (sortItems !== "") 1587 { 1588 sortItems += ","; 1589 } 1590 sortItems += sortTag; 1591 } 1592 } 1593 1594 var sortItem = new Array({name: 'sort_items', value: sortItems}); 1595 1596 z_queue_postback(this.id, sortPostbackInfo, sortItem, true); 1597 }; 1598 sortOptions.receive = function (ev, ui) { 1599 var $target = $(this).data().uiSortable.element; 1600 var $source = $(ui.sender); 1601 $target.data('z_sort_tag', $source.data('z_drag_tag')); 1602 }; 1603 sortOptions.helper = 'clone'; 1604 $(sortBlock).sortable(sortOptions); 1605} 1606 1607 1608/* typeselect input field 1609---------------------------------------------------------- */ 1610 1611function z_typeselect(ElementId, postbackInfo) 1612{ 1613 if (z_input_updater) 1614 { 1615 clearTimeout(z_input_updater); 1616 z_input_updater = false;
1617 } 1618 1619 z_input_updater = setTimeout(function() 1620 { 1621 var obj = $('#'+ElementId); 1622 1623 if(obj.val().length >= 2) 1624 { 1625 obj.addClass('loading'); 1626 z_queue_postback(ElementId, postbackInfo); 1627 } 1628 }, 400); 1629} 1630 1631 1632/* Lazy loading of content, based on visibility of an element 1633---------------------------------------------------------- */ 1634 1635function z_on_visible(CssSelector, Func) 1636{ 1637 z_on_visible_checks.push({selector: CssSelector, func: Func}); 1638 if (z_on_visible_timer == undefined) { 1639 z_on_visible_timer = setInterval(function() { 1640 z_on_visible_check(); 1641 }, 350); 1642 } 1643} 1644 1645function z_on_visible_check() 1646{ 1647 for (var i = 0; i < z_on_visible_checks.length; i++) { 1648 var elt = $(z_on_visible_checks[i].selector).get(0); 1649 if (elt != undefined) { 1650 if ($(elt).is(":visible") && isScrolledIntoView(elt)) { 1651 z_on_visible_checks[i].func.call(elt); 1652 z_on_visible_checks.splice(i, 1); 1653 } 1654 } 1655 } 1656 if (z_on_visible_checks.length == 0) { 1657 clearInterval(z_on_visible_timer); 1658 z_on_visible_timer = undefined; 1659 } 1660} 1661 1662 1663function isScrolledIntoView(elem) 1664{ 1665 var docViewTop = $(window).scrollTop(); 1666 var docViewBottom = docViewTop + $(window).height(); 1667 1668 var elemTop = $(elem).offset().top; 1669 var elemBottom = elemTop + $(elem).height(); 1670 1671 return (elemBottom >= docViewTop) && (elemTop <= docViewBottom); 1672 // && (elemBottom <= docViewBottom) && (elemTop >= docViewTop); 1673} 1674 1675 1676/* Error handling 1677---------------------------------------------------------- 1678 1679Fetch the error event and log it to the server. 1680Which should log it in a separate ui error log. 1681 1682---------------------------------------------------------- */ 1683 1684var oldOnError = window.onerror; 1685 1686window.onerror = function(message, file, line, col, error) { 1687 z_log_error(message, file, line, col, error); 1688 if (oldOnError) { 1689 return oldOnError(message, file, line, col, error); 1690 } else { 1691 return false; 1692 } 1693}; 1694 1695function z_log_error ( message, file, line, col, error ) { 1696 if (!z_page_unloading) { 1697 // Some code (plugin?) on Safari assumes that a JSON-LD context is always a string. 1698 // As it can also be an object that code will throw an error, ignore this error here. 1699 if (message.indexOf('r["@context"].toLowerCase') == -1) { 1700 let payload = { 1701 type: 'error', 1702 message: message, 1703 file: file, 1704 line: line, 1705 col: col, 1706 stack: error ? error.stack : null, 1707 user_agent: navigator.userAgent, 1708 url: window.location.href 1709 }; 1710 1711 let xhr = new XMLHttpRequest(); 1712 xhr.open('POST', '/log-client-event', true); 1713 xhr.send(JSON.stringify(payload)); 1714 1715 if ($("form.masked").length > 0 || (payload.stack && payload.stack.match(/(submitFunction|doValidations)/))) { 1716 alert("Sorry, something went wrong.\n\n(" + message + ")"); 1717 try { $("form.masked").unmask(); } catch (e) {} 1718 } 1719 } 1720 } 1721} 1722 1723 1724/* Form element validations 1725---------------------------------------------------------- 1726 1727Grab all "postback" forms, let them be handled by Ajax postback calls. 1728This function can be run multiple times. 1729 1730---------------------------------------------------------- */ 1731 1732function z_init_postback_forms() 1733{ 1734 $("form[action='postback']").each(function() { 1735 // store options in hash 1736 $(this).on('click.form-plugin', ":submit,input:image", function(e) { 1737 var form = this.form; 1738 form.clk = this; 1739 1740 if (this.type == 'image') 1741 { 1742 if (e.offsetX !== undefined) 1743 { 1744 form.clk_x = e.offsetX; 1745 form.clk_y = e.offsetY; 1746 } 1747 else if (typeof $.fn.offset == 'function') 1748 { // try to use dimensions plugin 1749 var offset = $(this).offset(); 1750 form.clk_x = e.pageX - offset.left; 1751 form.clk_y = e.pageY - offset.top; 1752 } 1753 else 1754 { 1755 form.clk_x = e.pageX - this.offsetLeft; 1756 form.clk_y = e.pageY - this.offsetTop; 1757 } 1758 } 1759 }); 1760 }) 1761 .submit(function(event) { 1762 var theForm = this; 1763 1764 event.preventDefault(); 1765 1766 z_editor_save(theForm); 1767 1768 submitFunction = function(ev) { 1769 try { $(theForm).mask("", 100); } catch (e) {} 1770 1771 var postback = $(theForm).data("z_submit_postback"); 1772 var action = $(theForm).data("z_submit_action"); 1773 var form_id = $(theForm).attr('id');
1774 var validations = $(theForm).formValidationPostback(); 1775 var transport = ''; 1776 var files = $('input:file', theForm).fieldValue(); 1777 var is_file_form = false; 1778 var args; 1779 1780 if (!postback) { 1781 postback = z_default_form_postback; 1782 } 1783 if (action) { 1784 setTimeout(action, 10); 1785 } 1786 1787 for (var j=0; j < files.length && !is_file_form; j++) { 1788 if (files[j]) { 1789 is_file_form = true; 1790 break; 1791 } 1792 } 1793 if (is_file_form) { 1794 transport = 'form'; 1795 args = validations; 1796 } else { 1797 if ($(theForm).hasClass("z_cookie_form") || 1798 $(theForm).hasClass("z_logon_form") || 1799 (typeof(z_only_post_forms) != "undefined" && z_only_post_forms)) { 1800 transport = 'ajax'; 1801 } 1802 args = validations.concat($(theForm).formToArray()); 1803 } 1804 1805 // add submitting element to data if we know it 1806 var sub = theForm.clk; 1807 if (sub) { 1808 var n = sub.name; 1809 if (n && !sub.disabled) { 1810 args.push({name: n, value: $(sub).val()}); 1811 args.push({name: 'z_submitter', value: n}); 1812 if (sub.type == "image") { 1813 args.push({name: name+'.x', value: theForm.clk_x}); 1814 args.push({name: name+'.y', value: theForm.clk_y}); 1815 } 1816 } 1817 } 1818 1819 // Queue the postback, or use a post to an iframe (if files present) 1820 z_queue_postback(form_id, postback, args, false, transport, theForm); 1821 1822 theForm.clk = null; 1823 theForm.clk_x = null; 1824 theForm.clk_y = null; 1825 ev.stopPropagation(); 1826 return false; 1827 }; 1828 1829 return z_form_submit_validated_delay(theForm, event, submitFunction); 1830 }) 1831 .attr('action', '#pb-installed'); 1832} 1833 1834function z_form_submit_validated_delay(theForm, event, submitFunction) 1835{ 1836 var validations = $(theForm).formValidationPostback(); 1837 1838 if (validations.length > 0 && !event.zIsValidated) 1839 { 1840 // There are form validations and they are not done yet. 1841 if (!event.zAfterValidation) 1842 { 1843 event.zAfterValidation = []; 1844 } 1845 event.zAfterValidation.push({ func: submitFunction, context: theForm }); 1846 return true; 1847 } 1848 else 1849 { 1850 // No form validations, or already validated 1851 return submitFunction.call(theForm, event); 1852 } 1853} 1854 1855function z_form_submit_validated_do(event) 1856{ 1857 var ret = true; 1858 1859 if (event.zAfterValidation) 1860 { 1861 $.each(event.zAfterValidation, function(){ 1862 ret = typeof this.func == 'function' && this.func.call(this.context, event) && ret; 1863 }); 1864 event.zAfterValidation.length = 0; 1865 } 1866 return ret; 1867} 1868 1869 1870function z_transport_form(qmsg) 1871{ 1872 var options = { 1873 url: '/postback', 1874 type: 'POST', 1875 dataType: 'text' 1876 }; 1877 var $form = $(qmsg.options.post_form); 1878 var form = $form[0]; 1879 1880 if ($(':input[name=submit]', form).length) { 1881 alert('Error: Form elements must not be named "submit".'); 1882 return; 1883 } 1884 1885 var opts = $.extend({}, $.ajaxSettings, options); 1886 var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); 1887 1888 var id = 'jqFormIO' + (new Date().getTime()); 1889 var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />'); 1890 var io = $io[0]; 1891 1892 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); 1893 1894 var xhr = { // mock object 1895 aborted: 0, 1896 responseText: null, 1897 responseXML: null, 1898 status: 0, 1899 statusText: 'n/a', 1900 getAllResponseHeaders: function() {}, 1901 getResponseHeader: function() {}, 1902 setRequestHeader: function() {}, 1903 abort: function() { 1904 this.aborted = 1; 1905 $io.attr('src','about:blank'); // abort op in progress 1906 } 1907 }; 1908 1909 var g = opts.global; 1910
1911 // trigger ajax global events so that activity/block indicators work like normal 1912 if (g && ! $.active++) $.event.trigger("ajaxStart"); 1913 if (g) $.event.trigger("ajaxSend", [xhr, opts]); 1914 1915 if (s.beforeSend && s.beforeSend(xhr, s) === false) { 1916 s.global && $.active--; 1917 return; 1918 } 1919 if (xhr.aborted) 1920 return; 1921 1922 var cbInvoked = 0; 1923 var timedOut = 0; 1924 1925 // take a breath so that pending repaints get some cpu time before the upload starts 1926 setTimeout(function() { 1927 // make sure form attrs are set 1928 var t = $form.attr('target'); 1929 var a = $form.attr('action'); 1930 1931 // update form attrs in IE friendly way 1932 form.setAttribute('target',id); 1933 if (form.getAttribute('method') != 'POST') 1934 form.setAttribute('method', 'POST'); 1935 if (form.getAttribute('action') != opts.url) 1936 form.setAttribute('action', opts.url); 1937 1938 // ie borks in some cases when setting encoding 1939 if (! options.skipEncodingOverride) { 1940 $form.attr({ 1941 encoding: 'multipart/form-data', 1942 enctype: 'multipart/form-data' 1943 }); 1944 } 1945 1946 // support timout 1947 if (opts.timeout) { 1948 setTimeout(function() { timedOut = true; cb(); }, opts.timeout); 1949 } 1950 1951 let extraInputs = []; 1952 1953 extraInputs.push( 1954 $('<input />') 1955 .attr('type', 'hidden') 1956 .attr('name', 'z_msg') 1957 .attr('value', ubf.encode(qmsg.msg)) 1958 .prependTo(form)[0]); 1959 1960 // Prepend all unchecked checkboxes as empty hidden input values 1961 $(form).find('input[type="checkbox"]:not(:checked):not(.nosubmit)').each( 1962 function() { 1963 const name = $(this).attr('name'); 1964 if (name) { 1965 extraInputs.push( 1966 $('<input />') 1967 .attr('type', 'hidden') 1968 .attr('name', name) 1969 .attr('value', '') 1970 .prependTo(form)[0]); 1971 } 1972 }); 1973 1974 try { 1975 // add iframe to doc and submit the form 1976 $io.appendTo('body'); 1977 if (io.attachEvent) { 1978 io.attachEvent('onload', cb); 1979 } else { 1980 io.addEventListener('load', cb, false); 1981 } 1982 form.submit(); 1983 } 1984 finally { 1985 // reset attrs and remove "extra" input elements 1986 form.setAttribute('action',a); 1987 if (t) { 1988 form.setAttribute('target', t); 1989 } else { 1990 $form.removeAttr('target'); 1991 } 1992 for (let n in extraInputs) { 1993 $(n).remove(); 1994 } 1995 } 1996 }, 10); 1997 1998 function cb() { 1999 if (io.detachEvent) { 2000 io.detachEvent('onload', cb); 2001 } else { 2002 io.removeEventListener('load', cb, false); 2003 } 2004 if (timedOut) { 2005 $.event.trigger("ajaxError", [xhr, opts, e]); 2006 z_unmask_error(form.id); 2007 } else { 2008 $.event.trigger("ajaxSuccess", [xhr, opts]); 2009 z_unmask(form.id); 2010 } 2011 if (g) { 2012 $.event.trigger("ajaxComplete", [xhr, opts]); 2013 $.event.trigger("ajaxStop"); 2014 } 2015 if (opts.complete) { 2016 opts.complete(xhr, ok ? 'success' : 'error'); 2017 } 2018 z_transport_ensure(); 2019 } 2020} 2021 2022 2023// Collect all postback validations from the form elements 2024$.fn.formValidationPostback = function() 2025{ 2026 var a = []; 2027 if(this.length > 0) { 2028 var form = this[0]; 2029 var els = form.elements; 2030 2031 if (!els) return a; 2032 2033 for(var i=0, max=els.length; i < max; i++) 2034 { 2035 var el = els[i]; 2036 var n = el.name; 2037 2038 if (n && !el.disabled && !$(el).hasClass("nosubmit")) 2039 { 2040 var v = $(el).data("z_postback_validation"); 2041 if (v) 2042 { 2043 a.push({name: "z_v", value: n+":"+v}); 2044 } 2045 } 2046 } 2047 } 2048 return a; 2049}; 2050 2051// Initialize a validator for the element #id 2052function z_init_validator(id, args) 2053{ 2054 var elt = $('#'+id); 2055 if (elt) 2056 { 2057 if (elt.attr('type') == 'radio') 2058 { 2059 $('input[name="'+elt.attr('name')+'"]').each(function() { 2060 addLiveValidation(this, args); 2061 }); 2062 } 2063 else 2064 { 2065 addLiveValidation(elt, args); 2066 } 2067 } 2068 else 2069 { 2070 $.misc.error('Validator error: no element with id #'+id, $(id)); 2071 } 2072} 2073 2074// Add a validator to the input field 2075function z_add_validator(id, type, args) 2076{ 2077 var elt = $('#'+id); 2078 2079 if (elt.attr('type') == 'radio') 2080 elt = $('input[name="'+elt.attr('name')+'"]'); 2081 2082 elt.each(function() { 2083 var v = getLiveValidation(this); 2084 if (v) 2085 { 2086 if (args['pattern']) 2087 { 2088 args['pattern'] = new RegExp(args['pattern']); 2089 } 2090 switch (type) 2091 { 2092 case 'email': v.add(Validate.Email, args); break; 2093 case 'date': v.add(Validate.Date, args
2093); break; 2094 case 'presence': v.add(Validate.Presence, args); break; 2095 case 'confirmation': v.add(Validate.Confirmation, args); break; 2096 case 'acceptance': v.add(Validate.Acceptance, args); break; 2097 case 'length': v.add(Validate.Length, args); break; 2098 case 'format': v.add(Validate.Format, args); break; 2099 case 'numericality': v.add(Validate.Numericality, args); break; 2100 case 'custom': v.add(Validate.Custom, args); break; 2101 case 'postback': 2102 args['z_id'] = id; 2103 v.add(Validate.Postback, args); 2104 break; 2105 default: 2106 $.misc.error("unknown validation: "+type); 2107 break; 2108 } 2109 } 2110 }); 2111} 2112 2113function z_set_validator_postback(id, postback) 2114{ 2115 if (postback) 2116 { 2117 var pb = $('#'+id).data("z_postback_validation"); 2118 if (pb) 2119 { 2120 $.misc.error("Element #"+id+" had already a validation postback, add all validations as one batch.", $('#' +id)); 2121 } 2122 2123 $('#'+id).data("z_postback_validation", postback); 2124 } 2125} 2126 2127function z_validation_on_invalid(id, on_invalid) 2128{ 2129 $('#'+id).each(function() { 2130 if (this.tagName.toLowerCase() == 'form') 2131 { 2132 var formObj = LiveValidationForm.getInstance(this); 2133 formObj.onInvalid = on_invalid; 2134 } 2135 }); 2136} 2137 2138 2139function z_async_validation_result(id, isValid, testedValue) 2140{ 2141 var v = getLiveValidation($('#'+id)); 2142 if (v && $('#'+id).val() == testedValue) 2143 { 2144 v.asyncValidationResult(isValid, testedValue); 2145 } 2146} 2147 2148// Called by the server on validation errors 2149function z_validation_error(id, error) 2150{ 2151 var v = getLiveValidation($('#'+id)); 2152 if (v) 2153 { 2154 if (error == 'invalid') 2155 { 2156 // Generic error - handle it ourselves 2157 error = "please correct"; 2158 } 2159 v.showErrorMessage(error); 2160 } 2161} 2162 2163 2164// Execute a function by name 2165function z_call_function_by_name(name, context) 2166{ 2167 var args = Array.prototype.slice.call(arguments).splice(2); 2168 var namespaces = name.split("."); 2169 var func = namespaces.pop(); 2170 for(var i = 0; i < namespaces.length; i++) { 2171 context = context[namespaces[i]]; 2172 } 2173 return context[func].apply(this, args); 2174} 2175 2176// URL encode function that is more RFC compatible. Also encodes +, *, / and @. 2177function urlencode(s) 2178{ 2179 s = escape(s); 2180 s = s.replace(/\+/g, '%2B'); 2181 s = s.replace(/\*/g, '%2A'); 2182 s = s.replace(/\//g, '%2F'); 2183 s = s.replace(/@/g, '%40'); 2184 return s; 2185} 2186 2187// HTML escape a string so it is safe to concatenate when making tags. 2188function html_escape(s) 2189{ 2190 return s.replace(/&/g, "&") 2191 .replace(/</g, "<") 2192 .replace(/>/g, ">") 2193 .replace(/"/g, """) 2194 .replace(/'/g, "'"); 2195} 2196 2197// HTML unescape a string. 2198function html_unescape(s) 2199{ 2200 return s.replace(/</g, "<") 2201 .replace(/>/g, ">") 2202 .replace(/"/g, "\"") 2203 .replace(/'/g, "'") 2204 .replace(/&/g, "&"); 2205} 2206 2207 2208// Convert an object to an array with {name: xxx, value: yyy} pairs 2209function ensure_name_value(a) 2210{ 2211 if ((typeof a == 'object') && !(a instanceof Array)) 2212 { 2213 var n = []; 2214 for (var prop in a) 2215 { 2216 if (a[prop] !== undefined) 2217 n.push({name: prop, value: a[prop]}); 2218 } 2219 return n; 2220 } 2221 else 2222 { 2223 return a; 2224 } 2225} 2226 2227// Update the contents of an iframe 2228function z_update_iframe(name, doc) 2229{ 2230 var iframe = window.frames[name]; 2231 if (iframe) { 2232 var iframe_doc = iframe.document || iframe.contentDocument || iframe.contentWindow.document; 2233 iframe_doc.open(); 2234 iframe_doc.write(doc); 2235 iframe_doc.close(); 2236 } 2237} 2238 2239 2240// Store the current cookie consent status 2241function z_cookie_consent_store( status ) 2242{ 2243 if (status !== 'all') { 2244 z_cookie_remove_all(); 2245 } 2246 switch (status) { 2247 case "functional": 2248 case "stats": 2249 case "all": 2250 const prev = z_cookie_consent_cache; 2251 window.z_cookie_consent_cache = status; 2252 try { 2253 // Use stringify to be compatible with model.localStorage 2254 localStorage.setItem('z_cookie_consent', JSON.stringify(status)); 2255 } catch (e) { 2256 } 2257 const ev = new CustomEvent("zotonic:cookie-consent", { 2258 detail: { 2259 cookie_consent: status 2260 } 2261 }); 2262 if (prev != status) { 2263 window.dispatchEvent(ev); 2264 } 2265 break; 2266 default: 2267 console.error("Cookie consent status must be one of 'all', 'stats' or 'functional'", status); 2268 break; 2269 } 2270} 2271 2272// Trigger on consent changes in other windows/tabs 2273window.addEventListener("storage", function(ev) { 2274 if (ev.key == 'z_cookie_consent') { 2275 if (ev.newValue === null) { 2276 window.z_cookie_consent_cache = 'functional'; 2277 } else if (ev.oldValue != ev.newValue) { 2278 z_cookie_consent_store(ev.newValue); 2279 } 2280 } 2281}, false); 2282 2283 2284// Fetch the current cookie consent status - default to 'functional' 2285function z_cookie_consent_fetch() 2286{ 2287 if (window.z_cookie_consent_cache) { 2288 return window.z_cookie_consent_cache; 2289 } else { 2290 let status; 2291 2292 try { 2293 status = localStorage.getItem('z_cookie_consent'); 2294 } catch (e) { 2295 status = null; 2296 } 2297 2298 if (status !== null) { 2299 status = JSON.parse(status); 2300 } else { 2301 status = 'functional'; 2302 } 2303 window.z_cookie_consent_cache = status 2304 return status; 2305 } 2306} 2307 2308// Check is the user consented to some cookies 2309function z_cookie_consent_given() 2310{ 2311 try { 2312 return typeof (localStorage.getItem('z_cookie_consent')) === 'string'; 2313 } catch (e) { 2314 return false;
2315 } 2316} 2317 2318 2319// Check if something is allowed according to the stored consent status 2320function z_cookie_consented( wanted ) 2321{ 2322 const consent = z_cookie_consent_fetch(); 2323 2324 switch (wanted) { 2325 case 'functional': 2326 return true; 2327 case 'stats': 2328 return consent === 'all' || consent === 'stats'; 2329 case 'all': 2330 return consent === 'all'; 2331 default: 2332 return false; 2333 } 2334} 2335 2336// Remove all non-functional cookies from the current document domain 2337function z_cookie_remove_all() 2338{ 2339 for ( const cookie of document.cookie.split(';') ){ 2340 const cookieName = cookie.split('=')[0].trim(); 2341 2342 switch (cookieName) { 2343 case "z_sid": 2344 case "z_rldid": 2345 case "z_ua": 2346 case "z.sid": 2347 case "z.lang": 2348 case "z.auth": 2349 case "z.autologon": 2350 // Functional - keep the cookie 2351 break; 2352 default: 2353 // Non-functional - remove the cookie 2354 let domains = window.location.hostname.split('.'); 2355 while ( domains.length > 0 ) { 2356 const domain = domains.join('.'); 2357 const cookieReset = encodeURIComponent(cookieName) + '=; expires=Thu, 01-Jan-1970 00:00:01 GMT'; 2358 2359 document.cookie = cookieReset; 2360 document.cookie = cookieReset + '; domain=' + domain + ' ;path=/'; 2361 2362 let pathSegments = location.pathname.split('/'); 2363 while ( pathSegments.length > 0 ){ 2364 const path = pathSegments.join('/'); 2365 document.cookie = cookieReset + '; domain=' + domain + ' ;path=' + path; 2366 pathSegments.pop(); 2367 } 2368 domains.shift(); 2369 } 2370 break; 2371 } 2372 } 2373} 2374 2375// From: http://malsup.com/jquery/form/jquery.form.js 2376 2377/* 2378 * jQuery Form Plugin 2379 * version: 2.28 (10-MAY-2009) 2380 * @requires jQuery v1.2.2 or later 2381 * 2382 * Examples and documentation at: http://malsup.com/jquery/form/ 2383 * Dual licensed under the MIT and GPL licenses: 2384 * http://www.opensource.org/licenses/mit-license.php 2385 * http://www.gnu.org/licenses/gpl.html 2386 */ 2387 2388/** 2389 * formToArray() gathers form element data into an array of objects that can 2390 * be passed to any of the following ajax functions: $.get, $.post, or load. 2391 * Each object in the array has both a 'name' and 'value' property. An example of 2392 * an array for a simple login form might be: 2393 * 2394 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] 2395 * 2396 * It is this array that is passed to pre-submit callback functions provided to the 2397 * ajaxSubmit() and ajaxForm() methods. 2398 */ 2399$.fn.formToArray = function(options) { 2400 var a = []; 2401 options = options || {}; 2402 if (this.length > 0) { 2403 var form = this[0]; 2404 var els = options.semantic ? form.getElementsByTagName('*') : form.elements; 2405 var n; 2406 2407 if (els) { 2408 for(var i=0, max=els.length; i < max; i++) { 2409 var el = els[i]; 2410 n = el.name; 2411 if (n && (!$(el).hasClass("nosubmit") || options.all)) { 2412 switch ($(el).attr("type")) { 2413 case "submit": 2414 break; 2415 case "file": 2416 break; 2417 default: 2418 var v = $.fieldValue(el, true); 2419 if (v && v.constructor == Array) { 2420 for(var j=0, jmax=v.length; j < jmax; j++) 2421 a.push({name: n, value: v[j]}); 2422 } 2423 else if (v !== null && typeof v != 'undefined') { 2424 a.push({name: n, value: v}); 2425 } 2426 } 2427 } 2428 } 2429 } 2430 } 2431 return a; 2432}; 2433 2434 2435/** 2436 * Returns the value(s) of the element in the matched set. For example, consider the following form: 2437 * 2438 * <form><fieldset>
2439 * <input name="A" type="text" /> 2440 * <input name="A" type="text" /> 2441 * <input name="B" type="checkbox" value="B1" /> 2442 * <input name="B" type="checkbox" value="B2"/> 2443 * <input name="C" type="radio" value="C1" /> 2444 * <input name="C" type="radio" value="C2" /> 2445 * </fieldset></form> 2446 * 2447 * var v = $(':text').fieldValue(); 2448 * // if no values are entered into the text inputs 2449 * v == ['',''] 2450 * // if values entered into the text inputs are 'foo' and 'bar' 2451 * v == ['foo','bar'] 2452 * 2453 * var v = $(':checkbox').fieldValue(); 2454 * // if neither checkbox is checked 2455 * v === undefined 2456 * // if both checkboxes are checked 2457 * v == ['B1', 'B2'] 2458 * 2459 * var v = $(':radio').fieldValue(); 2460 * // if neither radio is checked 2461 * v === undefined 2462 * // if first radio is checked 2463 * v == ['C1'] 2464 * 2465 * The successful argument controls whether or not the field element must be 'successful' 2466 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls). 2467 * The default value of the successful argument is true. If this value is false the value(s) 2468 * for each element is returned. 2469 * 2470 * Note: This method *always* returns an array. If no valid value can be determined the 2471 * array will be empty, otherwise it will contain one or more values. 2472 */ 2473$.fn.fieldValue = function(successful) { 2474 for (var val=[], i=0, max=this.length; i < max; i++) { 2475 var el = this[i]; 2476 var v = $.fieldValue(el, successful); 2477 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) 2478 continue; 2479 v.constructor == Array ? $.merge(val, v) : val.push(v); 2480 } 2481 return val; 2482}; 2483 2484/** 2485 * Returns the value of the field element. 2486 */ 2487$.fieldValue = function(el, successful) { 2488 var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); 2489 if (typeof successful == 'undefined') successful = true; 2490 2491 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || 2492 t == 'radio' && !el.checked || 2493 (t == 'submit' || t == 'image') && el.form && el.form.clk != el || 2494 tag == 'select' && el.selectedIndex == -1)) 2495 return null; 2496 2497 // Return empty value for non-checked checkboxes 2498 if (successful && t == 'checkbox' && !el.checked) 2499 return ''; 2500 2501 if (tag == 'select') { 2502 var index = el.selectedIndex; 2503 if (index < 0) return null; 2504 var a = [], ops = el.options; 2505 var one = (t == 'select-one'); 2506 var max = (one ? index+1 : ops.length); 2507 for(var i=(one ? index : 0); i < max; i++) { 2508 var op = ops[i]; 2509 if (op.selected) { 2510 var v = op.value; 2511 if (!v) // extra pain for IE... 2512 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; 2513 if (one) return v; 2514 a.push(v); 2515 } 2516 } 2517 return a; 2518 } 2519 return el.value; 2520}; 2521 2522 2523/** 2524 * Clears the form data. Takes the following actions on the form's input fields: 2525 * - input text fields will have their 'value' property set to the empty string 2526 * - select elements will have their 'selectedIndex' property set to -1 2527 * - checkbox and radio inputs will have their 'checked' property set to false 2528 * - inputs of type submit, button, reset, and hidden will *not* be effected 2529 * - button elements will *not* be effected 2530 */ 2531$.fn.clearForm = function() { 2532 return this.each(function() { 2533 $('input,select,textarea', this).clearFields(); 2534 }); 2535}; 2536 2537/** 2538 * Clears the selected form elements. 2539 */ 2540$.fn.clearFields = $.fn.clearInputs = function() { 2541 return this.each(function() { 2542 var t = this.type, tag = this.tagName.toLowerCase(); 2543 if (t == 'text' || t == 'password' || tag == 'textarea') 2544 this.value = ''; 2545 else if (t == 'checkbox' || t == 'radio') 2546 this.checked = false; 2547 else if (tag == 'select') 2548 this.selectedIndex = -1; 2549 }); 2550}; 2551 2552/** 2553 * Resets the form data. Causes all form elements to be reset to their original value. 2554 */ 2555$.fn.resetForm = function() { 2556 return this.each(function() { 2557 // guard against an input with the name of 'reset' 2558 // note that IE reports the reset function as an 'object' 2559 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) 2560 this.reset(); 2561 }); 2562}; 2563 2564/** 2565 * Enables or disables any matching elements. 2566 */ 2567$.fn.enable = function(b) { 2568 if (b === undefined) b = true; 2569 return this.each(function() { 2570 this.disabled = !b; 2571 }); 2572}; 2573 2574/** 2575 * Checks/unchecks any matching checkboxes or radio buttons and 2576 * selects/deselects and matching option elements. 2577 */ 2578$.fn.selected = function(select) { 2579 if (select === undefined) select = true; 2580 return this.each(function() { 2581 var t = this.type; 2582 if (t == 'checkbox' || t == 'radio') 2583 this.checked = select; 2584 else if (this.tagName.toLowerCase() == 'option') { 2585 var $sel = $(this).parent('select'); 2586 if (select && $sel[0] && $sel[0].type == 'select-one') { 2587 // deselect all other options 2588 $sel.find('option').selected(false); 2589 } 2590 this.selected = select; 2591 } 2592 }); 2593}; 2594 2595// helper fn for console logging 2596function log() { 2597 if (window.console && window.console.log) 2598 window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,'')); 2599} 2600 2601 2602 2603function is_equal(x, y) { 2604 if ( x === y ) return true; 2605 if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
2606 if ( x.constructor !== y.constructor ) return false; 2607 for ( var p in x ) { 2608 if ( ! x.hasOwnProperty( p ) ) continue; 2609 if ( ! y.hasOwnProperty( p ) ) return false; 2610 if ( x[ p ] === y[ p ] ) continue; 2611 if ( typeof( x[ p ] ) !== "object" ) return false; 2612 if ( ! is_equal( x[ p ], y[ p ] ) ) return false; 2613 } 2614 for ( p in y ) { 2615 if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false; 2616 } 2617 return true; 2618} 2619 2620$.extend({ 2621 keys: function(obj){ 2622 if (typeof Object.keys == 'function') 2623 return Object.keys(obj); 2624 var a = []; 2625 $.each(obj, function(k){ a.push(k) }); 2626 return a; 2627 } 2628}); 2629 2630 2631/** 2632 * A simple querystring parser. 2633 * Example usage: var q = $.parseQuery(); q.fooreturns "bar" if query contains "?foo=bar"; multiple values are added to an array. 2634 * Values are unescaped by default and plus signs replaced with spaces, or an alternate processing function can be passed in the params object . 2635 * http://actingthemaggot.com/jquery 2636 * 2637 * Copyright (c) 2008 Michael Manning (http://actingthemaggot.com) 2638 * Dual licensed under the MIT (MIT-LICENSE.txt) 2639 * and GPL (GPL-LICENSE.txt) licenses. 2640 **/ 2641$.parseQuery = function(qs,options) { 2642 var q = (typeof qs === 'string'?qs:window.location.search), o = {'f':function(v){return unescape(v).replace(/\+/g,' ');}}, options = (typeof qs === 'object' && typeof options === 'undefined')?qs:options, o = jQuery.extend({}, o, options), params = {}; 2643 jQuery.each(q.match(/^\??(.*)$/)[1].split('&'),function(i,p){ 2644 p = p.split('='); 2645 p[1] = o.f(p[1]); 2646 params[p[0]] = params[p[0]]?((params[p[0]] instanceof Array)?(params[p[0]].push(p[1]),params[p[0]]):[params[p[0]],p[1]]):p[1]; 2647 }); 2648 return params; 2649}; 2650 2651/** 2652 * Patch jQuery.find to return [] on queries for '#' 2653 * This fixes issue https://github.com/zotonic/zotonic/issues/1934 2654 */ 2655(function( jQuery, window, undefined ) { 2656 var oldFind = jQuery.find; 2657 2658 jQuery.find = function( selector ) { 2659 if (typeof selector == "string" && selector == '#') { 2660 if (window.console) { 2661 window.console.log("Zotonic jQuery patch: returning [] for illegal selector '#'"); 2662 } 2663 return $([]); 2664 } else { 2665 var args = Array.prototype.slice.call( arguments ); 2666 return oldFind.apply( this, args ); 2667 } 2668 }; 2669 2670 // Copy properties attached to original jQuery.find method (e.g. .attr, .isXML) 2671 var findProp; 2672 for ( findProp in oldFind ) { 2673 if ( Object.prototype.hasOwnProperty.call( oldFind, findProp ) ) { 2674 jQuery.find[ findProp ] = oldFind[ findProp ]; 2675 } 2676 } 2677})( jQuery, window ); 2678/* Admin widgetManager class 2679---------------------------------------------------------- 2680 2681@package: Zotonic 2682@Author: Tim Benniks <[email protected]> 2683@Author: Marc Worrell <[email protected]> 2684 2685Copyright 2009-2011 Tim Benniks 2686 2687Licensed under the Apache License, Version 2.0 (the "License"); 2688you may not use this file except in compliance with the License. 2689You may obtain a copy of the License at 2690 2691http://www.apache.org/licenses/LICENSE-2.0 2692 2693Unless required by applicable law or agreed to in writing, software 2694distributed under the License is distributed on an "AS IS" BASIS, 2695WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2696See the License for the specific language governing permissions and 2697limitations under the License. 2698 2699---------------------------------------------------------- */ 2700 2701;(function($) 2702{ 2703 $.extend( 2704 { 2705 widgetManager: function(context) 2706 { 2707 let stack = [context || document.body]; 2708 let nodes = []; 2709 2710 // 1. Collect nodes 2711 while (stack.length > 0) 2712 { 2713 var defaults; 2714 var element = stack.pop(); 2715 2716 if (typeof element.className == "string") 2717 { 2718 var objectClass = element.className.match(/do_[a-zA-Z0-9_]+/g); 2719 if (objectClass) 2720 { 2721 var n = objectClass.length; 2722 for (var i=0; i<n; i++) 2723 { 2724 var functionName = objectClass[i].substring(3); 2725 var defaultsName = functionName; 2726 2727 if ('dialog' == functionName) 2728 { 2729 functionName = 'show_dialog'; // work around to prevent ui.dialog redefinition 2730 } 2731 2732 if (typeof $(element)[functionName] == "function") 2733 { 2734 if ($.ui && $.ui[functionName] && $.ui[functionName].defaults) 2735 { 2736 defaults = $.ui[functionName].defaults; 2737 } 2738 else 2739 { 2740 defaults = {} 2741 } 2742 nodes.push({ 2743 element: element, 2744 functionName: functionName, 2745 defaults: defaults, 2746 defaultsName: defaultsName 2747 }); 2748 } 2749 } 2750 } 2751 } 2752 2753 if (element.childNodes) 2754 { 2755 for (var i = 0; i< element.childNodes.length; i++) 2756 { 2757 if (element.childNodes[i].nodeType != 3) 2758 { 2759 stack.unshift(element.childNodes[i]); 2760 } 2761 } 2762 } 2763 } 2764 2765 while (nodes.length > 0) 2766 { 2767 let n = nodes.pop(); 2768 $(n.element)[n.functionName]( $.extend({}, n.defaults, $(n.element).metadata(n.defaultsName)) ); 2769 } 2770 }, 2771 2772 misc: 2773 { 2774 log: function(obj) 2775 { 2776 var text = obj.toString(); 2777 if(window.console) 2778 { 2779 console.log(text); 2780 2781 if($.noticeAdd) 2782 { 2783 $.noticeAdd({ 2784 text: 'Logging, check firebug: '+text, 2785 type: 'notice', 2786 stay: 0 2787 }); 2788 } 2789 } 2790 else 2791 { 2792 if($.noticeAdd) 2793 { 2794 $.noticeAdd({ 2795 text: 'logged: '+text, 2796 type: 'notice', 2797 stay: 0 2798 }); 2799 } 2800 else 2801 { 2802 alert(text); 2803 } 2804 } 2805 }, 2806 2807 warn: function(text, obj) 2808 { 2809 obj = obj || ''; 2810 2811 if(window.console) 2812 { 2813 console.warn(text, obj.toString()); 2814 } 2815 2816 if($.noticeAdd) 2817 { 2818 $.noticeAdd({ 2819 text: text, 2820 type: 'notice', 2821 stay: 1 2822 }); 2823 } 2824 }, 2825 2826 error: function(text, obj) 2827 { 2828 obj = obj || ''; 2829 2830 if(window.console) 2831 { 2832 console.error(text, obj.toString()); 2833 if (obj.stack) 2834 console.error(obj.stack); 2835 } 2836 2837 if($.noticeAdd) 2838 { 2839 $.noticeAdd({ 2840 text: text, 2841 type: 'error', 2842 stay: 1 2843 }); 2844 } 2845 } 2846 } 2847 }); 2848 2849 $.fn.metadata = function(functionName) 2850 { 2851 var elem = this[0]; 2852 var data_name = 'widget-'+functionName; 2853 var data = $(elem).data(data_name); 2854 if(typeof data === "undefined") 2855 { 2856 data = elem.getAttribute("data-"+functionName); 2857 if (data) 2858 { 2859 if (data.substr(0,1) == "{") 2860 { 2861 try { 2862 data = JSON.parse(data); 2863 } catch (e) { 2864 console.error("Error parsing JSON in widget data attribute:", data); 2865 data = {}; 2866 } 2867 } 2868 else 2869 { 2870 try { 2871 data = eval("({" + data.replace(/[\n\r]/g,' ') + "})"); 2872 } catch (e) { 2873 console.error("Error evaluating widget data attribute:", data); 2874 data = {}; 2875 } 2876 } 2877 } 2878 else 2879 { 2880 data = {}; 2881 } 2882 $(elem).data(data_name, data); 2883 } 2884 return data; 2885 }; 2886 2887 $.fn.widgetManager = function() 2888 { 2889 this.each(function() { $.widgetManager(this); }); 2890 return this; 2891 }; 2892 2893})(jQuery); 2894/* UBF(A) encoder/decoder 2895 2896@package: Channel.me 2013 2897@Author: MM Zeeman <[email protected]> 2898 2899Copyright 2013-2014 Maas-Maarten Zeeman 2900 2901Licensed under the Apache License, Version 2.0 (the "License"); 2902you may not use this file except in compliance with the License. 2903You may obtain a copy of the License at 2904 2905http://www.apache.org/licenses/LICENSE-2.0 2906 2907Unless required by applicable law or agreed to in writing, softw
2907are 2908distributed under the License is distributed on an "AS IS" BASIS, 2909WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2910See the License for the specific language governing permissions and 2911limitations under the License. 2912 2913*/ 2914 2915;(function(window) { 2916 var window = window; 2917 var ubf = {}; 2918 var specs = {}; 2919 2920 ubf.BINARY = 0; 2921 ubf.CONSTANT = 1; 2922 ubf.STRING = 2; 2923 ubf.TUPLE = 3; 2924 ubf.LIST = 4; 2925 ubf.OPCODE = 5; 2926 2927 DecodeStack = function(start) { 2928 this._stack = start || []; 2929 this._markers = []; 2930 }; 2931 DecodeStack.prototype.length = function() { 2932 return this._stack.length; 2933 }; 2934 DecodeStack.prototype.push = function(v) { 2935 this._stack.push(v); 2936 }; 2937 DecodeStack.prototype.pop = function() { 2938 return this._stack.pop(); 2939 }; 2940 DecodeStack.prototype.push_offset = function() { 2941 this._markers.push(this._stack.length); 2942 }; 2943 DecodeStack.prototype.pop_offset_diff = function() { 2944 return this._stack.length - this._markers.pop(); 2945 }; 2946 2947 2948 // Make a constant 2949 function constant(value) { 2950 var s = new String(value); 2951 s.ubf_type = ubf.CONSTANT; 2952 return s; 2953 } 2954 ubf.constant = constant; 2955 2956 // Encode the value as a ubf(a) tuple. 2957 function encode_as_tuple(value, spec, buffer) { 2958 if (value._record) { 2959 encode_as_record(value, value._record, spec || specs[value._record], buf); 2960 } else { 2961 var buf = buffer || []; 2962 var inner = []; 2963 var i; 2964 buf.push('{'); 2965 if (spec) { 2966 for (i = 0; i<spec.length; i++) { 2967 encode(value[spec[i]], inner); 2968 } 2969 } else { 2970 var ks = Object.keys(value); 2971 for (i = 0; i<ks.length; i++) { 2972 encode(value[ks[i]], inner); 2973 } 2974 } 2975 buf.push(inner.join(",")); 2976 buf.push('}'); 2977 2978 if(!buffer) { 2979 buf.push("$"); 2980 return buf.join(""); 2981 } 2982 } 2983 } 2984 ubf.encode_as_tuple = encode_as_tuple; 2985 2986 // Encode the value as list 2987 function encode_as_list(value, buffer) { 2988 var buf = buffer || []; 2989 var i; 2990 2991 buf.push("#"); 2992 for(i=value.length-1; i >= 0; i--) { 2993 encode(value[i], buf); 2994 buf.push('&'); 2995 } 2996 2997 if(!buffer) { 2998 buf.push("$"); 2999 return buf.join(""); 3000 } 3001 } 3002 ubf.encode_as_list = encode_as_list; 3003 3004 // Encode as proplist with {key,value} tuples. 3005 function encode_as_proplist(value, buffer) { 3006 var buf = buffer || []; 3007 var ks = Object.keys(value); 3008 3009 buf.push("#"); 3010 for (var i = 0; i<ks.length; i++) { 3011 var k = ks[i]; 3012 buf.push('{'); 3013 encode(k, buf); 3014 buf.push(' '); 3015 encode(value[k], buf); 3016 buf.push('}&'); 3017 } 3018 if(!buffer) { 3019 buf.push("$"); 3020 return buf.join(""); 3021 } 3022 } 3023 ubf.encode_as_proplist = encode_as_proplist; 3024 3025 // Encode as record 3026 function encode_as_record(value, record_name, spec, buffer) { 3027 var buf = buffer || []; 3028 var inner = []; 3029 var i; 3030 spec = spec || specs[record_name]; 3031 3032 buf.push('{'); 3033 if(spec) { 3034 encode_as_constant(record_name, inner); 3035 for (i = 0; i<spec.length; i++) { 3036 encode(value[spec[i]], inner); 3037 } 3038 } else { 3039 ks = Object.keys(value); 3040 for (i = 0; i<ks.length; i++) { 3041 encode(value[ks[i]], inner); 3042 } 3043 } 3044 buf.push(inner.join(",")); 3045 buf.push('}'); 3046 3047 if(!buffer) { 3048 buf.push("$"); 3049 return buf.join(""); 3050 } 3051 } 3052 ubf.encode_as_record = encode_as_record; 3053 3054 function string_escape(s) { 3055 if(s === undefined) return ""; 3056 return s.replace(/\\/g, "\\\\").replace(/\"/g, '\\"'); 3057 } 3058 3059 function constant_escape(s) { 3060 if(s === undefined) return ""; 3061 return s.replace(/\\/g, "\\\\").replace(/\'/g, "\\'"); 3062 } 3063 3064 function encode_as_string(value, buffer) { 3065 buffer.push(['"', string_escape(value), '"'].join("")); 3066 } 3067 ubf.encode_as_string = encode_as_string; 3068 3069 function encode_as_binary(value, buffer) { 3070 buffer.push(_utf8len(value)+"~"+value+"~"); 3071 } 3072 ubf.encode_as_binary = encode_as_binary; 3073 3074 function encode_as_constant(value, buffer) { 3075 buffer.push(["'", constant_escape(value), "'"].join("")); 3076 } 3077 ubf.encode_as_constant = encode_as_constant; 3078 3079 // ubf(a) encode javascript to ubf. 3080 // 3081 function encode(value, buffer) { 3082 var buf = buffer || []; 3083 3084 if(value === undefined || value === null) { 3085 encode_as_constant("undefined", buf); 3086 } else { 3087 switch(value.ubf_type) { 3088 case ubf.STRING: 3089 encode_as_string(value, buf); 3090 break; 3091 case ubf.CONSTANT: 3092 encode_as_constant(value, buf); 3093 break; 3094 case ubf.BINARY: 3095 encode_as_binary(value, buf); 3096 break; 3097 case ubf.LIST: 3098 encode_as_list(value, buf); 3099 break; 3100 case ubf.TUPLE: 3101 encode_as_tuple(value, undefined, buf); 3102 break; 3103 case ubf.OPCODE: 3104 buf.push(value); 3105 break; 3106 default: 3107 if(typeof value == "object" && value instanceof Array) { 3108 encode_as_list(value, buf); 3109 } else if(typeof(value) == "number") { 3110 if (Math.floor(value) == value) { 3111 buf.push(Math.floor(value)); 3112 } else { 3113 buf.push('"'+value+'"`f`'); 3114 } 3115 } else if(typeof(value) == "string") { 3116 // Per default encode strings as binary - better on the server 3117 encode_as_binary(value, buf); 3118 } else if (typeof(value) == "object" && value instanceof Date) { 3119 buf.push(""+Math.round(value.getTime() / 1000)+"`dt`"); 3120 } else if (typeof(value) == "object" && value._record) {
3121 encode_as_record(value, value._record, specs[value._record], buf); 3122 } else if(typeof(value) == "object") { 3123 var keys = Object.keys(value); 3124 if (keys.length == 2 && ('name' in value) && ('value' in value)) { 3125 encode_as_tuple([value.name, value.value], undefined, buf); 3126 } else { 3127 encode_as_proplist(value, buf); 3128 } 3129 } else if(typeof(value) == "object" && value.valueOf) { 3130 encode_as_binary(value.valueOf(), buf); 3131 } else if(typeof(value) == "boolean") { 3132 encode_as_constant((value)?"true":"false", buf); 3133 } else if(value === null) { 3134 encode_as_constant("undefined", buf); 3135 } else { 3136 throw("ubf encode: unknown value"); 3137 } 3138 } 3139 } 3140 3141 if(!buffer) { 3142 buf.push("$"); 3143 return buf.join(""); 3144 } 3145 } 3146 ubf.encode = encode; 3147 3148 // ubf(a) decoder 3149 // 3150 // 3151 ubf.decode = function(bytes, ready_fun, env, startStack) { 3152 var j, opcode; 3153 3154 stack = new DecodeStack(startStack); 3155 env = env || {}; 3156 3157 try { 3158 while(true) { 3159 opcode = bytes.charAt(0); 3160 j = _operation(opcode, bytes, env, stack); 3161 if(j === 0) { 3162 if(ready_fun) { 3163 data = stack.pop(); 3164 ready_fun(data); 3165 bytes = bytes.slice(1); 3166 if(bytes.length === 0) 3167 return data; 3168 } else { 3169 return stack.pop(); 3170 } 3171 } 3172 bytes = bytes.slice(j); 3173 } 3174 } 3175 catch(err) { 3176 console.log(err, stack, bytes); 3177 throw err; 3178 } 3179 }; 3180 3181 /* A spec is a list of field names */ 3182 ubf.add_spec = function(name, spec) { 3183 specs[name] = spec; 3184 }; 3185 3186 function _read(bytes, terminator, type, stack) { 3187 var current, buf = [], i = 0; 3188 3189 while(true) { 3190 current = bytes.charAt(i); 3191 3192 if(current === undefined) 3193 throw "Missing terminator"; 3194 3195 if(current == "\\") { 3196 switch(bytes.charAt(i+1)) { 3197 case "\\": 3198 buf.push("\\"); 3199 break; 3200 case terminator: 3201 buf.push(terminator); 3202 break; 3203 default: 3204 throw "Wrong " + type + " escape sequence"; 3205 } 3206 i += 2; 3207 continue; 3208 } 3209 3210 if(current == terminator) { 3211 if(stack) { 3212 var obj; 3213 var buf_s = buf.join(""); 3214 if (type == ubf.STRING) { 3215 obj = buf_s; 3216 } else { 3217 if (type == ubf.CONSTANT) { 3218 switch (buf_s) 3219 { 3220 case 'false': obj = false; break; 3221 case 'true': obj = true; break; 3222 case 'undefined': break; 3223 default: 3224 obj = buf_s; 3225 break; 3226 } 3227 } else { 3228 obj = buf_s; 3229 } 3230 } 3231 stack.push(obj); 3232 } 3233 return i + 1; 3234 } 3235 3236 buf.push(current); 3237 i += 1; 3238 } 3239 } 3240 3241 function skip_ws(bytes) { 3242 if(!bytes) return 0; 3243 var ws = bytes.match(/^(\s|,)+/); 3244 if(ws) 3245 return ws[0].length; 3246 return 0; 3247 } 3248 3249 function _integer_or_binary_data(bytes, stack) { 3250 var found = bytes.match(/^\-?[0-9]+/)[0], 3251 integer = Number(found), 3252 length = found.length, 3253 rest = bytes.slice(length), 3254 ws_length = skip_ws(rest); 3255 3256 if(rest.charAt(ws_length) != "~") { 3257 stack.push(integer); 3258 return length; 3259 } 3260 // assume input was utf-8 data, correct for decoded JS utf-16 chars 3261 var charct = _binarychars(rest, ws_length+1, integer); 3262 var binary = rest.slice(ws_length + 1, ws_length + charct + 1); 3263 stack.push(binary); 3264 3265 if(rest.charAt(ws_length+1+charct) != "~") 3266 throw "UBF decode: missing closing ~"; 3267 3268 return length + ws_length + charct + 2; 3269 } 3270 3271 function _string(bytes, stack) { 3272 return _read(bytes.slice(1), '"', ubf.STRING, stack) + 1; 3273 } 3274 3275 function _constant(bytes, stack) { 3276 return _read(bytes.slice(1), "'", ubf.CONSTANT, stack) + 1; 3277 } 3278 3279 function _start_tuple(stack) { 3280 stack.push_offset(); // marker for building a tuple 3281 return 1; 3282 } 3283 3284 function _end_tuple(stack) { 3285 // pop items from the stack until we find NaN 3286 var tuple = []; 3287 tuple.ubf_type = ubf.TUPLE; 3288 3289 var obj; 3290 var ct = stack.pop_offset_diff(); 3291 3292 if (ct < 0) { 3293 console.log("UBF decode error - empty stack for tuple", stack); 3294 throw "UBF decode: Empty stack on tuple"; 3295 } 3296 while (ct--) { 3297 obj = stack.pop(); 3298 tuple.unshift(obj); 3299 } 3300 if (tuple[0] && 3301 typeof tuple[0] == "string" && 3302 typeof specs[tuple[0].valueOf()] !== 'undefined') 3303 { 3304 var rec_name = tuple[0].valueOf();
3305 var rec = { _record: rec_name }; 3306 var spec = specs[rec_name]; 3307 var n = spec.length; 3308 if (n in tuple && !((n+1) in tuple)) { 3309 for (var i=0; i<n; i++) { 3310 rec[spec[i]] = tuple[i+1]; 3311 } 3312 stack.push(rec); 3313 } else { 3314 // Length mismatch - leave as tuple 3315 stack.push(tuple); 3316 } 3317 } else { 3318 stack.push(tuple); 3319 } 3320 return 1; 3321 } 3322 3323 function _push_nil(stack) { 3324 var list = []; 3325 list.ubf_type = ubf.LIST; 3326 stack.push(list); 3327 return 1; 3328 } 3329 3330 function _push_element(stack) { 3331 var obj = stack.pop(), list = stack.pop(); 3332 if(list.ubf_type != ubf.LIST) throw "Push error: not a list"; 3333 list.unshift(obj); 3334 stack.push(list); 3335 return 1; 3336 } 3337 3338 function _comment(bytes) { 3339 return _read(bytes.slice(1), "%") + 1; 3340 } 3341 3342 function _pop(bytes, env, stack) { 3343 var code = bytes.charAt(1); 3344 env[code] = stack.pop(); 3345 return 2; 3346 } 3347 3348 function _push(bytes, env, stack) { 3349 var code = bytes.charAt(0); 3350 if(!env.hasOwnProperty(code)) 3351 throw "Unknown register value: " + code; 3352 stack.push(env[code]); 3353 return 1; 3354 } 3355 3356 function _return(stack) { 3357 if(stack.length() == 1) 3358 return 0; 3359 throw "The stack should contain one item"; 3360 } 3361 3362 function _type(bytes, stack) { 3363 var n = _read(bytes.slice(1), '`', ubf.STRING, stack) + 1; 3364 switch (stack.pop()) { 3365 case "map": 3366 case "plist": 3367 var list = stack.pop(); 3368 var ks = Object.keys(list); 3369 var map = {}; 3370 if (list.ubf_type != ubf.LIST) throw "Type error: not a list (for map)"; 3371 for (var i = 0; i<ks.length; i++) { 3372 var k = ks[i]; 3373 if (k != 'ubf_type') { 3374 var elt = list[k]; 3375 if (typeof elt == "object" && 1 in elt) { 3376 map[elt[0]] = elt[1]; 3377 } else { 3378 map[elt] = true; 3379 } 3380 } 3381 } 3382 stack.push(map); 3383 break; 3384 case "f": 3385 var f = stack.pop(); 3386 if (typeof f != "string") throw "Type error: not a string (for float)"; 3387 stack.push(parseFloat(f)); 3388 break; 3389 case "dt": 3390 var dt = stack.pop(); 3391 if (typeof dt != "number") throw "Type error: not a number (for dt)"; 3392 stack.push(new Date(dt*1000)); 3393 break; 3394 default: 3395 break; 3396 } 3397 return n; 3398 } 3399 3400 function _operation(opcode, bytes, env, stack) { 3401 switch(opcode) { 3402 case " ":case "\r":case"\n":case"\t":case",": return skip_ws(bytes); 3403 case "-": 3404 case"0":case"1":case"2":case"3":case"4": 3405 case"5":case"6":case"7":case"8":case"9": return _integer_or_binary_data(bytes, stack); 3406 case '"': return _string(bytes, stack); 3407 case "'": return _constant(bytes, stack); 3408 case "{": return _start_tuple(stack); 3409 case "}": return _end_tuple(stack); 3410 case "#": return _push_nil(stack); 3411 case "&": return _push_element(stack); 3412 case "%": return _comment(bytes); 3413 case ">": return _pop(bytes, env, stack); 3414 case "$": return _return(stack); 3415 case "`": return _type(bytes, stack); 3416 default: return _push(bytes, env, stack); 3417 } 3418 } 3419 3420 function _utf8len ( s ) 3421 { 3422 var n = 0; 3423 for (var i = 0; i < s.length; i++) { 3424 var code = s.charCodeAt(i); 3425 if (code <= 0x7f) n++; 3426 else if (code <= 0x7ff) n += 2; 3427 else if (code >= 0xd800 && code <= 0xdfff) { 3428 n += 4; 3429 i++; 3430 } 3431 else if (code < 0xffff) n += 3; 3432 else n += 4; 3433 } 3434 return n; 3435 } 3436 3437 function _binarychars ( s, offset, bytect ) 3438 { 3439 var i = offset; 3440 while (bytect > 0) 3441 {
3442 var code = s.charCodeAt(i++); 3443 if (code <= 0x7f) bytect--; 3444 else if (code <= 0x7ff) bytect -= 2; 3445 else if (code >= 0xd800 && code <= 0xdfff) { 3446 bytect -= 4; 3447 i++; 3448 } 3449 else if (code < 0xffff) bytect -= 3; 3450 else bytect -= 4; 3451 } 3452 return i - offset; 3453 } 3454 3455 window.ubf = ubf; 3456})(window); 3457/* growl notice js 3458 ---------------------------------------------------------- 3459 3460 @package: Zotonic 2009 3461 @Author: Tim Benniks <[email protected]> 3462 3463 Copyright 2009 Tim Benniks 3464 3465 Licensed under the Apache License, Version 2.0 (the "License"); 3466 you may not use this file except in compliance with the License. 3467 You may obtain a copy of the License at 3468 3469 http://www.apache.org/licenses/LICENSE-2.0 3470 3471 Unless required by applicable law or agreed to in writing, software 3472 distributed under the License is distributed on an "AS IS" BASIS, 3473 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3474 See the License for the specific language governing permissions and 3475 limitations under the License. 3476 3477 ---------------------------------------------------------- */ 3478 3479(function(jQuery) 3480 { 3481 jQuery.extend( 3482 { 3483 noticeAdd: function(options) 3484 { 3485 var defaults = { 3486 inEffect: {opacity: 'show'}, // in effect 3487 inEffectDuration: 300, // in effect duration in miliseconds 3488 stayTime: 3000, // time in miliseconds before the item has to disappear 3489 text: '', // content of the item 3490 stay: false, // should the notice item stay or not? 3491 type: 'notice' // could also be error, succes 3492 }; 3493 3494 var map = { 3495 notice: 'alert-info', 3496 error: 'alert-danger' 3497 }; 3498 3499 // declare varaibles 3500 var options, noticeWrapAll, noticeItemOuter, noticeItemInner, noticeItemClose; 3501 3502 options = jQuery.extend({}, defaults, options); 3503 noticeWrapAll = (!jQuery('.notice-wrap').length) ? jQuery('<div></div>').addClass('notice-wrap').appendTo('body') : jQuery('.notice-wrap'); 3504 noticeItemOuter = jQuery('<div></div>').addClass('notice-item-wrapper'); 3505 noticeItemInner = jQuery('<div></div>').hide().addClass('alert ' + map[options.type]).prependTo(noticeWrapAll).html(options.text).animate(options.inEffect, options.inEffectDuration).wrap(noticeItemOuter); 3506 noticeItemClose = jQuery('<a>').addClass('close').prependTo(noticeItemInner).html('<span>×</span>').click(function() { jQuery.noticeRemove(noticeItemInner); }); 3507 3508 // hmmmz, zucht 3509 if(navigator.userAgent.match(/MSIE 6/i)) 3510 { 3511 noticeWrapAll.css({top: document.documentElement.scrollTop}); 3512 } 3513 3514 if(!options.stay) 3515 { 3516 setTimeout(function() 3517 { 3518 jQuery.noticeRemove(noticeItemInner); 3519 }, 3520 options.stayTime); 3521 } 3522 }, 3523 3524 noticeRemove: function(obj) 3525 { 3526 obj.animate({opacity: '0'}, 600, function() 3527 { 3528 obj.closest(".notice-item-wrapper").animate({height: '0px'}, 300, function() 3529 { 3530 obj.closest(".notice-item-wrapper").remove(); 3531 }); 3532 }); 3533 } 3534 }); 3535 })(jQuery); 3536/* imageviewer js 3537---------------------------------------------------------- 3538 3539@package: Zotonic 2009 3540@Author: Tim Benniks <[email protected]> 3541 3542Copyright 2009 Tim Benniks 3543 3544Licensed under the Apache License, Version 2.0 (the "License"); 3545you may not use this file except in compliance with the License. 3546You may obtain a copy of the License at 3547 3548http://www.apache.org/licenses/LICENSE-2.0 3549 3550Unless required by applicable law or agreed to in writing, softw
3550are 3551distributed under the License is distributed on an "AS IS" BASIS, 3552WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3553See the License for the specific language governing permissions and 3554limitations under the License. 3555 3556---------------------------------------------------------- */ 3557 3558$.widget("ui.imageviewer", 3559{ 3560 _init: function() 3561 { 3562 this.init(); 3563 var imageWrapper, imageMagnifier, bigImg; 3564 }, 3565 3566 init: function() 3567 { 3568 var ui = this; 3569 imageWrapper = jQuery('<div></div>').addClass('image-wrapper'); 3570 imageMagnifier = jQuery('<div></div>').addClass('image-magnifier').css({top: this.element.offset().top, left: this.element.offset().left}); 3571 bigImg = jQuery('<img alt="'+ui.element.attr('alt')+'" />').hide(); 3572 3573 this.element.wrap(imageWrapper).after(imageMagnifier).parent().hover(function() 3574 { 3575 $('.image-magnifier', $(this).parent()).show(150); 3576 }, 3577 function() 3578 { 3579 $('.image-magnifier', $(this).parent()).hide(150); 3580 }); 3581 3582 imageMagnifier.after(bigImg).click(function() 3583 { 3584 ui.loadImage() 3585 }); 3586 }, 3587 3588 loadImage: function() 3589 { 3590 var ui = this; 3591 var imageOrigSrc = ui.element.attr('src').split('.'); 3592 var imageTempSrc = imageOrigSrc[0].split('/image/'); 3593 var imageExt = imageOrigSrc[imageOrigSrc.length - 1]; 3594 var imageSrc = '/media/inline/' + imageTempSrc[imageTempSrc.length - 1] + '.' + imageExt; 3595 var bigImg = ui.element.siblings('img'); 3596 3597 var loader = $('<span></span>').css({background: '#fff url(/lib/images/spinner.gif) 50% 50% no-repeat', opacity: .5, width: ui.element.width(), height: ui.element.height(), position: "absolute", top: ui.element.offset().top, left: ui.element.offset().left}) 3598 3599 if(!$('.loaded-bigImage', ui.element.parent()).length) 3600 { 3601 $(document.body).append(loader); 3602 } 3603 3604 $(bigImg) 3605 .load(function() 3606 { 3607 $(this) 3608 .hide() 3609 .addClass('loaded-bigImage') 3610 .unbind('load'); 3611 3612 if(!$('.loaded-bigImage', ui.element.parent()).length) 3613 { 3614 ui.element.after($(this)); 3615 } 3616 3617 loader.remove();
3618 ui.setWidthHeight(); 3619 ui.showBig(); 3620 }) 3621 .attr({src: imageSrc}); 3622 }, 3623 3624 setWidthHeight: function() 3625 { 3626 $('.loaded-bigImage', this.element.parent()) 3627 .attr({ 3628 width: jQuery('.loaded-bigImage', this.element.parent()).width(), 3629 height: jQuery('.loaded-bigImage', this.element.parent()).height() 3630 }); 3631 }, 3632 3633 showBig: function() 3634 { 3635 var ui = this; 3636 var imgObj = jQuery('.loaded-bigImage', ui.element.parent()); 3637 var imgWrapper = ui.element.parent(); 3638 var zoomImgWidth = imgObj.attr('width'); 3639 var zoomImgHeight = imgObj.attr('height'); 3640 var fullWidth = zoomImgWidth; 3641 var fullHeight = zoomImgHeight; 3642 3643 if(zoomImgWidth > $(window).width()) 3644 { 3645 fullWidth = $(window).width() - 40; 3646 fullHeight = zoomImgHeight * (fullWidth / zoomImgWidth); 3647 } 3648 3649 if(zoomImgHeight > $(window).height()) 3650 { 3651 fullHeight = $(window).height() - 40; 3652 fullWidth = zoomImgWidth * (fullHeight / zoomImgHeight); 3653 } 3654 3655 leftPos = ($(window).width() / 2) - (fullWidth / 2); 3656 topPos = $(window).scrollTop() + ($(window).height() / 2) - (fullHeight / 2); 3657 3658 $(window).resize(function() 3659 { 3660 $('.image-magnifier', ui.element.parent()).each(function() 3661 { 3662 $(this).css({top: $(this).parent().offset().top, left: $(this).parent().offset().left}); 3663 }); 3664 }); 3665 3666 if(!$('.popup-overlay').length) 3667 { 3668 $('<span</span>') 3669 .addClass('popup-overlay') 3670 .appendTo(document.body) 3671 .css({display: 'none', height: $(document).height(), zIndex: 8000}) 3672 .animate({opacity: .7}, 200) 3673 .click(function() 3674 { 3675 ui.kill() 3676 }); 3677 } 3678 3679 $('.popup-overlay').show(); 3680 3681 imgObj 3682 .css({display: 'none', position: "absolute", zIndex: 9999, width: fullWidth, height: fullHeight, left: leftPos, top: topPos}) 3683 .fadeIn(200) 3684 .click(function() 3685 { 3686 ui.kill() 3687 }); 3688 }, 3689 3690 kill: function() 3691 { 3692 jQuery('.popup-overlay').fadeOut(200); 3693 jQuery('.loaded-bigImage').fadeOut(200); 3694 this.destroy(); 3695 } 3696});/* dialog js 3697 ---------------------------------------------------------- 3698 3699 @package: Zotonic 2009, 2012, 2015 3700 @Author: Tim Benniks <[email protected]> 3701 3702 Copyright 2009 Tim Benniks 3703 Copyright 2012 Arjan Scherpenisse 3704 Copyright 2015, 2016 Arthur Clemens 3705 3706 Licensed under the Apache License, Version 2.0 (the "License");
3707 you may not use this file except in compliance with the License. 3708 You may obtain a copy of the License at 3709 3710 http://www.apache.org/licenses/LICENSE-2.0 3711 3712 Unless required by applicable law or agreed to in writing, software 3713 distributed under the License is distributed on an "AS IS" BASIS, 3714 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3715 See the License for the specific language governing permissions and 3716 limitations under the License. 3717 3718 ---------------------------------------------------------- */ 3719 3720(function($) { 3721 $.extend({ 3722 dialogAdd: function(options) { 3723 var width, 3724 $title, 3725 $body, 3726 $text, 3727 $modalContent, 3728 dialogClass, 3729 $modalDialog, 3730 $dialog; 3731 3732 $('#zmodal').remove(); 3733 $('.modal-backdrop').remove(); 3734 3735 options = $.extend({}, $.ui.dialog.defaults, options); 3736 3737 if (options.backdrop !== 'static') { 3738 $title = $('<div>') 3739 .addClass('modal-header') 3740 .append($('<a>') 3741 .addClass('close') 3742 .attr('data-dismiss', 'modal') 3743 .html('<span>×</span>')) 3744 .append($('<h4>') 3745 .addClass('modal-title') 3746 .html(options.title)); 3747 } else { 3748 $title = $('<div>') 3749 .addClass('modal-header') 3750 .append($('<h4>') 3751 .addClass('modal-title') 3752 .html(options.title)); 3753 } 3754 $modalContent = $('<div>').addClass('modal-content'); 3755 $text = $('<div>').html(options.text); 3756 3757 // if .modal-body is used in a template, don't add it again 3758 if ($text.hasClass('modal-body')) { 3759 $body = $text; 3760 } else { 3761 $body = $('<div>') 3762 .addClass('modal-body') 3763 .html(options.text); 3764 } 3765 3766 $modalContent = $('<div>') 3767 .addClass('modal-content') 3768 .append($title) 3769 .append($body); 3770 3771 dialogClass = 'modal'; 3772 if (typeof(options.addclass) == 'string') { 3773 dialogClass += ' ' + options.addclass; 3774 } 3775 3776 $modalDialog = $('<div>') 3777 .addClass('modal-dialog') 3778 .append($modalContent); 3779 3780 width = options.width; 3781 if (width) { 3782 if (width === 'large') { 3783 $modalDialog.addClass('modal-lg'); 3784 } else if (width === 'small') { 3785 $modalDialog.addClass('modal-sm'); 3786 } else { 3787 $modalDialog.css({'width': width + 'px'}); 3788 } 3789 } 3790 3791 $dialog = $('<div>') 3792 .attr('id', 'zmodal') 3793 .addClass(dialogClass) 3794 .append($modalDialog) 3795 .appendTo($('body')); 3796 3797 $dialog 3798 .modal({backdrop: options.backdrop}) 3799 .css({'overflow-x': 'hidden', 'overflow-y': 'auto'}); 3800 3801 3802 if (options.center) { 3803 $modalDialog.hide(); 3804 setTimeout(function() { 3805 // $.dialogCenter(); 3806 $modalDialog.show(); 3807 }, 0); 3808 } 3809 3810 if (typeof($.widgetManager) != 'undefined') { 3811 $dialog.widgetManager(); 3812 } 3813 z_editor_add($dialog); 3814 }, 3815 3816 dialogClose: function() { 3817 $('#zmodal').modal('hide'); 3818 }, 3819 3820 dialogRemove: function(obj) { 3821 obj = obj || $('#zmodal'); 3822 z_editor_remove(obj); 3823 obj 3824 .draggable('destroy') 3825 .resizable('destroy') 3826 .fadeOut(300, function() { 3827 $(this).remove(); 3828 }); 3829 }, 3830 3831 dialogCenter: function() { 3832 var $dialog, 3833 newMarginTop; 3834 $dialog = $('#zmodal:visible').find('.modal-dialog'); 3835 newMarginTop = Math.max(0, ($(window).height() - $dialog.height()) / 2); 3836 newMarginTop *= .96; // visual coherence 3837 newMarginTop = Math.max(newMarginTop, 30); 3838 $dialog.css('margin-top', newMarginTop); 3839 }, 3840 3841 dialogScrollTo: function(position) { 3842 position = position || 0; 3843 $("#zmodal")[0].scrollTop = position 3844 } 3845 }); 3846 3847 // $(window).on('resize', function() { 3848 // $.dialogCenter(); 3849 // }); 3850 3851 $.widget('ui.show_dialog', { 3852 _init: function() { 3853 var self = this; 3854 this.element.click(function() { 3855 $.dialogAdd({ 3856 title: self.options.title, 3857 text: self.options.text, 3858 width: self.options.width, 3859 addclass: self.options.addclass, 3860 backdrop: self.options.backdrop 3861 }); 3862 }); 3863 } 3864 }); 3865 3866 /* 3867 Default dialog parameters: 3868 title: text, will be inserted in h4 3869 text: text content, may contain html (will be inserted into div) 3870 width: (optional) 3871 addclass: (optional) classname will be appended to default dialog class 3872 backdrop: (optional) boolean (0, 1) or the string 'static' 3873 center: (optional) boolean (0, 1); set to 0 to align dialog to the top 3874 */ 3875 $.ui.dialog.defaults = { 3876 title: 'Title', 3877 text: 'text', 3878 width: undefined, 3879 addclass: undefined, 3880 backdrop: 1, 3881 center: 1 3882 }; 3883})(jQuery); 3884// LiveValidation 1.3 (standalone version) 3885// Copyright (c) 2007-2008 Alec Hill (www.livevalidation.com) 3886// LiveValidation is licensed under the terms of the MIT License 3887 3888// MW: 20100316: Adapted for async usage with Zotonic.
3889// MW: 20100629: Added support for presence check on radio buttons 3890// MW: 20110329: Dynamically fetch the validation fields from the DOM, this makes it possible to add/remove fields dynamically. 3891// AC: 20150129: Removed unused class names. Changed messageClass to Bootstrap 3 conform "help-block". Replaced hardcoded "control-group" to fieldGroupClass, using Bootstrap 3 "form-group". Replaced hardcoded class "success" in favor of fieldGroupSuccessClass (Bootstrap 3 "has-success"); replaced hardcoded class "error" in favor of fieldGroupErrorClass (Bootstrap 3 "has-error"). 3892 3893 3894/*********************************************** LiveValidation class ***********************************/ 3895 3896 3897function addLiveValidation(element, args) { 3898 if (!$(element).data("z_live_validation")) 3899 $(element).data("z_live_validation", new LiveValidation($(element).attr('id'), args)); 3900} 3901 3902 3903function getLiveValidation(element) { 3904 return $(element).data("z_live_validation"); 3905} 3906 3907 3908/** 3909 * validates a form field in real-time based on validations you assign to it 3910 * 3911 * @var element {mixed} - either a dom element reference or the string id of the element to validate 3912 * @var optionsObj {Object} - general options, see below for details 3913 * 3914 * optionsObj properties: 3915 * validMessage {String} - the message to show when the field passes validation 3916 * (DEFAULT: "Thankyou!") 3917 * onAsync {Function} - function to execute when field passes is waiting for async validation 3918 * (DEFAULT: function(){ this.insertMessage(this.createSpinnerSpan()); this.addFieldClass(); } ) 3919 * onValid {Function} - function to execute when field passes validation 3920 * (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); } ) 3921 * onInvalid {Function} - function to execute when field fails validation 3922 * (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); }) 3923 * insertAfterWhatNode {Int} - position to insert default message 3924 * (DEFAULT: the field that is being validated) 3925 * onlyOnBlur {Boolean} - whether you want it to validate as you type or only on blur 3926 * (DEFAULT: false) 3927 * wait {Integer} - the time you want it to pause from the last keystroke before it validates (ms) 3928 * (DEFAULT: 0) 3929 * onlyOnSubmit {Boolean} - whether should be validated only when the form it belongs to is submitted 3930 * (DEFAULT: false) 3931 */ 3932 3933var LiveValidation = function(element, optionsObj){ 3934 this.initialize(element, optionsObj); 3935}; 3936 3937LiveValidation.VERSION = '1.3 standalone-zotonic'; 3938 3939/** element types constants ****/ 3940 3941LiveValidation.TEXTAREA = 1; 3942LiveValidation.TEXT = 2; 3943LiveValidation.PASSWORD = 3; 3944LiveValidation.CHECKBOX = 4; 3945LiveValidation.SELECT = 5; 3946LiveValidation.FILE = 6; 3947LiveValidation.RADIO = 7; 3948LiveValidation.FORM = 8; 3949 3950 3951/****** prototype ******/ 3952 3953LiveValidation.prototype = { 3954 3955 validClass: '', // was: z_valid 3956 invalidClass: 'z_invalid', // used ny mod_survey 3957 messageClass: 'z_validation help-block', 3958 validFieldClass: '', // was: z_valid_field 3959 invalidFieldClass: '', // was: form-field-error 3960 asyncFieldClass: '', // was: z_async_validation 3961 fieldGroupClass: 'form-group', 3962 fieldGroupErrorClass: 'has-error', 3963 fieldGroupSuccessClass: 'has-success', 3964 3965 /** 3966 * initialises all of the properties and events 3967 * 3968 * @var - Same as constructor above 3969 */ 3970 initialize: function(element, optionsObj){ 3971 var self = this; 3972 var $form; 3973 3974 if(!element) 3975 throw new Error("LiveValidation::initialize - No element reference or element id has been provided!"); 3976 this.element = element.nodeName ? element : document.getElementById(element); 3977 if(!this.element) 3978 throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!"); 3979 // default properties that could not be initialised above 3980 this.validations = []; 3981 this.elementType = this.getElementType(); 3982 var options = optionsObj || {}; 3983 if (this.elementType == LiveValidation.FORM) { 3984 this.form = this.element; 3985 $form = $(this.element); 3986 this.onAsync = options.onAsync || function(){}; 3987 this.onValid = options.onValid || function(){}; 3988 this.onInvalid = options.onInvalid || function(){}; 3989 this.onlyOnBlur = options.onlyOnBlur || false; 3990 this.wait = options.wait || 0; 3991 this.onlyOnSubmit = true; 3992 } else { 3993 this.form = this.element.form; 3994 $form = $(this.element).closest("form"); 3995 this.onAsync = options.onAsync || function(){ this.insertSpinner(this.createSpinnerSpan()); this.addFieldClass(); }; 3996 this.onValid = options.onValid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); }; 3997 this.onInvalid = options.onInvalid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); }; 3998 this.onlyOnBlur = options.onlyOnBlur || false; 3999 this.wait = options.wait || 0; 4000 this.onlyOnSubmit = options.onlyOnSubmit || false; 4001 } 4002 // options 4003 this.validMessage = options.validMessage || ''; 4004 var node = options.insertAfterWhatNode || this.element; 4005 this.insertAfterWhatNode = node.nodeType ? node : document.getElementById(node); 4006 4007 //if document.getElementById(node) returned null, then set the original element 4008 if(!this.insertAfterWhatNode) 4009 this.insertAfterWhatNode = this.element; 4010 4011 this.validationAsync = false;
4012 4013 // Initialize the form hooks, remember the LiveValidationForm object. 4014 if($form.length){ 4015 this.formObj = LiveValidationForm.getInstance($form[0]); 4016 } 4017 4018 // events 4019 // collect old events 4020 if (this.elementType != LiveValidation.FORM) { 4021 this.oldOnFocus = this.element.onfocus || function(){}; 4022 this.oldOnBlur = this.element.onblur || function(){}; 4023 this.oldOnClick = this.element.onclick || function(){}; 4024 this.oldOnChange = this.element.onchange || function(){}; 4025 this.oldOnKeyup = this.element.onkeyup || function(){}; 4026 this.element.onfocus = function(e){ self.doOnFocus(e); return self.oldOnFocus.call(this, e); }; 4027 if(!this.onlyOnSubmit){ 4028 switch(this.elementType){ 4029 case LiveValidation.RADIO: 4030 case LiveValidation.CHECKBOX: 4031 this.element.onclick = function(e){ self.validate(); return self.oldOnClick.call(this, e); }; 4032 this.element.onchange = function(e){ self.validate(); return self.oldOnChange.call(this, e); }; 4033 break; 4034 case LiveValidation.SELECT: 4035 case LiveValidation.FILE: 4036 this.element.onchange = function(e){ self.validate(); return self.oldOnChange.call(this, e); }; 4037 break; 4038 default: 4039 if(!this.onlyOnBlur) this.element.onkeyup = function(e){ self.deferValidation(); return self.oldOnKeyup.call(this, e); }; 4040 this.element.onblur = function(e){ self.doOnBlur(e); return self.oldOnBlur.call(this, e); }; 4041 break; 4042 } 4043 } 4044 } 4045 }, 4046 4047 /** 4048 * destroys the instance's events (restoring previous ones) and removes it from any LiveValidationForms 4049 */ 4050 destroy: function(){ 4051 // remove events - set them back to the previous events 4052 if (this.elementType != LiveValidation.FORM) { 4053 this.element.onfocus = this.oldOnFocus; 4054 if(!this.onlyOnSubmit){ 4055 switch(this.elementType){ 4056 case LiveValidation.RADIO: 4057 case LiveValidation.CHECKBOX: 4058 this.element.onclick = this.oldOnClick; 4059 this.element.onchange = this.oldOnChange; 4060 break; 4061 case LiveValidation.SELECT: 4062 case LiveValidation.FILE: 4063 this.element.onchange = this.oldOnChange; 4064 break; 4065 default: 4066 if(!this.onlyOnBlur) this.element.onkeyup = this.oldOnKeyup; 4067 this.element.onblur = this.oldOnBlur; 4068 break; 4069 } 4070 } 4071 } 4072 this.validations = []; 4073 this.removeMessageAndFieldClass(); 4074 }, 4075 4076 /** 4077 * Adds a validation to perform to a LiveValidation object 4078 * 4079 * @var validationFunction {Function} - validation function to be used (ie Validate.Presence ) 4080 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 4081 * @return {Object} - the LiveValidation object itself so that calls can be chained 4082 */ 4083 add: function(validationFunction, validationParamsObj){ 4084 this.validations.push( {type: validationFunction, params: validationParamsObj || {} } ); 4085 return this; 4086 }, 4087 4088 /** 4089 * Removes a validation from a LiveValidation object - must have exactly the same arguments as used to add it 4090 * 4091 * @var validationFunction {Function} - validation function to be used (ie Validate.Presence ) 4092 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 4093 * @return {Object} - the LiveValidation object itself so that calls can be chained 4094 */ 4095 remove: function(validationFunction, validationParamsObj){ 4096 var found = false; 4097 for( var i = 0, len = this.validations.length; i < len; i++ ){ 4098 if( this.validations[i].type == validationFunction ){ 4099 if (this.validations[i].params == validationParamsObj) { 4100 found = true; 4101 break; 4102 } 4103 } 4104 } 4105 if(found) this.validations.splice(i,1); 4106 return this; 4107 }, 4108 4109 4110 /** 4111 * makes the validation wait the alotted time from the last keystroke 4112 */ 4113 deferValidation: function(e){ 4114 if (this.wait >= 300) 4115 this.removeMessageAndFieldClass(); 4116 if (this.timeout) 4117 clearTimeout(this.timeout); 4118 var self = this; 4119 this.timeout = setTimeout( function(){ self.validate(); }, this.wait); 4120 }, 4121 4122 /** 4123 * sets the focused flag to false when field loses focus 4124 */ 4125 doOnBlur: function(e){ 4126 this.focused = false;
4127 this.validate(); 4128 }, 4129 4130 /** 4131 * sets the focused flag to true when field gains focus 4132 */ 4133 doOnFocus: function(e){ 4134 this.focused = true; 4135 this.removeMessageAndFieldClass(); 4136 }, 4137 4138 /** 4139 * gets the type of element, to check whether it is compatible 4140 * 4141 * @var validationFunction {Function} - validation function to be used (ie Validate.Presence ) 4142 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 4143 */ 4144 getElementType: function(){ 4145 var nodeName = this.element.nodeName.toUpperCase(); 4146 if (nodeName == 'TEXTAREA') 4147 return LiveValidation.TEXTAREA; 4148 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'TEXT') 4149 return LiveValidation.TEXT; 4150 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'PASSWORD') 4151 return LiveValidation.PASSWORD; 4152 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'CHECKBOX') 4153 return LiveValidation.CHECKBOX; 4154 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'FILE') 4155 return LiveValidation.FILE; 4156 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'RADIO') 4157 return LiveValidation.RADIO; 4158 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'EMAIL') 4159 return LiveValidation.TEXT; 4160 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'TEL') 4161 return LiveValidation.TEXT; 4162 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'NUMBER') 4163 return LiveValidation.TEXT; 4164 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'URL') 4165 return LiveValidation.TEXT; 4166 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'HIDDEN') 4167 return LiveValidation.TEXT; 4168 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'COLOR') 4169 return LiveValidation.TEXT; 4170 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'DATE') 4171 return LiveValidation.TEXT; 4172 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'DATETIME-LOCAL') 4173 return LiveValidation.TEXT; 4174 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'MONTH') 4175 return LiveValidation.TEXT; 4176 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'WEEK') 4177 return LiveValidation.TEXT; 4178 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'TIME') 4179 return LiveValidation.TEXT; 4180 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'RANGE') 4181 return LiveValidation.TEXT; 4182 if (nodeName == 'INPUT' && this.element.type.toUpperCase() == 'SEARCH') 4183 return LiveValidation.TEXT; 4184 if (nodeName == 'SELECT') 4185 return LiveValidation.SELECT; 4186 if (nodeName == 'FORM') 4187 return LiveValidation.FORM; 4188 if (nodeName == 'INPUT') 4189 throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!'); 4190 throw new Error('LiveValidation::getElementType - Element must be an input, select, or textarea!'); 4191 }, 4192 4193 /** 4194 * Loops through all the validations added to the LiveValidation object and checks them one by one 4195 * 4196 * @var validationFunction {Function} - validation function to be used (ie Validate.Presence ) 4197 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 4198 * @return {Boolean} - whether the all the validations passed or if one failed 4199 */ 4200 doValidations: function(isSubmit, submitTrigger){ 4201 var result = true; 4202 4203 this.validationFailed = false; 4204 this.validationAsync = false; 4205 for(var i = 0, len = this.validations.length; i < len; ++i){ 4206 var validation = this.validations[i]; 4207 switch(validation.type){ 4208 case Validate.Presence: 4209 case Validate.Confirmation: 4210 case Validate.Acceptance: 4211 case Validate.Custom: 4212 this.displayMessageWhenEmpty = true; 4213 break; 4214 default: 4215 break; 4216 } 4217 var v = this.validateElement(validation.type, validation.params, isSubmit, submitTrigger); 4218 if (v === 'async') { 4219 this.validationAsync = true; 4220 result = 'async'; 4221 } else if (!v) { 4222 this.validationFailed = true; 4223 return false;
4224 } 4225 } 4226 this.message = this.validMessage; 4227 return result; 4228 }, 4229 4230 /** 4231 * Check if there is an async validation. 4232 */ 4233 isAsync: function (){ 4234 for(var i = 0, len = this.validations.length; i < len; ++i) { 4235 var validation = this.validations[i]; 4236 if (validation.type == Validate.Postback || validation.params.isAsync === true) 4237 return true; 4238 } 4239 return false; 4240 }, 4241 4242 /** 4243 * Performs validation on the element and handles any error (validation or otherwise) it throws up 4244 * 4245 * @var validationFunction {Function} - validation function to be used (ie Validate.Presence ) 4246 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 4247 * @var isSubmit {Boolean} - is this a form submit or an individual field check 4248 * @var submitTrigger {Object} - the element that triggered the submit 4249 * @return {Boolean} or {"async"} - whether the validation has passed, failed or waits for an async server side check 4250 */ 4251 validateElement: function(validationFunction, validationParamsObj, isSubmit, submitTrigger){ 4252 if (!this.element.disabled) { 4253 var value = this.getValue(); 4254 if(validationFunction == Validate.Acceptance){ 4255 if(this.elementType != LiveValidation.CHECKBOX) 4256 throw new Error('LiveValidation::validateElement - Element to validate acceptance must be a checkbox!'); 4257 value = this.element.checked; 4258 } 4259 var isValid = true; 4260 try { 4261 isValid = validationFunction(value, validationParamsObj, isSubmit, submitTrigger); 4262 if (isValid === 'async') { 4263 this.validationAsync = true; 4264 } 4265 } 4266 catch(error) { 4267 if(error instanceof Validate.Error){ 4268 if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){ 4269 this.validationFailed = true; 4270 this.message = error.message; 4271 isValid = false; 4272 } 4273 } else { 4274 throw error; 4275 } 4276 } 4277 return isValid; 4278 } else { 4279 return true; 4280 } 4281 }, 4282 4283 4284 getValue: function() { 4285 switch (this.elementType) { 4286 case LiveValidation.SELECT: 4287 if (this.element.selectedIndex >= 0) return this.element.options[this.element.selectedIndex].value; 4288 else return ""; 4289 case LiveValidation.RADIO: 4290 return $('input[name="'+this.element.name+'"]:checked').val(); 4291 case LiveValidation.CHECKBOX: 4292 var val = []; 4293 $('input[name="'+this.element.name+'"]:checked').each(function() { val.push($(this).val()); }); 4294 if (val.length === 0) { 4295 return undefined; 4296 } else { 4297 return val; 4298 } 4299 case LiveValidation.FORM: 4300 return ""; 4301 default: 4302 return this.element.value; 4303 } 4304 }, 4305 4306 /** 4307 * Do all the validations and fires off the onValid or onInvalid callbacks 4308 * 4309 * @var isSubmit {Boolean} - is this a form submit or an individual field check 4310 * @var submitTrigger {Object} - the element that triggered the submit 4311 * @return {Boolean} or "async" - whether all the validations passed or if one failed 4312 */ 4313 validate: function(isSubmit, submitTrigger){ 4314 if(!this.element.disabled) { 4315 var valid = this.doValidations(isSubmit, submitTrigger); 4316 if (valid === 'async') { 4317 this.onAsync(); 4318 return 'async'; 4319 } else if (valid) { 4320 this.onValid(); 4321 return true; 4322 } else { 4323 this.onInvalid(); 4324 return false; 4325 } 4326 } else { 4327 return true; 4328 } 4329 }, 4330 4331 /** 4332 * Called when there is an async validation result. 4333 * The caller has already checked if the current input value hasn't changed. 4334 */ 4335 asyncValidationResult: function(isValid){ 4336 if (this.validationAsync){ 4337 // Find which validation was waiting for async, assume only one async postback per field. 4338 for(var i = 0, len = this.validations.length; i < len; ++i){
4339 var validation = this.validations[i]; 4340 if(validation.type == Validate.Postback || validation.params.isAsync === true) { 4341 // Clear the async wait flag 4342 this.validationAsync = false; 4343 this.validationFailed = !isValid; 4344 if (isValid){ 4345 this.onValid(); 4346 } else { 4347 this.onInvalid(); 4348 } 4349 this.formObj.asyncResult(this, isValid); 4350 } 4351 } 4352 } 4353 }, 4354 4355 /** 4356 * enables the field 4357 * 4358 * @return {LiveValidation} - the LiveValidation object for chaining 4359 */ 4360 enable: function(){ 4361 this.element.disabled = false; 4362 return this; 4363 }, 4364 4365 /** 4366 * disables the field and removes any message and styles associated with the field 4367 * 4368 * @return {LiveValidation} - the LiveValidation object for chaining 4369 */ 4370 disable: function(){ 4371 this.element.disabled = true; 4372 this.removeMessageAndFieldClass(); 4373 return this; 4374 }, 4375 4376 /** Message insertion methods **************************** 4377 * 4378 * These are only used in the onValid and onInvalid callback functions and so if you overide the default callbacks, 4379 * you must either impliment your own functions to do whatever you want, or call some of these from them if you 4380 * want to keep some of the functionality 4381 */ 4382 4383 /** 4384 * makes a span containing a spinner image 4385 * 4386 * @return {HTMLSpanObject} - a span element with the message in it 4387 */ 4388 createSpinnerSpan: function(){ 4389 var span = document.createElement('span'); 4390 span.innerHTML = '<img src="/lib/images/spinner.gif" height="16" width="16" alt="Validating..." />'; 4391 return span; 4392 }, 4393 4394 /** 4395 * makes a span containg the passed or failed message 4396 * 4397 * @return {HTMLSpanObject} - a span element with the message in it 4398 */ 4399 createMessageSpan: function(){ 4400 if (!this.message) return null; 4401 var span = document.createElement('span'); 4402 var textNode = document.createTextNode(this.message); 4403 span.appendChild(textNode); 4404 return span; 4405 }, 4406 4407 /** 4408 * Show an error message 4409 */ 4410 showErrorMessage: function(message){ 4411 this.message = message; 4412 this.onInvalid(); 4413 }, 4414 4415 /** 4416 * Insert a spinner in the message element. 4417 */ 4418 insertSpinner: function (elementToInsert){ 4419 this.removeMessage(); 4420 if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value === '')) 4421 || this.element.value !== '' ){ 4422 4423 elementToInsert.className += ' ' + this.messageClass + ' ' + this.asyncFieldClass; 4424 if(this.insertAfterWhatNode.nextSibling){ 4425 this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling); 4426 }else{ 4427 this.insertAfterWhatNode.parentNode.appendChild(elementToInsert); 4428 } 4429 } 4430 4431 }, 4432 4433 /** 4434 * inserts the element containing the message in place of the element that already exists (if it does) 4435 * 4436 * @var elementToIsert {HTMLElementObject} - an element node to insert 4437 */ 4438 insertMessage: function(elementToInsert){ 4439 this.removeMessage(); 4440 if (elementToInsert) { 4441 if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value === '')) 4442 || this.element.value !== '' ) { 4443 4444 var className = this.validationFailed ? this.invalidClass : this.validClass; 4445 elementToInsert.className += ' ' + this.messageClass + ' ' + className; 4446 if(this.insertAfterWhatNode.nextSibling){ 4447 this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling); 4448 }else{ 4449 this.insertAfterWhatNode.parentNode.appendChild(elementToInsert); 4450 } 4451 } 4452 } 4453 }, 4454 4455 4456 /** 4457 * changes the class of the field based on whether it is valid or not 4458 */ 4459 addFieldClass: function(){ 4460 this.removeFieldClass(); 4461 if(!this.validationFailed){ 4462 if(this.displayMessageWhenEmpty || this.element.value !== ''){ 4463 $('input[name="'+this.element.name+'"],select[name="'+this.element.name+'"],textarea[name="'+this.element.name+'"]') 4464 .closest('.' + this.fieldGroupClass).addClass(this.fieldGroupSuccessClass); 4465 switch (this.elementType) { 4466 case LiveValidation.RADIO: 4467 case LiveValidation.CHECKBOX: 4468 $('input[name="'+this.element.name+'"]').closest('label').addClass(this.validFieldClass); 4469 break; 4470 case LiveValidation.FORM: 4471 break; 4472 default: 4473 $(this.element).addClass(this.validFieldClass); 4474 break; 4475 } 4476 } 4477 }else{ 4478 $('input[name="'+this.element.name+'"],select[name="'+this.element.name+'"],textarea[name="'+this.element.name+'"]') 4479 .closest('.' + this.fieldGroupClass).removeClass(this.fieldGroupSuccessClass).addClass(this.fieldGroupErrorClass);
4480 $('label[for="'+this.element.id+'"]').addClass(this.invalidFieldClass); 4481 4482 switch (this.elementType) { 4483 case LiveValidation.RADIO: 4484 case LiveValidation.CHECKBOX: 4485 $('input[name="'+this.element.name+'"]').closest('label').addClass(this.invalidFieldClass); 4486 break; 4487 case LiveValidation.FORM: 4488 break; 4489 default: 4490 $(this.element).addClass(this.invalidFieldClass); 4491 break; 4492 } 4493 } 4494 }, 4495 4496 /** 4497 * removes the message element if it exists, so that the new message will replace it 4498 */ 4499 removeMessage: function(){ 4500 var nextEl; 4501 var el = this.insertAfterWhatNode; 4502 while(el.nextSibling){ 4503 if(el.nextSibling.nodeType === 1){ 4504 nextEl = el.nextSibling; 4505 break; 4506 } 4507 el = el.nextSibling; 4508 } 4509 if(nextEl && nextEl.className.indexOf(this.messageClass) != -1) 4510 this.insertAfterWhatNode.parentNode.removeChild(nextEl); 4511 }, 4512 4513 /** 4514 * removes the class that has been applied to the field to indicate if valid or not 4515 */ 4516 removeFieldClass: function(){ 4517 $('input[name="'+this.element.name+'"],select[name="'+this.element.name+'"],textarea[name="'+this.element.name+'"]') 4518 .closest('.' + this.fieldGroupClass).removeClass(this.fieldGroupSuccessClass).removeClass(this.fieldGroupErrorClass); 4519 $('label[for="'+this.element.id+'"]').removeClass(this.invalidFieldClass); 4520 switch (this.elementType) { 4521 case LiveValidation.RADIO: 4522 case LiveValidation.CHECKBOX: 4523 $('input[name="'+this.element.name+'"]').closest('label').removeClass(this.invalidFieldClass).removeClass(this.validFieldClass); 4524 break; 4525 case LiveValidation.FORM: 4526 break; 4527 default: 4528 $(this.element).removeClass(this.invalidFieldClass).removeClass(this.validFieldClass); 4529 break; 4530 } 4531 }, 4532 4533 /** 4534 * removes the message and the field class 4535 */ 4536 removeMessageAndFieldClass: function(){ 4537 this.removeMessage(); 4538 this.removeFieldClass(); 4539 } 4540 4541}; // end of LiveValidation class 4542 4543 4544 4545 4546/*************************************** LiveValidationForm class ****************************************/ 4547/** 4548 * This class is used internally by LiveValidation class to associate a LiveValidation field with a form it is icontained in one 4549 * 4550 * It will therefore not really ever be needed to be used directly by the developer, unless they want to associate a LiveValidation 4551 * field with a form that it is not a child of 4552 */ 4553 4554/** 4555 * handles validation of LiveValidation fields belonging to this form on its submittal 4556 * 4557 * @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm 4558 */ 4559var LiveValidationForm = function(element){ 4560 this.initialize(element); 4561}; 4562 4563/** 4564 * gets the instance of the LiveValidationForm if it has already been made or creates it if it doesnt exist 4565 * 4566 * @var element {HTMLFormElement} - a dom element reference to a form 4567 */ 4568LiveValidationForm.getInstance = function(element){ 4569 var rand = Math.random() * Math.random(); 4570 if(!$(element).attr("id")) 4571 $(element).attr("id", 'formId_' + rand.toString().replace(/\./, '') + new Date().valueOf()); 4572 var instance = $(element).data("z_live_validation_instance"); 4573 if (!instance) { 4574 instance = new LiveValidationForm(element); 4575 $(element).data("z_live_validation_instance", instance); 4576 } 4577 return instance; 4578}; 4579 4580LiveValidationForm.prototype = { 4581 validFormClass: 'z_form_valid', 4582 invalidFormClass: 'z_form_invalid', 4583 4584 /** 4585 * constructor for LiveValidationForm - handles validation of LiveValidation fields belonging to this form on its submittal 4586 * 4587 * @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm 4588 */ 4589 initialize: function(element){ 4590 this.name = $(element).attr("id"); 4591 this.element = element; 4592 this.skipValidations = 0; 4593 this.submitWaitForAsync = []; 4594 this.isWaitForFormAsync = false;
4595 this.clk = undefined; 4596 4597 // preserve the old onsubmit event 4598 this.oldOnSubmit = this.element.onsubmit || function(){}; 4599 var self = this; 4600 4601 this.onInvalid = function() { 4602 $(this).removeClass(self.validFormClass).addClass(self.invalidFormClass); 4603 $(".z_form_valid", this).hide(); 4604 $(".z_form_invalid", this).fadeIn(); 4605 }; 4606 this.onValid = function() { 4607 $(this).removeClass(self.invalidFormClass).addClass(self.validFormClass); 4608 $(".z_form_invalid", this).hide(); 4609 $(".z_form_valid", this).fadeIn(); 4610 }; 4611 4612 $(element).submit(function(event) { 4613 event.zIsValidated = true; 4614 if (self.skipValidations === 0) { 4615 var result = true; 4616 var is_first = true; 4617 var i, len; 4618 var fields = self.getFields(); 4619 4620 self.submitWaitForAsync = []; 4621 self.isWaitForFormAsync = false; 4622 self.clk = this.clk; 4623 4624 for(i = 0, len = fields.length; i < len; ++i ) { 4625 if (!fields[i].element.disabled) { 4626 var ve = fields[i].validate(true, this.clk); 4627 if (ve === 'async') { 4628 self.submitWaitForAsync.push(fields[i]); 4629 } else if (!ve) { 4630 result = false;
4631 } 4632 } 4633 } 4634 if (result === false) { 4635 self.submitWaitForAsync = []; 4636 } 4637 4638 // Optionally check validations attached to the form itself 4639 // Only done if all other validations are done and passing 4640 if (result && self.submitWaitForAsync.length === 0) { 4641 var formValidation = $(this).data('z_live_validation'); 4642 if (formValidation) { 4643 var vf = formValidation.validate(true, this.clk); 4644 if (vf === 'async') { 4645 self.submitWaitForAsync = [formValidation]; 4646 self.isWaitForFormAsync = true; 4647 } else if (!vf) { 4648 result = false; 4649 } 4650 } 4651 } 4652 if (self.submitWaitForAsync.length > 0) { 4653 event.stopImmediatePropagation(); 4654 return false; 4655 } else if (!result) { 4656 self.onInvalid.call(this); 4657 event.stopImmediatePropagation(); 4658 return false; 4659 } else { 4660 self.onValid.call(this); 4661 return z_form_submit_validated_do(event); 4662 } 4663 } else { 4664 self.skipValidations--; 4665 if (self.skipValidations === 0) { 4666 self.onValid.call(this); 4667 return z_form_submit_validated_do(event); 4668 } else { 4669 return false; 4670 } 4671 } 4672 }); 4673 }, 4674 4675 /** 4676 * destroy this instance and its events 4677 * 4678 * @var force {Boolean} - whether to force the destruction even if there are fields still associated 4679 */ 4680 destroy: function(force){ 4681 if (force || this.getFields().length === 0) { 4682 // remove events - set back to previous events 4683 this.element.onsubmit = this.oldOnSubmit; 4684 // remove from the instances namespace 4685 $(this.element).removeData("z_live_validation_instance"); 4686 return true; 4687 } else { 4688 return false; 4689 } 4690 }, 4691 4692 /** 4693 * get the to-be-validated fields 4694 */ 4695 getFields: function() { 4696 var fields = []; 4697 $("input,select,textarea", this.element).each(function() { 4698 var field = $(this).data('z_live_validation'); 4699 if (field) { 4700 fields.push(field); 4701 } 4702 }); 4703 return fields; 4704 }, 4705 4706 asyncResult: function(Validation, isValid){ 4707 if (isValid){ 4708 var index = $.inArray(Validation, this.submitWaitForAsync); 4709 if (index >= 0){ 4710 this.submitWaitForAsync.splice(index, 1); 4711 if (this.submitWaitForAsync.length === 0){ 4712 // Optionally perform (and wait for) form-level validations 4713 if (!this.isWaitForFormAsync) { 4714 var formValidation = $(this.element).data('z_live_validation'); 4715 if (formValidation) { 4716 var result = formValidation.validate(true, this.clk); 4717 if (result === 'async') { 4718 this.submitWaitForAsync = [formValidation]; 4719 this.isWaitForFormAsync = true; 4720 } else if (!result) { 4721 isValid = false; 4722 } else { 4723 isValid = true; 4724 } 4725 } 4726 } else { 4727 this.isWaitForFormAsync = false; 4728 } 4729 4730 if (!this.isWaitForFormAsync) { 4731 if (isValid){ 4732 // All validations were successful, resubmit (and skip validations for once) 4733 this.skipValidations = 1; 4734 var formObj = this.element; 4735 this.onValid.call(this); 4736 setTimeout(function(){ $(formObj).submit(); }, 0); 4737 } else { 4738 this.onInvalid.call(this); 4739 } 4740 } 4741 } 4742 } 4743 } else { 4744 if (this.submitWaitForAsync.length > 0) { 4745 this.onInvalid.call(this); 4746 } 4747 this.submitWaitForAsync = []; 4748 } 4749 } 4750}; // end of LiveValidationForm prototype 4751 4752 4753 4754 4755/*************************************** Validate class ****************************************/ 4756/** 4757 * This class contains all the methods needed for doing the actual validation itself 4758 * 4759 * All methods are static so that they can be used outside the context of a form field 4760 * as they could be useful for validating stuff anywhere you want really 4761 * 4762 * All of them will return true if the validation is successful, but will raise a ValidationError if 4763 * they fail, so that this can be caught and the message explaining the error can be accessed ( as just 4764 * returning false would leave you a bit in the dark as to why it failed ) 4765 * 4766 * Can use validation methods alone and wrap in a try..catch statement yourself if you want to access the failure 4767 * message and handle the error, or use the Validate::now method if you just want true or false 4768 */ 4769 4770var Validate = { 4771 4772 /** 4773 * validates that the field has been filled in 4774 * 4775 * @var value {mixed} - value to be checked 4776 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4777 * 4778 * paramsObj properties:
4779 * failureMessage {String} - the message to show when the field fails validation 4780 * (DEFAULT: "Can't be empty!") 4781 */ 4782 Presence: function(value, paramsObj){ 4783 paramsObj = paramsObj || {}; 4784 var message = paramsObj.failureMessage || ""; 4785 if(value === '' || value === null || value === undefined){ 4786 Validate.fail(message); 4787 } 4788 return true; 4789 }, 4790 4791 /** 4792 * validates that the value is numeric, does not fall within a given range of numbers 4793 * 4794 * @var value {mixed} - value to be checked 4795 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4796 * 4797 * paramsObj properties: 4798 * notANumberMessage {String} - the message to show when the validation fails when value is not a number 4799 * (DEFAULT: "Must be a number!") 4800 * notAnIntegerMessage {String} - the message to show when the validation fails when value is not an integer 4801 * (DEFAULT: "Must be a number!") 4802 * wrongNumberMessage {String} - the message to show when the validation fails when is param is used 4803 * (DEFAULT: "Must be {is}!") 4804 * tooLowMessage {String} - the message to show when the validation fails when minimum param is used 4805 * (DEFAULT: "Must not be less than {minimum}!") 4806 * tooHighMessage {String} - the message to show when the validation fails when maximum param is used 4807 * (DEFAULT: "Must not be more than {maximum}!") 4808 * is {Int} - the length must be this long 4809 * minimum {Int} - the minimum length allowed 4810 * maximum {Int} - the maximum length allowed 4811 * onlyInteger {Boolean} - if true will only allow integers to be valid 4812 * (DEFAULT: false) 4813 * 4814 * NB. can be checked if it is within a range by specifying both a minimum and a maximum 4815 * NB. will evaluate numbers represented in scientific form (ie 2e10) correctly as numbers 4816 */ 4817 Numericality: function(value, paramsObj){ 4818 var suppliedValue = value; 4819 value = Number(value); 4820 paramsObj = paramsObj || {}; 4821 var minimum = ((paramsObj.minimum) || (paramsObj.minimum === 0)) ? paramsObj.minimum : null; 4822 var maximum = ((paramsObj.maximum) || (paramsObj.maximum === 0)) ? paramsObj.maximum : null; 4823 var is = ((paramsObj.is) || (paramsObj.is === 0)) ? paramsObj.is : null; 4824 var notANumberMessage = paramsObj.notANumberMessage || "Must be a number."; 4825 var notAnIntegerMessage = paramsObj.notAnIntegerMessage || "Must be an integer."; 4826 var wrongNumberMessage = paramsObj.wrongNumberMessage || "Must be " + is + "."; 4827 var tooLowMessage = paramsObj.tooLowMessage || "Must not be less than " + minimum + "."; 4828 var tooHighMessage = paramsObj.tooHighMessage || "Must not be more than " + maximum + "."; 4829 4830 if (!isFinite(value)) 4831 Validate.fail(notANumberMessage); 4832 if (paramsObj.onlyInteger && (/\.0+$|\.$/.test(String(suppliedValue)) || value != parseInt(value,10)) ) 4833 Validate.fail(notAnIntegerMessage); 4834 switch(true){ 4835 case (is !== null): 4836 if( value != Number(is) ) Validate.fail(wrongNumberMessage); 4837 break; 4838 case (minimum !== null && maximum !== null): 4839 Validate.Numericality(value, {tooLowMessage: tooLowMessage, minimum: minimum}); 4840 Validate.Numericality(value, {tooHighMessage: tooHighMessage, maximum: maximum}); 4841 break; 4842 case (minimum !== null): 4843 if( value < Number(minimum) ) Validate.fail(tooLowMessage); 4844 break; 4845 case (maximum !== null): 4846 if( value > Number(maximum) ) Validate.fail(tooHighMessage); 4847 break; 4848 } 4849 return true; 4850 }, 4851 4852 /** 4853 * validates against a RegExp pattern 4854 * 4855 * @var value {mixed} - value to be checked 4856 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4857 * 4858 * paramsObj properties:
4859 * failureMessage {String} - the message to show when the field fails validation 4860 * (DEFAULT: "") 4861 * pattern {RegExp} - the regular expression pattern 4862 * (DEFAULT: /./) 4863 * negate {Boolean} - if set to true, will validate true if the pattern is not matched 4864 * (DEFAULT: false) 4865 * 4866 * NB. will return true for an empty string, to allow for non-required, empty fields to validate. 4867 * If you do not want this to be the case then you must either add a LiveValidation.PRESENCE validation 4868 * or build it into the regular expression pattern 4869 */ 4870 Format: function(value, paramsObj){ 4871 value = String(value); 4872 paramsObj = paramsObj || {}; 4873 var message = paramsObj.failureMessage || ""; 4874 var pattern = paramsObj.pattern || /./; 4875 var negate = paramsObj.negate || false; 4876 if(!negate && !pattern.test(value)) Validate.fail(message); // normal 4877 if(negate && pattern.test(value)) Validate.fail(message); // negated 4878 return true; 4879 }, 4880 4881 /** 4882 * validates that the field contains a valid email address 4883 * 4884 * @var value {mixed} - value to be checked 4885 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4886 * 4887 * paramsObj properties: 4888 * failureMessage {String} - the message to show when the field fails validation 4889 * (DEFAULT: "Must be a number!" or "Must be an integer!") 4890 */ 4891 Email: function(value, paramsObj){ 4892 paramsObj = paramsObj || {}; 4893 var message = paramsObj.failureMessage || ""; 4894 value = $.trim(value); 4895 // see validator_base_email.erl:43 4896 var re = /^$|^(("[^"\f\n\r\t\v\b]+")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((([A-Za-z0-9\-])+\.)+[A-Za-z\-]{2,})$/; 4897 Validate.Format(value, { failureMessage: message, pattern: re } ); 4898 return true; 4899 }, 4900 4901 /* 4902 * validates that the field contains a valid date 4903 * 4904 * @var value {mixed} - value to be checked 4905 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4906 * 4907 * paramsObj properties: 4908 * failureMessage {String} - the message to show when the field fails validation 4909 * (DEFAULT: "Incorrect Date") 4910 * format {String} - l, m,b endian 4911 * (DEFAULT: "l") 4912 * separator {String} - a character which is not a number 4913 * (DEFAULT: "-") 4914 * 4915 */ 4916 4917 Date: function(value, paramsObj){ 4918 function to_integer(value) { 4919 if (parseInt(value, 10) == value) { 4920 return parseInt(value, 10); 4921 } else { 4922 return parseInt("NaN", 10); 4923 } 4924 } 4925 4926 paramsObj = paramsObj || {}; 4927 var message = paramsObj.failureMessage || "Incorrect Date"; 4928 var format = paramsObj.format || "l"; 4929 var separator = paramsObj.separator || "-"; 4930 value = $.trim(value); 4931 4932 var date_components = value.split(separator); 4933 4934 if (date_components.length != 3) { 4935 Validate.fail(message); 4936 } else { 4937 var day; 4938 var month; 4939 var year; 4940 4941 not_a_number = to_integer(separator); 4942 if (!isNaN(not_a_number)) { 4943 throw "Seperator cannot be a number!"; 4944 } 4945 if (format == 'l') { 4946 day = to_integer(date_components[0]); 4947 month = to_integer(date_components[1]); 4948 year = to_integer(date_components[2]); 4949 } else if (format == 'b') { 4950 day = to_integer(date_components[2]); 4951 month = to_integer(date_components[1]); 4952 year = to_integer(date_components[0]); 4953 } else if (format == 'm') { 4954 day = to_integer(date_components[1]); 4955 month = to_integer(date_components[0]); 4956 year = to_integer(date_components[2]); 4957 } else { 4958 throw "Bad date format error!"; 4959 } 4960 var date_object = new Date(year, month-1, day); 4961 if (!((date_object.getDate() == day) && (date_object.getMonth()+1 == month) && (date_object.getFullYear() == year))) { 4962 Validate.fail(message); 4963 } 4964 } 4965 return true; 4966 }, 4967 4968 /** 4969 * validates the length of the value 4970 * 4971 * @var value {mixed} - value to be checked 4972 * @var paramsObj {Object} - parameters for this particular validation, see below for details 4973 * 4974 * paramsObj properties:
4975 * wrongLengthMessage {String} - the message to show when the fails when is param is used 4976 * (DEFAULT: "Must be {is} characters long!") 4977 * tooShortMessage {String} - the message to show when the fails when minimum param is used 4978 * (DEFAULT: "Must not be less than {minimum} characters long!") 4979 * tooLongMessage {String} - the message to show when the fails when maximum param is used 4980 * (DEFAULT: "Must not be more than {maximum} characters long!") 4981 * is {Int} - the length must be this long 4982 * minimum {Int} - the minimum length allowed 4983 * maximum {Int} - the maximum length allowed 4984 * 4985 * NB. can be checked if it is within a range by specifying both a minimum and a maximum 4986 */ 4987 Length: function(value, paramsObj){ 4988 value = String(value); 4989 paramsObj = paramsObj || {}; 4990 var minimum = ((paramsObj.minimum) || (paramsObj.minimum === 0)) ? paramsObj.minimum : null; 4991 var maximum = ((paramsObj.maximum) || (paramsObj.maximum === 0)) ? paramsObj.maximum : null; 4992 var is = ((paramsObj.is) || (paramsObj.is === 0)) ? paramsObj.is : null; 4993 var wrongLengthMessage = paramsObj.wrongLengthMessage || "Must be " + is + " characters long."; 4994 var tooShortMessage = paramsObj.tooShortMessage || "Must not be less than " + minimum + " characters long."; 4995 var tooLongMessage = paramsObj.tooLongMessage || "Must not be more than " + maximum + " characters long."; 4996 switch(true){ 4997 case (is !== null): 4998 if( value.length != Number(is) ) Validate.fail(wrongLengthMessage); 4999 break; 5000 case (minimum !== null && maximum !== null): 5001 Validate.Length(value, {tooShortMessage: tooShortMessage, minimum: minimum}); 5002 Validate.Length(value, {tooLongMessage: tooLongMessage, maximum: maximum}); 5003 break; 5004 case (minimum !== null): 5005 if( value.length < Number(minimum) ) Validate.fail(tooShortMessage); 5006 break; 5007 case (maximum !== null): 5008 if( value.length > Number(maximum) ) Validate.fail(tooLongMessage); 5009 break; 5010 default: 5011 throw new Error("Validate::Length - Length(s) to validate against must be provided"); 5012 } 5013 return true; 5014 }, 5015 5016 /** 5017 * validates that the value falls within a given set of values 5018 * 5019 * @var value {mixed} - value to be checked 5020 * @var paramsObj {Object} - parameters for this particular validation, see below for details 5021 * 5022 * paramsObj properties: 5023 * failureMessage {String} - the message to show when the field fails validation 5024 * (DEFAULT: "Must be included in the list!") 5025 * within {Array} - an array of values that the value should fall in 5026 * (DEFAULT: []) 5027 * allowNull {Bool} - if true, and a null value is passed in, validates as true 5028 * (DEFAULT: false) 5029 * partialMatch {Bool} - if true, will not only validate against the whole value to check but also if it is a substring of the value 5030 * (DEFAULT: false) 5031 * caseSensitive {Bool}
5031 - if false will compare strings case insensitively 5032 * (DEFAULT: true) 5033 * negate {Bool} - if true, will validate that the value is not within the given set of values 5034 * (DEFAULT: false) 5035 */ 5036 Inclusion: function(value, paramsObj){ 5037 paramsObj = paramsObj || {}; 5038 var message = paramsObj.failureMessage || "Must be included in the list!"; 5039 var caseSensitive = (paramsObj.caseSensitive === false) ? false : true; 5040 if(paramsObj.allowNull && value === null) 5041 return true; 5042 if(!paramsObj.allowNull && value === null) 5043 Validate.fail(message); 5044 var within = paramsObj.within || []; 5045 var length; 5046 5047 //if case insensitive, make all strings in the array lowercase, and the value too 5048 if(!caseSensitive){ 5049 var lowerWithin = []; 5050 length = within.length; 5051 for(var j = 0; j < length; ++j){ 5052 var item = within[j]; 5053 if(typeof item == 'string') 5054 item = item.toLowerCase(); 5055 lowerWithin.push(item); 5056 } 5057 within = lowerWithin; 5058 if(typeof value == 'string') 5059 value = value.toLowerCase(); 5060 } 5061 var found = false; 5062 length = within.length; 5063 for(var i = 0; i < length; ++i){ 5064 if(within[i] == value) found = true; 5065 if(paramsObj.partialMatch){ 5066 if(value.indexOf(within[i]) != -1) found = true; 5067 } 5068 } 5069 if( (!paramsObj.negate && !found) || (paramsObj.negate && found) ) 5070 Validate.fail(message); 5071 return true; 5072 }, 5073 5074 /** 5075 * validates that the value does not fall within a given set of values 5076 * 5077 * @var value {mixed} - value to be checked 5078 * @var paramsObj {Object} - parameters for this particular validation, see below for details 5079 * 5080 * paramsObj properties: 5081 * failureMessage {String} - the message to show when the field fails validation 5082 * (DEFAULT: "Must not be included in the list!") 5083 * within {Array} - an array of values that the value should not fall in 5084 * (DEFAULT: []) 5085 * allowNull {Bool} - if true, and a null value is passed in, validates as true 5086 * (DEFAULT: false) 5087 * partialMatch {Bool} - if true, will not only validate against the whole value to check but also if it is a substring of the value 5088 * (DEFAULT: false) 5089 * caseSensitive {Bool}
5089 - if false will compare strings case insensitively 5090 * (DEFAULT: true) 5091 */ 5092 Exclusion: function(value, paramsObj){ 5093 paramsObj = paramsObj || {}; 5094 paramsObj.failureMessage = paramsObj.failureMessage || "Must not be included in the list"; 5095 paramsObj.negate = true; 5096 Validate.Inclusion(value, paramsObj); 5097 return true; 5098 }, 5099 5100 /** 5101 * validates that the value matches that in another field 5102 * 5103 * @var value {mixed} - value to be checked 5104 * @var paramsObj {Object} - parameters for this particular validation, see below for details 5105 * 5106 * paramsObj properties: 5107 * failureMessage {String} - the message to show when the field fails validation 5108 * (DEFAULT: "Does not match!") 5109 * match {String} - id of the field that this one should match 5110 */ 5111 Confirmation: function(value, paramsObj){ 5112 if(!paramsObj.match) 5113 throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided"); 5114 paramsObj = paramsObj || {}; 5115 var message = paramsObj.failureMessage || "Does not match."; 5116 var match = paramsObj.match.nodeName ? paramsObj.match : document.getElementById(paramsObj.match); 5117 if(!match) 5118 throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + paramsObj.match + "'"); 5119 if(value != match.value){ 5120 Validate.fail(message); 5121 } 5122 return true; 5123 }, 5124 5125 /** 5126 * validates that the value is true (for use primarily in detemining if a checkbox has been checked) 5127 * 5128 * @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox) 5129 * @var paramsObj {Object} - parameters for this particular validation, see below for details 5130 * 5131 * paramsObj properties: 5132 * failureMessage {String} - the message to show when the field fails validation 5133 * (DEFAULT: "Must be accepted!") 5134 */ 5135 Acceptance: function(value, paramsObj){ 5136 paramsObj = paramsObj || {}; 5137 var message = paramsObj.failureMessage || "Must be accepted."; 5138 if(!value){ 5139 Validate.fail(message); 5140 } 5141 return true; 5142 }, 5143 5144 /** 5145 * validates against a custom function that returns true or false (or throws a Validate.Error) when passed the value 5146 * 5147 * @var value {mixed} - value to be checked 5148 * @var paramsObj {Object} - parameters for this particular validation, see below for details 5149 * 5150 * paramsObj properties: 5151 * failureMessage {String} - the message to show when the field fails validation 5152 * (DEFAULT: "") 5153 * against {Function} - a function that will take the value and object of arguments and return true or false 5154 * (DEFAULT: function(){ return true; }) 5155 * args {Object} - an object of named arguments that will be passed to the custom function so are accessible through this object within it 5156 * (DEFAULT: {}) 5157 */ 5158 Custom: function(value, paramsObj, isSubmit, submitTrigger){ 5159 paramsObj = paramsObj || {}; 5160 var against = paramsObj.against || function(){ return true; }; 5161 var args = paramsObj.args || {}; 5162 var message = paramsObj.failureMessage || ""; 5163 var result; 5164 5165 if (typeof against == "string") { 5166 result = z_call_function_by_name(against, window, value, args, isSubmit, submitTrigger); 5167 } else { 5168 result = against(value, args, isSubmit, submitTrigger); 5169 } 5170 if (result === 'async') { 5171 return 'async'; 5172 } else { 5173 if(!result) Validate.fail(message); 5174 return true; 5175 } 5176 }, 5177 5178 5179 /** 5180 * Performs a postback, delays the check till the postback is returned. till then a spinner is shown 5181 * next to the input element. 5182 */ 5183 Postback: function(value, paramsObj, isSubmit, submitTrigger) { 5184 paramsObj = paramsObj || {}; 5185 var against = paramsObj.against || function(){ return true; }; 5186 var args = paramsObj.args || {}; 5187 var message = paramsObj.failureMessage || ""; 5188 5189 if (!against(value, args, isSubmit, submitTrigger)) { 5190 Validate.fail(message); 5191 } else if (paramsObj.z_postback) { 5192 // Perform the async postback 5193 extraParams = []; 5194 if (isSubmit) { 5195 extraParams.push({name: 'z_submitter', value: (submitTrigger && submitTrigger.name) ? submitTrigger.name : ''}); 5196 } 5197 z_queue_postback(paramsObj.z_id, paramsObj.z_postback, extraParams); 5198 return 'async'; 5199 } else { 5200 return true; 5201 } 5202 }, 5203 5204 5205 /** 5206 * validates whatever it is you pass in, and handles the validation error for you so it gives a nice true or false reply 5207 * 5208 * @var validationFunction {Function} - validation function to be used (ie Validation.validatePresence ) 5209 * @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox) 5210 * @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary 5211 */
5212 now: function(validationFunction, value, validationParamsObj){ 5213 if(!validationFunction) 5214 throw new Error("Validate::now - Validation function must be provided!"); 5215 var isValid = true; 5216 try { 5217 validationFunction(value, validationParamsObj || {}); 5218 } catch(error) { 5219 if (error instanceof Validate.Error) { 5220 isValid = false; 5221 } else { 5222 throw error; 5223 } 5224 } finally { 5225 return isValid; 5226 } 5227 }, 5228 5229 /** 5230 * shortcut for failing throwing a validation error 5231 * 5232 * @var errorMessage {String} - message to display 5233 */ 5234 fail: function(errorMessage){ 5235 throw new Validate.Error(errorMessage); 5236 }, 5237 5238 Error: function(errorMessage){ 5239 this.message = errorMessage; 5240 this.name = 'ValidationError'; 5241 } 5242 5243}; 5244/* inputoverlay js 5245---------------------------------------------------------- 5246 5247@package: Zotonic 2010 5248@Author: Marc Worrell <[email protected]> 5249 5250Copyright 2010 Marc Worrell 5251 5252Licensed under the Apache License, Version 2.0 (the "License"); 5253you may not use this file except in compliance with the License. 5254You may obtain a copy of the License at 5255 5256http://www.apache.org/licenses/LICENSE-2.0 5257 5258Unless required by applicable law or agreed to in writing, software 5259distributed under the License is distributed on an "AS IS" BASIS, 5260WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5261See the License for the specific language governing permissions and 5262limitations under the License. 5263 5264---------------------------------------------------------- */ 5265 5266/* 5267This widget overlays a label field with an input field. When the input 5268is empty the label is visible. When there is content then the label is hidden. 5269 5270HTML: 5271 5272<p class="do_inputoverlay"> 5273 <span>Username</span> 5274 <input type="text" id="username" name="username" value="" /> 5275</p> 5276 5277CSS: 5278 5279p.do_inputoverlay { 5280 margin: 0px; 5281 padding: 0px; 5282 position: relative; 5283 height: 40px; 5284 font-size: 18px; 5285} 5286 5287p.do_inputoverlay input { 5288 position: absolute; 5289 left: 0px; 5290 background: none; 5291 font-size: 18px; 5292} 5293 5294p.do_inputoverlay span { 5295 position: absolute; 5296 left: 8px; 5297 top: 5px; 5298 color: #aaa; 5299} 5300 5301p.do_inputoverlay span.focus { 5302 color: #d8d8d8; 5303} 5304 5305p.do_inputoverlay span.hidden { 5306 display: none; 5307} 5308 5309*/ 5310 5311$.widget("ui.inputoverlay", 5312{ 5313 _init: function() 5314 { 5315 var self = this; 5316 var obj = this.element; 5317 var input = $('input', obj); 5318 var span = $('span', obj); 5319 5320 if (!input.length) { 5321 input = $('textarea', obj); 5322 } 5323 if ($(input).val() != "") { 5324 $(span).addClass('hidden'); 5325 } 5326 5327 var func = function(focus) { 5328 if ($(input).val() == "") { 5329 if (focus) { 5330 $(span).removeClass('hidden').addClass('focus'); 5331 } else { 5332 $(span).removeClass('hidden').removeClass('focus'); 5333 } 5334 } else { 5335 $(span).removeClass('focus').addClass('hidden'); 5336 } 5337 }; 5338 5339 input.change(function() { 5340 func(true); 5341 }).focus(function() { 5342 func(true); 5343 }).blur(function() { 5344 func(false); 5345 }).keydown(function() { 5346 setTimeout(function(){func(true);},10); 5347 }).keyup(function() { 5348 func(true); 5349 }); 5350 5351 input.closest("form").bind("reset", function() { 5352 setTimeout(function(){func(true);},10); 5353 }); 5354 5355 span.click(function() { 5356 input.focus(); 5357 }); 5358 5359 if (input.attr('autocomplete') == 'on') { 5360 setInterval(function() { 5361 if ($(input).val() == "") { 5362 $(span).removeClass('hidden'); 5363 } else { 5364 $(span).addClass('hidden'); 5365 } 5366 }, 100); 5367 } 5368 } 5369}); 5370/** 5371 * Copyright (c) 2009 Sergiy Kovalchuk ([email protected]) 5372 * 5373 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 5374 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 5375 * 5376 * Following code is based on Element.mask() implementation from ExtJS framework (http://extjs.com/) 5377 * 5378 */ 5379;(function($){ 5380 5381 /** 5382 * Displays loading mask over selected element(s). Accepts both single and multiple selectors. 5383 * 5384 * @param label Text message that will be displayed on top of the mask besides a spinner (optional). 5385 * If not provided only mask will be displayed without a label or a spinner. 5386 * @param delay Delay in milliseconds before element is masked (optional). If unmask() is called 5387 * before the delay times out, no mask is displayed. This can be used to prevent unnecessary 5388 * mask display for quick processes. 5389 */ 5390 $.fn.mask = function(label, delay){ 5391 $(this).each(function() { 5392 if(delay !== undefined && delay > 0) { 5393 var element = $(this); 5394 element.data("_mask_timeout", setTimeout(function() { $.maskElement(element, label)}, delay)); 5395 } else { 5396 $.maskElement($(this), label); 5397 } 5398 }); 5399 }; 5400 5401 /**
5402 * Removes mask from the element(s). Accepts both single and multiple selectors. 5403 */ 5404 $.fn.unmask = function(){ 5405 $(this).each(function() { 5406 $.unmaskElement($(this)); 5407 }); 5408 }; 5409 5410 /** 5411 * Checks if a single element is masked. Returns false if mask is delayed or not displayed. 5412 */ 5413 $.fn.isMasked = function(){ 5414 return this.hasClass("masked"); 5415 }; 5416 5417 /** 5418 * Show or update the upload progressbar 5419 */ 5420 $.fn.maskProgress = function(value){ 5421 $(this).each(function() { 5422 $.maskProgress($(this), value); 5423 }); 5424 }; 5425 5426 $.maskElement = function(element, label){ 5427 5428 //if this element has delayed mask scheduled then remove it and display the new one 5429 if (element.data("_mask_timeout") !== undefined) { 5430 clearTimeout(element.data("_mask_timeout")); 5431 element.removeData("_mask_timeout"); 5432 } 5433 5434 if(element.isMasked()) { 5435 $.unmaskElement(element); 5436 } 5437 5438 if(element.css("position") == "static") { 5439 element.addClass("masked-relative"); 5440 } 5441 5442 element.addClass("masked"); 5443 5444 var maskDiv = $('<div class="loadmask"></div>'); 5445 5446 //auto height fix for IE 5447 if(navigator.userAgent.toLowerCase().indexOf("msie") > -1){ 5448 maskDiv.height(element.height() + parseInt(element.css("padding-top")) + parseInt(element.css("padding-bottom"))); 5449 maskDiv.width(element.width() + parseInt(element.css("padding-left")) + parseInt(element.css("padding-right"))); 5450 } 5451 5452 //fix for z-index bug with selects in IE6 5453 if(navigator.userAgent.toLowerCase().indexOf("msie 6") > -1){ 5454 element.find("select").addClass("masked-hidden"); 5455 } 5456 5457 element.append(maskDiv); 5458 5459 if ($(element).progressbar != undefined) { 5460 var maskProgressDiv = $('<div class="loadmask-progress" style="display:none;"></div>'); 5461 element.append(maskProgressDiv); 5462 element.find(".loadmask-progress").progressbar({value: 0}); 5463 } 5464 if(label !== undefined) { 5465 var maskMsgDiv = $('<div class="loadmask-msg" style="display:none;"></div>'); 5466 maskMsgDiv.append('<div>' + label + '</div>'); 5467 element.append(maskMsgDiv); 5468 5469 //calculate center position 5470 maskMsgDiv.css("top", Math.round(element.height() / 2 - (maskMsgDiv.height() - parseInt(maskMsgDiv.css("padding-top")) - parseInt(maskMsgDiv.css("padding-bottom"))) / 2)+"px"); 5471 maskMsgDiv.css("left", Math.round(element.width() / 2 - (maskMsgDiv.width() - parseInt(maskMsgDiv.css("padding-left")) - parseInt(maskMsgDiv.css("padding-right"))) / 2)+"px"); 5472 5473 maskMsgDiv.show(); 5474 } 5475 }; 5476 5477 $.maskProgress = function(element, value){ 5478 if ($(element).progressbar != undefined) { 5479 element.find(".loadmask-progress").show().progressbar('option', 'value', value); 5480 element.find(".loadmask-msg").hide(); 5481 } 5482 }; 5483 5484 $.unmaskElement = function(element){ 5485 //if this element has delayed mask scheduled then remove it 5486 if (element.data("_mask_timeout") !== undefined) { 5487 clearTimeout(element.data("_mask_timeout")); 5488 element.removeData("_mask_timeout"); 5489 } 5490 5491 element.find(".loadmask-msg,.loadmask-progress,.loadmask").remove(); 5492 element.removeClass("masked"); 5493 element.removeClass("masked-relative"); 5494 element.find("select").removeClass("masked-hidden"); 5495 }; 5496 5497})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.