PageSourceSearch

https://www.mijnzuiderzee.nl/lib/js/apps/zotonic-1.0~z.widgetmanag…z.inputoverlay~jquery.loadmask~302205000.js

js mijnzuiderzee.nl collected 2026-09-25 07:25:48 UTC 180,925 bytes, 5,497 lines download raw bytes

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()">&times;</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, "&amp;")
2191            .replace(/</g, "&lt;")
2192            .replace(/>/g, "&gt;")
2193            .replace(/"/g, "&quot;")
2194            .replace(/'/g, "&#39;");
2195}
2196
2197// HTML unescape a string.
2198function html_unescape(s)
2199{
2200    return s.replace(/&lt;/g, "<")
2201            .replace(/&gt;/g, ">")
2202            .replace(/&quot;/g, "\"")
2203            .replace(/&#39;/g, "'")
2204            .replace(/&amp;/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>&times;</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>&times;</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.