PageSourceSearch

https://myoresearch.com/modules/system/assets/js/framework.js

js myoresearch.com collected 2026-09-24 09:37:41 UTC 33,770 bytes, 942 lines download raw bytes

1/* ========================================================================
2 * October CMS: Frontend JavaScript Framework
3 * https://octobercms.com
4 * ========================================================================
5 * Copyright 2013-2021 Alexey Bobkov, Samuel Georges
6 * ======================================================================== */
7
8if (window.jQuery === undefined) {
9    throw new Error('The jQuery library is not loaded! The October CMS framework cannot be initialized.');
10}
11if (window.jQuery.request !== undefined) {
12    throw new Error('The October CMS framework is already loaded!');
13}
14
15+function ($) { "use strict";
16
17    var Request = function (element, handler, options) {
18        var $el = this.$el = $(element);
19        this.options = options || {};
20
21        /*
22         * Validate handler name
23         */
24        if (handler === undefined) {
25            throw new Error('The request handler name is not specified.');
26        }
27
28        if (!handler.match(/^(?:\w+\:{2})?on*/)) {
29            throw new Error('Invalid handler name. The correct handler name format is: "onEvent".');
30        }
31
32        /*
33         * Prepare the options and execute the request
34         */
35        var context = { handler: handler, options: options };
36        $el.trigger('ajaxSetup', [context]);
37
38        /*
39         * Validate the form client-side
40         */
41        var $form = options.form ? $(options.form) : $el.closest('form'),
42            $triggerEl = !!$form.length ? $form : $el;
43
44        if (
45            options.browserValidate !== undefined &&
46            typeof document.createElement('input').reportValidity === 'function' &&
47            !!$form.length &&
48            !$form.get(0).checkValidity()
49        ) {
50            $form.get(0).reportValidity();
51            return false;
52        }
53
54        /*
55         * Execute the request
56         */
57        var _event = jQuery.Event('oc.beforeRequest');
58        $triggerEl.trigger(_event, context);
59        if (_event.isDefaultPrevented()) {
60            return;
61        }
62
63        var loading = options.loading !== undefined ? options.loading : null,
64            url = options.url !== undefined ? options.url : window.location.href,
65            isRedirect = options.redirect !== undefined && options.redirect.length,
66            useFlash = options.flash !== undefined,
67            useFiles = options.files !== undefined;
68
69        if (useFiles && typeof FormData === 'undefined') {
70            console.warn('This browser does not support file uploads via FormData');
71            useFiles = false;
72        }
73
74        if ($.type(loading) == 'string') {
75            loading = $(loading);
76        }
77
78        /*
79         * Request headers
80         */
81        var requestHeaders = {
82            'X-OCTOBER-REQUEST-HANDLER': handler,
83            'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update)
84        };
85
86        if (useFlash) {
87            requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1;
88        }
89
90        var csrfToken = getXSRFToken();
91        if (csrfToken) {
92            requestHeaders['X-XSRF-TOKEN'] = csrfToken;
93        }
94
95        /*
96         * Request data
97         */
98        var requestData,
99            inputName,
100            data = {};
101
102        $.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() {
103            $.extend(data, paramToObj('data-request-data', $(this).data('request-data')));
104        });
105
106        if ($el.is(':input') && !$form.length) {
107            inputName = $el.attr('name');
108            if (inputName !== undefined && options.data[inputName] === undefined) {
109                options.data[inputName] = $el.val();
110            }
111        }
112
113        if (options.data !== undefined && !$.isEmptyObject(options.data)) {
114            $.extend(data, options.data);
115        }
116
117        if (useFiles) {
118            requestData = new FormData($form.length ? $form.get(0) : undefined);
119
120            if ($el.is(':file') && inputName) {
121                $.each($el.prop('files'), function() {
122                    requestData.append(inputName, this);
123                });
124
125                delete data[inputName];
126            }
127
128            $.each(data, function(key) {
129                requestData.append(key, this);
130            });
131        }
132        else {
133            requestData = [$form.serialize(), $.param(data)].filter(
133Boolean).join('&');
134        }
135
136        /*
137         * Request options
138         */
139        var requestOptions = {
140            url: url,
141            crossDomain: false,
142            global: options.ajaxGlobal,
143            context: context,
144            headers: requestHeaders,
145            success: function(data, textStatus, jqXHR) {
146                /*
147                 * Halt here if beforeUpdate() or data-request-before-update returns false
148                 */
149                if (options.beforeUpdate.apply(context, [data, textStatus, jqXHR]) === false) {
150                    return;
151                }
152
153                if (options.evalBeforeUpdate && $.proxy(new Function('data', options.evalBeforeUpdate), $el.get(0))(data) === false) {
154                    return;
155                }
156
157                /*
158                 * Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called
159                 */
160                var _event = jQuery.Event('ajaxBeforeUpdate');
161                $triggerEl.trigger(_event, [context, data, textStatus, jqXHR]);
162                if (_event.isDefaultPrevented()) return;
163
164                if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) {
165                    $.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) {
166                        requestOptions.handleFlashMessage(message, type);
167                    })
168                }
169
170                /*
171                 * Proceed with the update process
172                 */
173                var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR);
174
175                updatePromise.done(function() {
176                    $triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR]);
177                    options.evalSuccess && $.proxy(new Function('data', options.evalSuccess), $el.get(0))(data);
178                    options.afterUpdate.apply(context, [data, textStatus, jqXHR]);
179                })
180
181                return updatePromise;
182            },
183            error: function(jqXHR, textStatus, errorThrown) {
184                var data = {},
185                    errorMsg,
186                    updatePromise = $.Deferred();
187
188                if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort') {
189                    return;
190                }
191
192                /*
193                 * Disable redirects
194                 */
195                isRedirect = false;
196                options.redirect = null;
197
198                /*
199                 * Error 406 is a "smart error" that returns response object that is
200                 * processed in the same fashion as a successful response.
201                 */
202                if (jqXHR.status == 406 && jqXHR.responseJSON) {
203                    data = jqXHR.responseJSON;
204                    errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE'];
205                    updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR);
206                }
207                /*
208                 * Standard error with standard response text
209                 */
210                else {
211                    errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText;
212                    updatePromise.resolve();
213                }
214
215                updatePromise.done(function() {
216                    $el.data('error-message', errorMsg);
217
218                    /*
219                     * Trigger 'ajaxError' on the form, halt if event.preventDefault() is called
220                     */
221                    var _event = jQuery.Event('ajaxError');
222                    $triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR]);
223                    if (_event.isDefaultPrevented()) return;
224
225                    /*
226                     * Halt here if the data-request-error attribute returns false
227                     */
228                    if (options.evalError && $.proxy(new Function('data', options.evalError), $el.get(0))(data) === false) {
229                        return;
230                    }
231
232                    requestOptions.handleErrorMessage(errorMsg);
233                });
234
235                return updatePromise;
236            },
237            complete: function(data, textStatus, jqXHR) {
238                $triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR]);
239                options.evalComplete && $.proxy(new Function('data', options.evalComplete), $el.get(0))(data);
240            },
241
242            /*
243             * Custom function, requests confirmation from the user
244             */
245            handleConfirmMessage: function(message) {
246                var _event = jQuery.Event('ajaxConfirmMessage');
247
248                _event.promise = $.Deferred();
249                if ($(window).triggerHandler(_event, [message]) !== undefined) {
250                    _event.promise.done(function() {
251                        options.confirm = null;
252                        new Request(element, handler, options);
253                    })
254                    return false;
255                }
256
257                if (_event.isDefaultPrevented()) return;
258                if (message) return confirm(message);
259            },
260
261            /*
262             * Custom function, display an error message to the user
263             */
264            handleErrorMessage: function(message) {
265                var _event = jQuery.Event('ajaxErrorMessage');
266                $(window).trigger(_event, [message]);
267                if (_event.isDefaultPrevented()) return;
268                if (message) alert(message);
269            },
270
271            /*
272             * Custom function, focus fields with errors
273             */
274            handleValidationMessage: function(message, fields) {
275                $triggerEl.trigger('ajaxValidation', [context, message, fields]);
276
277                var isFirstInvalidField = true;
278                $.each(fields, function focusErrorField(fieldName, fieldMessages) {
279                    var fieldNameRaw = fieldName.replace(/\.(\w+)/g, '[$1]'),
280                        fieldNameArr = ('.'+fieldName).replace(/\.(\w+)/g, '[$1]');
281
282                    var fieldNameOptions = [
283                        '[name="'+fieldNameRaw+'"]',
284                        '[name="'+fieldNameRaw+'[]"]',
285                        '[name$="'+fieldNameArr+'"]',
286                        '[name$="'+fieldNameArr+'[]"]'
287                    ];
288
289                    var fieldElement = $form.find(fieldNameOptions.join(', ')).filter(':enabled').first();
290                    if (fieldElement.length > 0) {
291                        var _event = jQuery.Event('ajaxInvalidField');
292                        $(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField]);
293
294                        if (isFirstInvalidField) {
295                            if (!_event.isDefaultPrevented()) fieldElement.focus();
296                            isFirstInvalidField = false;
297                        }
298                    }
299                });
300            },
301
302            /*
303             * Custom function, display a flash message to the user
304             */
305            handleFlashMessage: function(message, type) {},
306
307            /*
308             * Custom function, redirect the browser to another location
309             */
310            handleRedirectResponse: function(url) {
311                window.location.assign(url);
312                $el.trigger('ajaxRedirect');
313            },
314
315            /*
316             * Custom function, handle any application specific response values
317             * Using a promisary object here in case injected assets need time to load
318             */
319            handleUpdateResponse: function(data, textStatus, jqXHR) {
320
321                /*
322                 * Update partials and finish request
323                 */
324                var updatePromise = $.Deferred().done(function() {
325                    for (var partial in data) {
326                        /*
327                         * If a partial has been supplied on the client side that matches the server supplied key, look up
328                         * it's selector and use that. If not, we assume it is an explicit selector reference.
329                         */
330                        var selector = (options.update[partial]) ? options.update[partial] : partial;
331                        if ($.type(selector) == 'string' && selector.charAt(0) == '!') {
332                            $(selector.substring(1)).replaceWith(data[partial]).trigger('aja
332xUpdate', [context, data, textStatus, jqXHR]);
333                        }
334                        else if ($.type(selector) == 'string' && selector.charAt(0) == '@') {
335                            $(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]);
336                        }
337                        else if ($.type(selector) == 'string' && selector.charAt(0) == '^') {
338                            $(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]);
339                        }
340                        else {
341                            $(selector).trigger('ajaxBeforeReplace');
342                            $(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]);
343                        }
344                    }
345
346                    /*
347                     * Wait for .html() method to finish rendering from partial updates
348                     */
349                    setTimeout(function() {
350                        $(window)
351                            .trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR])
352                            .trigger('resize');
353                    }, 0);
354                })
355
356                /*
357                 * Handle redirect
358                 */
359                if (data['X_OCTOBER_REDIRECT']) {
360                    options.redirect = data['X_OCTOBER_REDIRECT'];
361                    isRedirect = true;
362                }
363
364                if (isRedirect) {
365                    requestOptions.handleRedirectResponse(options.redirect);
366                }
367
368                /*
369                 * Handle validation
370                 */
371                if (data['X_OCTOBER_ERROR_FIELDS']) {
372                    requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS']);
373                }
374
375                /*
376                 * Handle asset injection
377                 */
378                 if (data['X_OCTOBER_ASSETS']) {
379                    assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise));
380                 }
381                 else {
382                    updatePromise.resolve();
383                }
384
385                return updatePromise;
386            }
387        }
388
389        if (useFiles) {
390            requestOptions.processData = requestOptions.contentType = false;
391        }
392
393        /*
394         * Allow default business logic to be called from user functions
395         */
396        context.success = requestOptions.success;
397        context.error = requestOptions.error;
398        context.complete = requestOptions.complete;
399        requestOptions = $.extend(requestOptions, options);
400        requestOptions.data = requestData;
401
402        /*
403         * Initiate request
404         */
405        if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) {
406            return;
407        }
408
409        if (loading) loading.show();
410        $(window).trigger('ajaxBeforeSend', [context]);
411        $el.trigger('ajaxPromise', [context]);
412
413        return $.ajax(requestOptions)
414            .fail(function(jqXHR, textStatus, errorThrown) {
415                if (!isRedirect) {
416                    $el.trigger('ajaxFail', [context, textStatus, jqXHR]);
417                }
418                if (loading) loading.hide();
419            })
420            .done(function(data, textStatus, jqXHR) {
421                if (!isRedirect) {
422                    $el.trigger('ajaxDone', [context, data, textStatus, jqXHR]);
423                }
424                if (loading) loading.hide();
425            })
426            .always(function(dataOrXhr, textStatus, xhrOrError) {
427                $el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError]);
428            });
429    }
430
431    Request.DEFAULTS = {
432        update: {},
433        type : 'POST',
434        beforeUpdate: function(data, textStatus, jqXHR) {},
435        afterUpdate: function(data, textStatus, jqXHR) {},
436        evalBeforeUpdate: null,
437        evalSuccess: null,
438        evalError: null,
439        evalComplete: null,
440        ajaxGlobal: false
441    }
442
443    /*
444     * Internal function, build a string of partials and their update elements.
445     */
446    Request.prototype.extractPartials = function(update) {
447        var result = [];
448
449        for (var partial in update) {
450            result.push(partial);
451        }
452
453        return result.join('&');
454    }
455
456    // REQUEST PLUGIN DEFINITION
457    // ============================
458
459    var old = $.fn.request;
460
461    $.fn.request = function(handler, option) {
462        var args = arguments;
463
464        var $this = $(this).first();
465        var data  = {
466            evalBeforeUpdate: $this.data('request-before-update'),
467            evalSuccess: $this.data('request-success'),
468            evalError: $this.data('request-error'),
469            evalComplete: $this.data('request-complete'),
470            ajaxGlobal: $this.data('request-ajax-global'),
471            confirm: $this.data('request-confirm'),
472            redirect: $this.data('request-redirect'),
473            loading: $this.data('request-loading'),
474            flash: $this.data('request-flash'),
475            files: $this.data('request-files'),
476            browserValidate: $this.data('browser-validate'),
477            form: $this.data('request-form'),
478            url: $this.data('request-url'),
479            update: paramToObj('data-request-update', $this.data('request-update')),
480            data: paramToObj('data-request-data', $this.data('request-data'))
481        };
482        if (!handler) handler = $this.data('request');
483        var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option);
484        return new Request($this, handler, options);
485    }
486
487    $.fn.request.Constructor = Request;
488
489    $.request = function(handler, option) {
490        return $(document).request(handler, option);
491    }
492
493    // REQUEST NO CONFLICT
494    // =================
495
496    $.fn.request.noConflict = function() {
497        $.fn.request = old;
498        return this;
499    }
500
501    // REQUEST DATA-API
502    // ==============
503
504    function paramToObj(name, value) {
505        if (value === undefined) value = '';
506        if (typeof value == 'object') return value;
507
508        try {
509            return ocJSON("{" + value + "}");
510        }
511        catch (e) {
512            throw new Error('Error parsing the '+name+' attribute value. '+e);
513        }
514    }
515
516    function getXSRFToken() {
517        var cookieValue = null;
518        if (document.cookie && document.cookie != '') {
519            var cookies = document.cookie.split(';');
520            for (var i = 0; i < cookies.length; i++) {
521                var cookie = jQuery.trim(cookies[i]);
522                if (cookie.substring(0, 11) == ('XSRF-TOKEN' + '=')) {
523                    cookieValue = decodeURIComponent(cookie.substring(11));
524                    break;
525                }
526            }
527        }
528        return cookieValue;
529    }
530
531    $(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]', function documentOnChange() {
532        $(this).request();
533    });
534
535    $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function documentOnClick(e) {
536        e.preventDefault();
537
538        $(this).request();
539
540        if ($(this).is('[type=submit]')) {
541            return false;
542        }
543    });
544
545    $(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function documentOnKeydown(e) {
546        if (e.key === 'Enter') {
547            if (this.dataTrackInputTimer !== undefined) {
548                window.clearTimeout(this.dataTrackInputTimer);
549            }
550
551            $(this).request();
552            return false;
553        }
554    });
555
556    $(document).on('input', 'input[data-request][data-track-input]', function documentOnKeyup(e) {
557        var
558            $el = $(this),
559            lastValue = $el.data('oc.lastvalue');
560
561        if (!$el.is('[type=email],[type=number],[type=password],[type=search],[type=text]')) {
562            return;
563        }
564
565        if (lastValue !== undefined && lastValue == this.value) {
566            return;
567        }
568
569        $el.data('oc.lastvalue', this.value);
570
571        if (this.dataTrackInputTimer !== undefined) {
572            window.clearTimeout(this.dataTrackInputTimer);
573        }
574
575        var interval = $(this).data('track-input');
576        if (!interval) {
577            interval = 300;
578        }
579
580        var self = this;
581        this.dataTrackInputTimer = window.setTimeout(function() {
582            if (self.lastDataTrackInputRequest) {
583                self.lastDataTrackInputRequest.abort();
584            }
585            self.lastDataTrackInputRequest = $(self).request();
586        }, interval);
587    });
588
589    $(document).on('submit', '[data-request]', function documentOnSubmit() {
590        $(this).request();
591        return false;
592    });
593
594    window.onbeforeunload = function documentOnBeforeUnload() {
595        window.ocUnloading = true;
596    }
597
598    /*
599     * Invent our own event that unifies document.ready with window.ajaxUpdateComplete
600     *
601     * $(document).render(function() { })
602     * $(document).on('render', function() { })
603     */
604
605    $(document).ready(function triggerRenderOnReady() {
606        $(document).trigger('render');
607    });
608
609    $(window).on('ajaxUpdateComplete', function triggerRenderOnAjaxUpdateComplete() {
610        $(document).trigger('render');
611    });
612
613    $.fn.render = function(callback) {
614        $(document).on('render', callback);
615    };
616
617}(window.jQuery);
618
619/*
620 * October CMS JSON Parser
621 */
622+function(window) { "use strict";
623    function parseKey(str, pos, quote) {
624        var key = "";
625        for (var i = pos; i < str.length; i++) {
626            if (quote && quote === str[i]) {
627                return key;
628            } else if (!quote && (str[i] === " " || str[i] === ":")) {
629                return key;
630            }
631
632            key += str[i];
633
634            if (str[i] === "\\" && i + 1 < str.length) {
635                key += str[i + 1];
636                i++;
637            }
638        }
639        throw new Error("Broken JSON syntax near " + key);
640    }
641
642    function getBody(str, pos) {
643        // parse string body
644        if (str[pos] === "\"" || str[pos] === "'") {
645            var body = str[pos];
646            for (var i = pos + 1; i < str.length; i++) {
647                if (str[i] === "\\") {
648                    body += str[i];
649                    if (i + 1 < str.length) body += str[i + 1];
650                    i++;
651                } else if (str[i] === str[pos]) {
652                    body += str[pos];
653                    return {
654                        originLength: body.length,
655                        body: body
656                    };
657                } else body += str[i];
658            }
659            throw new Error("Broken JSON string body near " + body);
660        }
661
662        // parse true / false
663        if (str[pos] === "t") {
664            if (str.indexOf("true", pos) === pos) {
665                return {
666                    originLength: "true".length,
667                    body: "true"
668                };
669            }
670            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
671        }
672        if (str[pos] === "f") {
673            if (str.indexOf("f", pos) === pos) {
674                return {
675                    originLength: "false".length,
676                    body: "false"
677                };
678            }
679            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
680        }
681
682        // parse null
683        if (str[pos] === "n") {
684            if (str.indexOf("null", pos) === pos) {
685                return {
686                    originLength: "null".length,
687                    body: "null"
688                };
689            }
690            throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10));
691        }
692
693        // parse number
694        if (str[pos] === "-" || str[pos] === "+" || str[pos] === "." || (str[pos] >= "0" && str[pos] <= "9")) {
695            var body = "";
696            for (var i = pos; i < str.length; i++) {
697                if (str[i] === "-" || str[i] === "+" || str[i] === "." || (str[i] >= "0" && str[i] <= "9")) {
698                    body += str[i];
699                } else {
700                    return {
701                        originLength: body.length,
702                        body: body
703                    };
704                }
705            }
706            throw new Error("Broken JSON number body near " + body);
707        }
708
709        // parse object
710        if (str[pos] === "{" || str[pos] === "[") {
711            var stack = [str[pos]];
712            var body = str[pos];
713            for (var i = pos + 1; i < str.length; i++) {
714                body += str[i];
715                if (str[i] === "\\") {
716                    if (i + 1 < str.length) body += str[i + 1];
717                    i++;
718                } else if (str[i] === "\"") {
719                    if (stack[stack.length - 1] === "\"") {
720                        stack.pop();
721                    } else if (stack[stack.length - 1] !== "'") {
722                        stack.push(str[i]);
723                    }
724                } else if (str[i] === "'") {
725                    if (stack[stack.length - 1] === "'") {
726                        stack.pop();
727                    } else if (stack[stack.length - 1] !== "\"") {
728                        stack.push(str[i]);
729                    }
730                } else if (stack[stack.length - 1] !== "\"" && stack[stack.length - 1] !== "'") {
731                    if (str[i] === "{") {
732                        stack.push("{");
733                    } else if (str[i] === "}") {
734                        if (stack[stack.length - 1] === "{") {
735                            stack.pop();
736                        } else {
737                            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
738                        }
739                    } else if (str[i] === "[") {
740                        stack.push("[");
741                    } else if (str[i] === "]") {
742                        if (stack[stack.length - 1] === "[") {
743                            stack.pop();
744                        } else {
745                            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
746                        }
747                    }
748                }
749                if (!stack.length) {
750                    return {
751                        originLength: i - pos,
752                        body: body
753                    };
754                }
755            }
756            throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body);
757        }
758        throw new Error("Broken JSON body near " + str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50));
759    }
760
761    function canBeKeyHead(ch) {
762        if (ch[0] === "\\") return false;
763        if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') return true;
764        if (ch[0] >= '0' && ch[0] <= '9') return true;
765        if (ch[0] === '$') return true;
766        if (ch.charCodeAt(0) > 255) return true;
767        return false;
768    }
769
770    function isBlankChar(ch) {
771        return ch === " " || ch === "\n" || ch === "\t";
772    }
773
774    function parse(str) {
775        str = str.trim();
776        if (!str.length) throw new Error("Broken JSON object.");
777        var result = "";
778
779        /*
780         * the mistake ','
781         */
782        while (str && str[0] === ",") {
783            str = str.substr(1);
784        }
785
786        /*
787         * string
788         */
789        if (str[0] === "\"" || str[0] === "'") {
790            if (str[str.length - 1] !== str[0]) {
791                throw new Error("Invalid string JSON object.");
792            }
793
794            var body = "\"";
795            for (var i = 1; i < str.length; i++) {
796                if (str[i] === "\\") {
797                    if (str[i + 1] === "'") {
798                        body += str[i + 1]
799                    } else {
800                        body += str[i];
801                        body += str[i + 1];
802                    }
803                    i++;
804                } else if (str[i] === str[0]) {
805                    body += "\"";
806                    return body
807                } else if (str[i] === "\"") {
808                    body += "\\\""
809                } else body += str[i];
810            }
811            throw new Error("Invalid string JSON object.");
812        }
813
814        /*
815         * boolean
816         */
817        if (str === "true" || str === "false") {
818            return str;
819        }
820
821        /*
822         * null
823         */
824        if (str === "null") {
825            return "null";
826        }
827
828        /*
829         * number
830         */
831        var num = parseFloat(str);
832        if (!isNaN(num)) {
833            return num.toString();
834        }
835
836        /*
837         * object
838         */
839        if (str[0] === "{") {
840            var type = "needKey";
841            var result = "{";
842
843            for (var i = 1; i < str.length; i++) {
844                if (isBlankChar(str[i])) {
845                    continue;
846                } else if (type === "needKey" && (str[i] === "\"" || str[i] === "'")) {
847                    var key = parseKey(str, i + 1, str[i]);
848                    result += "\"" + key + "\"";
849                    i += key.length;
850                    i += 1;
851                    type = "afterKey";
852                } else if (type === "needKey" && canBeKeyHead(str[i])) {
853                    var key = parseKey(str, i);
854                    result += "\"";
855                    result += key;
856                    result += "\"";
857                    i += key.length - 1;
858                    type = "afterKey";
859                } else if (type === "afterKey" && str[i] === ":") {
860                    result += ":";
861                    type = ":";
862                } else if (type === ":") {
863                    var body = getBody(str, i);
864
865                    i = i + body.originLength - 1;
866                    result += parse(body.body);
867
868                    type = "afterBody";
869                } else if (type === "afterBody" || type === "needKey") {
870                    var last = i;
871                    while (str[last] === "," || isBlankChar(str[last])) {
872                        last++;
873                    }
874                    if (str[last] === "}" && last === str.length - 1) {
875                        while (result[result.length - 1] === ",") {
876                            result = result.substr(0, result.length - 1);
877                        }
878                        result += "}";
879                        return result;
880                    } else if (last !== i && result !== "{") {
881                        result += ",";
882                        type = "needKey";
883                        i = last - 1;
884                    }
885                }
886            }
887            throw new Error("Broken JSON object near " + result);
888        }
889
890        /*
891         * array
892         */
893        if (str[0] === "[") {
894            var result = "[";
895            var type = "needBody";
896            for (var i = 1; i < str.length; i++) {
897                if (" " === str[i] || "\n" === str[i] || "\t" === str[i]) {
898                    continue;
899                } else if (type === "needBody") {
900                    if (str[i] === ",") {
901                        result += "null,";
902                        continue;
903                    }
904                    if (str[i] === "]" && i === str.length - 1) {
905                        if (result[result.length - 1] === ",") result = result.substr(0, result.length - 1);
906                        result += "]";
907                        return result;
908                    }
909
910                    var body = getBody(str, i);
911
912                    i = i + body.originLength - 1;
913                    result += parse(body.body);
914
915                    type = "afterBody";
916                } else if (type === "afterBody") {
917                    if (str[i] === ",") {
918                        result += ",";
919                        type = "needBody";
920
921                        // deal with mistake ","
922                        while (str[i + 1] === "," || isBlankChar(str[i + 1])) {
923                            if (str[i + 1] === ",") result += "null,";
924                            i++;
925                        }
926                    } else if (str[i] === "]" && i === str.length - 1) {
927                        result += "]";
928                        return result;
929                    }
930                }
931            }
932            throw new Error("Broken JSON array near " + result);
933        }
934    }
935
936    // Global function
937    window.ocJSON = function(json) {
938        var jsonString = parse(json);
939        return JSON.parse(jsonString);
940    };
941
942}(window);

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.