PageSourceSearch

https://applyindex.com/wp-content/themes/workreap-child/js/cv-autofill.js?ver=1791047159

js applyindex.com collected 2026-10-04 23:01:02 UTC 18,792 bytes, 472 lines download raw bytes

1/**
2 * CV autofill: the §6 Education confirmation pop-ups.
3 *
4 * Phase 2B of the developer brief.
5 *
6 * Nothing the CV produced has been written to the profile when this runs.
7 * The server parked it; one pop-up per extracted degree collects the four
8 * fields that must be exactly right, and only the final Confirm triggers the
9 * single write (action: cv_ai_confirm_autofill).
10 *
11 * The three picker boxes behave like the ones in the Education form: they
12 * search the same lists through get_taxonomy_lists, and offer "Create it" at
13 * the bottom through create_custom_select.
14 */
15(function ($) {
16    'use strict';
17
18    /*
19     * Which list each box searches, and the "type" create_custom_select wants
20     * in order to create into that same list. These strings are not arbitrary
21     * -- they are the keys of the $taxonomies map in create_custom_select(),
22     * and 'service[...]' are the Education form's own field names, so the
23     * Create-it behaviour here is identical to the Education form's.
24     */
25    var BOXES = {
26        degree: {
27            taxonomy: 'response_time',
28            createType: 'degree',
29            label: 'Degree Type',
30            idKey: 'degree_id',
31            labelKey: 'degree_label'
32        },
33        university: {
34            taxonomy: 'delivery',
35            createType: 'service[delivery_time]',
36            label: 'University/Institute Name',
37            idKey: 'university_id',
38            labelKey: 'university_label'
39        },
40        department: {
41            taxonomy: 'project_experience',
42            createType: 'service[title]',
43            label: 'Department Name',
44            idKey: 'department_id',
45            labelKey: 'department_label'
46        }
47    };
48
49    var CvAutofill = {
50
51        rows: [],
52        index: 0,
53        busy: false,
54
55        /**
56         * Ask the server whether a CV is waiting, and start the sequence.
57         *
58         * @param {function} [onNothing] called when there was nothing pending
59         */
60        start: function (onNothing) {
61            var self = this;
62
63            $.ajax({
64                type: 'POST',
65                url: scripts_vars.ajaxurl,
66                dataType: 'json',
67                data: {
68                    action: 'cv_ai_pending_popups',
69                    security: scripts_vars.ajax_nonce
70                },
71                success: function (response) {
72                    if (!response || response.type !== 'success') {
73                        return;
74                    }
75                    if (!response.rows || !response.rows.length) {
76                        /*
77                         * A CV with no degrees in it still has five tabs worth
78                         * of data parked, so confirm with an empty list rather
79                         * than leave it stranded.
80                         */
81                        if (response.context) {
82                            self.rows = [];
83                            self.submit();
84                        } else if (typeof onNothing === 'function') {
85                            onNothing();
86                        }
87                        return;
88                    }
89                    self.rows = response.rows;
90                    self.index = 0;
91                    self.render();
92                }
93            });
94        },
95
96        /** Draw the pop-up for the current degree. */
97        render: function () {
98            var row = this.rows[this.index];
99            var total = this.rows.length;
100            var step = this.index + 1;
101
102            var heading = total > 1
103                ? 'Confirm your education (' + step + ' of ' + total + ')'
104                : 'Confirm your education';
105
106            var said = [];
107            if (row.raw_degree) { said.push(this.esc(row.raw_degree)); }
108            if (row.raw_university) { said.push(this.esc(row.raw_university)); }
109
110            var html =
111                '<div class="cv-confirm-overlay">' +
112                  '<div class="cv-confirm-box" role="dialog" aria-modal="true">' +
113                    '<h3 class="cv-confirm-title">' + heading + '</h3>' +
114                    (said.length
115                        ? '<p class="cv-confirm-said">From your CV: <em>' + said.join(' &mdash; ') + '</em></p>'
116                        : '') +
117                    '<p class="cv-confirm-help">Please check these four fields. We only filled in what we could verify &mdash; anything left empty needs you.</p>' +
118                    '<div class="cv-confirm-fields">' +
119                      this.pickerHtml('degree', row) +
120                      this.gpaHtml(row) +
121                      this.pickerHtml('university', row) +
122                      this.pickerHtml('department', row) +
123                    '</div>' +
124                    '<p class="cv-confirm-error" style="display:none;"></p>' +
125                    '<div class="cv-confirm-actions">' +
126                      '<button type="button" class="wt-btn cv-confirm-go">' +
127                        (step === total ? 'Confirm and fill my profile' : 'Confirm') +
128                      '</button>' +
129                    '</div>' +
130                  '</div>' +
131                '</div>';
132
133            $('.cv-confirm-overlay').remove();
134            $('body').append(html).addClass('cv-confirm-open');
135        },
136
137        /** One autocomplete box with a hidden term id behind it. */
138        pickerHtml: function (key, row) {
139            var box = BOXES[key];
140            var id = row[box.idKey] || 0;
141            var label = row[box.labelKey] || '';
142
143            return '' +
144                '<div class="cv-confirm-field" data-box="' + key + '">' +
145                  '<label>' + box.label + '</label>' +
146                  '<input type="hidden" class="cv-term-id" value="' + (id ? parseInt(id, 10) : 0) + '">' +
147                  '<input type="text" class="form-control cv-term-text" autocomplete="off"' +
148                        ' value="' + this.esc(label) + '"' +
149                        ' placeholder="Type to search, at least 3 letters">' +
150                  '<ul class="cv-term-results" style="display:none;"></ul>' +
151                '</div>';
152        },
153
154        /**
155         * The same control as the Create/Edit Education page: a picker, never
156         * a free-text box, so a GPA can only ever be N/A or a whole number one
157         * unit apart. Highest first, with N/A kept at the top of the list.
158         */
159        gpaHtml: function (row) {
160            var current = $.trim(String(row.gpa == null ? '' : row.gpa));
161            var selectedNumber = (current !== '' && current.toUpperCase() !== 'N/A')
162                ? Math.round(parseFloat(current)) : null;
163
164            var html =
165                '<div class="cv-confirm-field" data-box="gpa">' +
166                  '<label>Overall GPA (%)</label>' +
167                  '<select class="form-control cv-gpa">' +
168                    '<option value="">Overall GPA (%)</option>' +
169                    '<option value="N/A"' +
170                      (current.toUpperCase() === 'N/A' ? ' selected' : '') + '>N/A</option>';
171
172            for (var n = 100; n >= 50; n--) {
173                html += '<option value="' + n + '"' +
174                        (selectedNumber === n ? ' selected' : '') +
175                        '>' + n + '</option>';
176            }
177
178            return html + '</select></div>';
179        },
180
181        /** Read the pop-up back into the row, refusing anything incomplete. */
182        collect: function () {
183            var $box = $('.cv-confirm-box');
184            var row = this.rows[this.index];
185            var missing = [];
186
187            $.each(BOXES, function (key, box) {
188                var $f = $box.find('.cv-confirm-field[data-box="' + key + '"]');
189                var id = parseInt($f.find('.cv-term-id').val(), 10) || 0;
190                var text = $.trim($f.find('.cv-term-text').val());
191
192                // A typed name that was never picked from the list has no id,
193                // and an id with the text cleared is stale. Both are refused.
194                if (!id || !text) {
195                    missing.push(box.label);
196                    return;
197                }
198                row[box.idKey] = id;
199                row[box.labelKey] = text;
200            });
201
202            // The picker only offers N/A and whole numbers, so the one thing
203            // left to refuse is the placeholder.
204            var gpa = $.trim($box.find('.cv-gpa').val() || '');
205            if (gpa === '') {
206                missing.push('Overall GPA (%)');
207            } else {
208                row.gpa = gpa;
209            }
210
211            if (missing.length) {
212                $box.find('.cv-confirm-error')
213                    .text('Please complete: ' + missing.join(', ') + '.')
214                    .show();
215                return false;
216            }
217
218            return true;
219        },
220
221        /** Confirm pressed: next degree, or write everything. */
222        next: function () {
223            if (!this.collect()) {
224                return;
225            }
226            if (this.index + 1 < this.rows.length) {
227                this.index += 1;
228                this.render();
229                return;
230            }
231            this.submit();
232        },
233
234        /** The single write. */
235        submit: function () {
236            var self = this;
237            if (this.busy) {
238                return;
239            }
240            this.busy = true;
241
242            $('.cv-confirm-box').find('.cv-confirm-go')
243                .prop('disabled', true).text('Saving...');
244
245            $.ajax({
246                type: 'POST',
247                url: scripts_vars.ajaxurl,
248                dataType: 'json',
249                data: {
250                    action: 'cv_ai_confirm_autofill',
251                    security: scripts_vars.ajax_nonce,
252                    rows: JSON.stringify(self.rows)
253                },
254                success: function (response) {
255                    self.busy = false;
256                    self.close();
257
258                    if (response && response.type === 'success') {
259                        if (typeof $.sticky === 'function') {
260                            $.sticky(response.message, {
261                                classList: 'success', speed: 200,
262                                autoclose: scripts_vars.sticky_speed
263                            });
264                        }
265                        /*
266                         * The tabs on screen were rendered before any of this
267                         * existed, so they still look empty. Every route out
268                         * of the What's Next pop-up therefore navigates or
269                         * reloads -- which also stops a later Save posting
270                         * that stale empty form back over what was written.
271                         */
272                        if (response.whats_next && response.whats_next.lines
273                            && response.whats_next.lines.length) {
274                            self.whatsNext(response.whats_next);
275                        } else {
276                            setTimeout(function () { window.location.reload(); }, 1200);
277                        }
278                        return;
279                    }
280
281                    if (typeof $.sticky === 'function') {
282                        $.sticky(
283                            (response && response.message) ? response.message
284                                : 'We could not save your CV data. Please fill your profile manually.',
285                            { classList: 'important', speed: 200, autoclose: scripts_vars.sticky_speed }
286                        );
287                    }
288                },
289                error: function () {
290                    self.busy = false;
291                    self.close();
292                    if (typeof $.sticky === 'function') {
293                        $.sticky('We could not save your CV data. Please fill your profile manually.',
294                            { classList: 'important', speed: 200, autoclose: scripts_vars.sticky_speed });
295                    }
296                }
297            });
298        },
299
300        /**
301         * The final "What's Next?" pop-up (section 9).
302         *
303         * Which lines appear depends on the degree the student is applying
304         * for; the server has already applied the priority rule. A line with
305         * no url is informational (the a-index), everything else navigates.
306         *
307         * @param {{degree: string, lines: Array}} next
308         */
309        whatsNext: function (next) {
310            var html =
311                '<div class="cv-confirm-overlay cv-next-overlay">' +
312                  '<div class="cv-confirm-box cv-next-box" role="dialog" aria-modal="true">' +
313                    '<h3 class="cv-confirm-title">What&rsquo;s Next?</h3>' +
314                    '<p class="cv-confirm-help">Your profile has been filled in from your CV.</p>' +
315                    '<ul class="cv-next-list">';
316
317            $.each(next.lines, function (i, line) {
318                var icon = '<span class="cv-next-icon">' + (line.icon || '') + '</span>';
319
320                if (!line.url) {
321                    html += '<li class="cv-next-info">' + icon +
322                            '<span>' + CvAutofill.esc(line.text) + '</span></li>';
323                    return;
324                }
325                html += '<li class="cv-next-item">' +
326                            '<a href="' + CvAutofill.esc(line.url) + '">' + icon +
327                              '<span>' + CvAutofill.esc(line.text) + '</span>' +
328                            '</a>' +
329                            '<a class="wt-btn cv-next-go" href="' + CvAutofill.esc(line.url) + '">Go</a>' +
330                        '</li>';
331            });
332
333            html += '</ul></div></div>';
334
335            $('.cv-confirm-overlay').remove();
336            $('body').append(html).addClass('cv-confirm-open');
337        },
338
339        close: function () {
340            $('.cv-confirm-overlay').remove();
341            $('body').removeClass('cv-confirm-open');
342        },
343
344        esc: function (value) {
345            return $('<div>').text(value == null ? '' : value).html();
346        }
347    };
348
349    window.CvAutofill = CvAutofill;
350
351    /* ------------------------------------------------------------------
352     * The autocomplete boxes
353     * --------------------------------------------------------------- */
354
355    var searchTimer = null;
356    var searchXhr = null;
357
358    $(document).on('input', '.cv-confirm-box .cv-term-text', function () {
359        var $input = $(this);
360        var $field = $input.closest('.cv-confirm-field');
361        var $list = $field.find('.cv-term-results');
362        var box = BOXES[$field.data('box')];
363        var value = $.trim($input.val());
364
365        // Typing invalidates whatever was picked before.
366        $field.find('.cv-term-id').val(0);
367
368        if (searchTimer) { clearTimeout(searchTimer); }
369        if (value.length < 3) {
370            $list.hide().empty();
371            return;
372        }
373
374        searchTimer = setTimeout(function () {
375            if (searchXhr) { searchXhr.abort(); }
376            searchXhr = $.ajax({
377                type: 'POST',
378                url: scripts_vars.ajaxurl,
379                dataType: 'json',
380                data: {
381                    action: 'get_taxonomy_lists',
382                    security: scripts_vars.ajax_nonce,
383                    taxonomy: box.taxonomy,
384                    value: value
385                },
386                success: function (response) {
387                    var items = (response && response.terms) ? response.terms : [];
388                    var html = '';
389
390                    $.each(items.slice(0, 20), function (i, term) {
391                        html += '<li class="cv-term-hit" data-id="' + parseInt(term.term_id, 10) + '">' +
392                                CvAutofill.esc(term.name) + '</li>';
393                    });
394
395                    // "Create it", exactly as the Education form offers it.
396                    html += '<li class="cv-term-create">' +
397                            'Not in the list? <strong>Create it</strong>: &ldquo;' +
398                            CvAutofill.esc(value) + '&rdquo;</li>';
399
400                    $list.html(html).show();
401                }
402            });
403        }, 250);
404    });
405
406    $(document).on('click', '.cv-term-hit', function () {
407        var $hit = $(this);
408        var $field = $hit.closest('.cv-confirm-field');
409        $field.find('.cv-term-id').val($hit.data('id'));
410        $field.find('.cv-term-text').val($hit.text());
411        $field.find('.cv-term-results').hide().empty();
412        $('.cv-confirm-error').hide();
413    });
414
415    $(document).on('click', '.cv-term-create', function () {
416        var $field = $(this).closest('.cv-confirm-field');
417        var box = BOXES[$field.data('box')];
418        var name = $.trim($field.find('.cv-term-text').val());
419        var $list = $field.find('.cv-term-results');
420
421        if (name.length < 3) {
422            return;
423        }
424        $list.html('<li class="cv-term-busy">Creating&hellip;</li>');
425
426        $.ajax({
427            type: 'POST',
428            url: scripts_vars.ajaxurl,
429            dataType: 'json',
430            data: {
431                action: 'create_custom_select',
432                security: scripts_vars.ajax_nonce,
433                name: name,
434                type: box.createType
435            },
436            success: function (response) {
437                if (response && response.type === 'success' && response.term_id) {
438                    $field.find('.cv-term-id').val(response.term_id);
439                    $field.find('.cv-term-text').val(response.name);
440                    $list.hide().empty();
441                    $('.cv-confirm-error').hide();
442                    return;
443                }
444                $list.html('<li class="cv-term-busy">' +
445                    CvAutofill.esc((response && response.message) ? response.message : 'Could not create that.') +
446                    '</li>');
447            },
448            error: function () {
449                $list.html('<li class="cv-term-busy">Could not create that.</li>');
450            }
451        });
452    });
453
454    $(document).on('click', '.cv-confirm-go', function () {
455        CvAutofill.next();
456    });
457
458    /* ------------------------------------------------------------------
459     * Entry point: a CV parked during registration is confirmed on the
460     * first dashboard load. The Personal Details path calls start()
461     * itself, straight after extraction.
462     * --------------------------------------------------------------- */
463    $(function () {
464        if (typeof scripts_vars === 'undefined' ||
465            typeof cv_autofill_vars === 'undefined' ||
466            !cv_autofill_vars.pending) {
467            return;
468        }
469        CvAutofill.start();
470    });
471
472})(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.