// Do-nothing function for old IE. if (! window.console) { window.console = {log: function(){} }; } // Document global for fallback initialization. var flipping_cards; var flipping_cards_part2; // ============================================================================= // Isolate namespace. qcard_ = {}; var qcardf = function () { // ============================================================================= var qname = 'qcard_'; // Debug settings. var debug = []; debug.push (false); // 0 - general. debug.push (false); // 1 - process_card_input (). debug.push (false); // 2 - answer (card back) html. debug.push (false); // 3 - old/new html dump. debug.push (false); // 4 - card tags/topics. debug.push (false); // 5 - "next" buttons, element objects. debug.push (false); // 6 - [textentry] w/ required input. debug.push (false); // 7 - Enter -> click. debug.push (false); // 8 - unused. debug.push (false); // 9 - [hangman]. var $ = jQuery; // Private data, but global to this qcard instance. var q = this; var qqc; q.processing_complete_b = false; var content; var default_use_dict; var default_use_terms; var hint_timeout_sec; var post_id; var errmsgs = []; var n_decks = 0; var no_intro_b = []; var deck_id; var deckdata = []; var next_button_active_b = false; var textentry_i_deck; var loaded_metaphone_js_b = false; // Object (singular and plural) of arrays of term-metaphone pairs. // Constant across quizzes. var default_textentry_terms_metaphones; // (deckdata[i_deck].textentry_terms_metaphones are quiz-specific terms given // with [terms]...[/terms].) // These vary with quiz, and are set up anew for each [textentry] question. var current_card_textentry_terms_metaphones = {}; var textentry_answers = {}; var textentry_answer_metaphones = {}; var textentry_matches = {}; var lc_textentry_matches = {}; var Tcheck_answer_message; var show_hint_timeout = {}; var qrecord_b = false; var q_and_a_text = ''; var scroll_detect_i_deck; var panel_exit_mobile_open_b = false; var panel_exit_mobile_just_closed_b; var non_mobile_scrollLeft; var non_mobile_scrollTop; // ----------------------------------------------------------------------------- $(document).ready (function () { q.qdeck_init (); }); this.qdeck_init = function () { qqc = qwiz_qcards_common; // The identifier -- including qualifiers like "#" -- of the page content (that // perhaps contains inline flashcard decks) on WordPress. Default // set in qwiz-online-quizzes-wp-plugin.php: div.entry-content, div.post-entry, // div.container. Apparently themes can change this; these have come up so far. // Body default for stand-alone use. content = qqc.get_qwiz_param ('content', 'body'); default_use_dict = qqc.get_qwiz_param ('use_dict', 'true'); default_use_terms = qqc.get_qwiz_param ('use_terms', 'true'); hint_timeout_sec = qqc.get_qwiz_param ('hint_timeout_sec', 20); post_id = qqc.get_qwiz_param ('post_id', 0); Tcheck_answer_message = T ("Need help? Try the 'hint' button"); process_html (); // Error messages, if any. if (errmsgs.length) { alert (Tplural ('Error found', 'Errors found', errmsgs.length) + ':\n\n' + errmsgs.join ('\n')); } if (n_decks) { // If on mobile, show expand-to-full-screen icon and clickable target on // all decks. if (qqc.is_mobile ()) { $ ('.go-mobile-qdeck').show (); } for (var i_deck=0; i_deck)*\s*\[!+\][\s\S]*?\[\/!+\]\s*(<.+?>)*\s*$/m) == 0) { $ (this).remove (); } }); // Look for [qdeck] and [/qdeck] that are only thing inside parents (e.g., //

[qdeck]

). Replace with "unwrapped" content if so. $ ('p:contains("qdeck"), :header:contains("qdeck")').each (function () { var tag_htm = $ (this).html (); if (tag_htm.search (/\s*\[\/{0,1}qdeck[^\]]*\]\s*/m) == 0) { $ (this).replaceWith (tag_htm); } }); // We're either going to deal with HTML (stand-alone version) or divs (added // by WordPress content filter). The divs allow us to replace content // specific to qwiz/qdeck -- avoid clobbering any events bound to remaining // html by other plugins. See if there are such divs. WP content filter // always adds at least one empty div, so don't have to do HTML branch. var div_html_selector = ''; var $qdeck_divs = $ ('div.qdeck_wrapper'); var $fallback_wrappers = $ ('div.qdeck_wrapper_fallback'); if ($qdeck_divs.length) { div_html_selector = 'div.qdeck_wrapper'; // Hide fallback wrappers. $fallback_wrappers.css ({display: 'none'}); } else { // If there are no fallback wrappers, need to add style so they'll show // if they're inserted into the page later. if ($fallback_wrappers.length == 0) { var style = '\n'; $ ('head').append (style); } div_html_selector = content; } // Read appropriate divs, look for inline qcard shortcodes, loop over // shortcode pairs. var i_deck = 0; $(div_html_selector).each (function () { var htm = $(this).html (); if (! htm) { //errmsgs.push (T ('Did not find page content (looking for div') + ' "' + content + '")'); } else { // See if there is a deck or decks. var qdeck_pos = htm.search (/\[qdeck/); if (qdeck_pos != -1) { // Remove and save text inside [qdeckdemo] ... [/qdeckdemo] pairs. // Replace with pairs as placeholder. var qdeckdemo_re = new RegExp ('\\[qdeckdemo\\][\\s\\S]*?\\[\\/qdeckdemo\\]', 'gm'); var qdeckdemos = htm.match (qdeckdemo_re); var n_qdeckdemos = 0; if (qdeckdemos) { n_qdeckdemos = qdeckdemos.length; htm = htm.replace (qdeckdemo_re, ''); if (debug[0]) { console.log ('[process_html] n_qdeckdemos: ', n_qdeckdemos); } } // Delete comments -- don't want to process [qdeck][/qdeck] pairs or any other // deck-related tags that are in comments. var new_html = htm.replace (//gm, ''); // Take out any remaining [!]...[\!] comments (those that were not // inside paragraph or header elements). new_html = new_html.replace (/\[!+\][\s\S]*?\[\/!+\]/gm, ''); // Check that there are pairs. var do_not_process_html = check_qdeck_tag_pairs (new_html); if (do_not_process_html) { new_html = do_not_process_html; } else { // Get text, including beginning and ending tags. // "." does not match line-ends (!), so use the whitespace/not-whitespace // construct. Non-greedy search, global, multiline. var qdeck_matches = new_html.match (/\[qdeck[\s\S]*?\[\/qdeck\]/gm); if (qdeck_matches) { var local_n_decks = qdeck_matches.length; if (debug[0]) { console.log ('[process_html] local_n_decks: ', local_n_decks); console.log (' qdeck_matches[0]: ', qdeck_matches[0]); } // Loop over qdeck-tag pairs. for (var ii_deck=0; ii_deck and from before [qdeck]. new_html = new_html.replace (/(<[ph][^>]*>\s*)*?\[qdeck[\s\S]*?\[\/qdeck\]/m, new_deck_html); i_deck++; } } } // Restore examples, but without [qdeckdemo] ... [/qdeckdemo] tags. for (var i_qdeckdemo=0; i_qdeckdemo< n_qdeckdemos; i_qdeckdemo++) { var qdeckdemo_i = qdeckdemos[i_qdeckdemo]; var len = qdeckdemo_i.length; qdeckdemo_i = qdeckdemo_i.substring (11, len - 12); new_html = new_html.replace ('', qdeckdemo_i); } // Replace content html. $ (this).html (new_html); // Mouseenter for this deck records it as the active qwiz/deck. $ (this).find ('div.qcard_window') .on ('mouseenter', function (e) { // Make sure get container div. if (e.target.className.toLowerCase () == 'qcard_window') { document_active_qwiz_qdeck = $ (e.target); } else { var $qdeckdiv = $ (e.target).parents ('div.qcard_window'); if ($qdeckdiv.length) { document_active_qwiz_qdeck = $qdeckdiv[0]; } } if (debug[7]) { console.log ('[qcard_window mouseenter] e.target:', e.target); console.log ('[qcard_window mouseenter] document_active_qwiz_qdeck:', document_active_qwiz_qdeck); } }); // Waited to do check_registered so can update sharing href of qwiz // icon. Also, record/update number of cards. $ (this).find ('div.qcard_window').each (function () { var id = $ (this)[0].id; // ID looks like: qcard_window-qdeck0 // 0----+----1----+--- var ii_deck = parseInt (id.substr (18), 10); var qrecord_id = $ (this).find ('table.qcard_table').attr ('qrecord_id'); if (qrecord_id) { var n_cards = deckdata[ii_deck].n_cards; var data = {qwiz_qdeck: 'qdeck', n_questions_cards: n_cards}; qqc.jjax (qname, ii_deck, qrecord_id, 'check_registered', data); } }); } // If wrapper divs, unwrap. if ($qdeck_divs.length) { $ (this).contents ().unwrap (); } } n_decks = i_deck; }); // Set up Enter-key intercept -- trigger appropriate button press // (Check answer, Login). qqc.init_enter_intercept (); // If any quizzes subject to recording, set user menus -- if this comes after // check_session_id () callback, it will properly set the menus (while the // callback may not have worked if the html hadn't been set at that time). if (qrecord_b) { qqc.set_user_menus_and_icons (); } // Set flag to display page (qwizscripts.js). q.processing_complete_b = true; } // ----------------------------------------------------------------------------- // Set up [textentry] autocomplete for this card. function init_textentry_autocomplete (i_deck, ii_card) { // Set minlength for autocomplete suggestions for this card. var card = deckdata[i_deck].cards[ii_card]; var minlength = card.textentry_minlength; if (card.all_choices[0].length < minlength) { minlength = card.all_choices[0].length; } if (card.single_char_b) { // Single-char textentry. $ ('input.qcard_single_char_entry').keyup (single_char_textentry_keyup); } else { var $textentry = $ ('#textentry-qdeck' + i_deck); $textentry.autocomplete ({ minLength: minlength, source: find_matching_terms, close: menu_closed, open: menu_shown, select: item_selected }); $textentry.keyup (menu_closed); } // See if using terms if (card.single_char_b) { // Not using terms. Gray out and disable "Check answer"/"Flip" button. $ ('button.flip-qdeck' + i_deck).removeClass ('qbutton').addClass ('qbutton_disabled').attr ('disabled', true); deckdata[i_deck].check_answer_disabled_b = true; card.check_answer = 'Check answer'; } else { // Get terms given with [terms]...[/terms] for this flashcard deck; or // load default terms if haven't done so already. if (deckdata[i_deck].terms) { // Only do this once per flashcard deck. if (! deckdata[i_deck].textentry_terms_metaphones) { deckdata[i_deck].textentry_terms_metaphones = qqc.process_textentry_terms (deckdata[i_deck].terms); } } else { if (! default_textentry_terms_metaphones) { var plugin_url = qqc.get_qwiz_param ('url', './'); var terms_data = qqc.get_textentry_terms (plugin_url + 'terms.txt', deckdata); default_textentry_terms_metaphones = qqc.process_textentry_terms (terms_data); } } // Also need to process additional terms for this flashcard deck, if any. // Only do once per deck. if (deckdata[i_deck].add_terms) { if (! deckdata[i_deck].add_textentry_terms_metaphones) { deckdata[i_deck].add_textentry_terms_metaphones = qqc.process_textentry_terms (deckdata[i_deck].add_terms); } } if (card.use_terms_b) { // Set terms for this card. List of terms (term, metaphone pairs): // (1) default or specific to this flashcard deck; plus (2) additional terms // for this deck, if any; and (3) specified entries for this [textentry]. // Singular or plural in each case. var singular_plural; if (card.textentry_plural_b) { singular_plural = 'plural'; } else { singular_plural = 'singular'; } // (1) Quiz-specific or default. if (deckdata[i_deck].terms) { current_card_textentry_terms_metaphones[i_deck] = deckdata[i_deck].textentry_terms_metaphones[singular_plural]; } else { current_card_textentry_terms_metaphones[i_deck] = default_textentry_terms_metaphones[singular_plural]; } // (2) Additional. if (deckdata[i_deck].add_terms) { current_card_textentry_terms_metaphones[i_deck] = current_card_textentry_terms_metaphones[i_deck] .concat (deckdata[i_deck].add_textentry_terms_metaphones[singular_plural]); } } else { current_card_textentry_terms_metaphones[i_deck] = []; } // (3) All specified entries. Calculate metaphones up to first blank // following a non-blank. textentry_answers[i_deck] = card.all_choices; textentry_answer_metaphones[i_deck] = textentry_answers[i_deck].map (function (answer) { answer = answer.replace (/\s*(\S+)\s.*/, '\$1'); return qqc.metaphone (answer); }) var textentry_answers_metaphones = textentry_answers[i_deck].map (function (answer) { return [answer, qqc.metaphone (answer)]; }); if (debug[6]) { console.log ('[init_textentry_autocomplete] textentry_answers_metaphones: ', textentry_answers_metaphones); } current_card_textentry_terms_metaphones[i_deck] = current_card_textentry_terms_metaphones[i_deck] .concat (textentry_answers_metaphones); // Sort and de-dupe. current_card_textentry_terms_metaphones[i_deck] = qqc.sort_dedupe_terms_metaphones (current_card_textentry_terms_metaphones[i_deck]); if (debug[6]) { console.log ('[init_textentry_autocomplete] current_card_textentry_terms_metaphones[i_deck].length: ', current_card_textentry_terms_metaphones[i_deck].length); console.log ('[init_textentry_autocomplete] current_card_textentry_terms_metaphones[i_deck].slice (0, 10): ', current_card_textentry_terms_metaphones[i_deck].slice (0, 10)); var i_start = current_card_textentry_terms_metaphones[i_deck].length - 10; if (i_start > 0) { console.log ('[init_textentry_autocomplete] current_card_textentry_terms_metaphones[i_deck].slice (' + i_start + '): ', current_card_textentry_terms_metaphones[i_deck].slice (i_start)); } } // Set placeholder now. Also reset "Check answer" button. var placeholder; var check_answer; if (minlength <= 1) { placeholder = T ('Type a letter/number, then select'); check_answer = T ('Type a letter'); } else { minlength = Math.max (minlength, 3); placeholder = T ('Type %s+ letters/numbers, then select'); placeholder = placeholder.replace ('%s', minlength); check_answer = T ('Type %s+ letters'); check_answer = check_answer.replace ('%s', minlength); } $textentry.attr ('placeholder', placeholder); $ ('button.flip-qdeck' + i_deck).html (check_answer); // Save for flip (). card.save_check_answer = check_answer; card.check_answer = check_answer; // Needed in find_matching_terms (). card.textentry_minlength = minlength; // Gray out "Check answer"/"Flip" button, but leave enabled -- click // will print alert rather than do flip. Also provide alert text as // title. $ ('button.flip-qdeck' + i_deck).removeClass ('qbutton').addClass ('qbutton_disabled').attr ('title', Tcheck_answer_message); deckdata[i_deck].check_answer_disabled_b = true; deckdata[i_deck].textentry_n_hints = 0; // If first card of no-intro deck, set up for mouseenter to start timer // to show hint button. if (deckdata[i_deck].n_reviewed == 0 && (no_intro_b[i_deck] || deckdata[i_deck].n_cards == 1)) { deckdata[i_deck].$qcard_window.attr ('onmouseenter', qname + '.start_hint_timeout (' + i_deck + ')'); } else { // Otherwise, start timeout now (with question display). q.start_hint_timeout (i_deck); } } // Don't let clicks bubble (to flip). if (deckdata[i_deck].click_flip_b) { $ ('#textentry_hint-qdeck' + i_deck).click (function (event) { event.stopPropagation (); if (debug[0]) { console.log ('[init_textentry_autocomplete] click event:', event); } }); } } // ----------------------------------------------------------------------------- this.start_hint_timeout = function (i_deck) { if (debug[0]) { console.log ('[start_hint_timeout] i_deck:', i_deck); } // Only execute this function once for this question. deckdata[i_deck].$qcard_window.removeAttr ('onmouseenter'); // Closure for setTimeout (). var show_hint_button = function () { $ ('#textentry_hint-qdeck' + i_deck) .removeAttr ('disabled') .addClass ('qbutton') .removeClass ('qbutton_disabled') .show (); } $ ('#textentry_hint-qdeck' + i_deck).html ('Hint').hide (); if (hint_timeout_sec >= 0) { show_hint_timeout[i_deck] = setTimeout (show_hint_button, hint_timeout_sec*1000); } } // ----------------------------------------------------------------------------- function process_qdeck_pair (htm, i_deck) { // Data object for this deck. deckdata.push ({}); // Array of cards ("cards"). deckdata[i_deck].cards = []; deckdata[i_deck].showing_front_b = true; deckdata[i_deck].i_card = 0; deckdata[i_deck].n_reviewed = 0; deckdata[i_deck].n_got_it = 0; deckdata[i_deck].exit_html = ''; deckdata[i_deck].align = ''; deckdata[i_deck].qrecord_id = ''; deckdata[i_deck].qrecord_id_ok = 'check credit'; deckdata[i_deck].click_flip_b = true; // Include any opening tags (e.g., "

" in WordPress). var m = htm.match (/(<[^\/][^>]*>\s*)*?\[qdeck([^\]]*)\]/m); var qdeck_tag = m[0]; var attributes = m[2]; attributes = qqc.replace_smart_quotes (attributes); if (debug[0]) { console.log ('[process_qdeck_pair] qdeck_tag: ', qdeck_tag); console.log ('[process_qdeck_pair] attributes: ', attributes); } // Alignment. Default = left. If center or right, set. var align = get_attr (attributes, 'align'); if (align == 'center' || align == 'right') { deckdata[i_deck].align = align; } // If "qrecord_id=..." present, parse out database ID. var qrecord_id = get_attr (attributes, 'qrecord_id'); if (qrecord_id) { // Set flag indicating this deck subject to recording. (Will get unset // by check_registered returned JavaScript if not registered.) deckdata[i_deck].qrecord_id = qrecord_id; // Set up array to save question text. deckdata[i_deck].q_and_a_text = {}; // If haven't checked already, see if user already logged in (get session // ID in cookie, see if still valid). if (! qrecord_b) { qrecord_b = true; if (typeof (document_qwiz_user_logged_in_b) == 'undefined' || document_qwiz_user_logged_in_b == 'not ready') { qqc.check_session_id (i_deck); } } } // Turn off flip on click? var click_flip_val = get_attr (attributes, 'click_flip'); if (click_flip_val) { deckdata[i_deck].click_flip_b = ! (click_flip_val == 'false'); } var n_decks = 0; var new_html = ''; var no_intro_i_b = false; // Is deck encoded? Decode if necessary. //htm = decode_qdeck (htm, qdeck_tag); // Capture any initial closing tags after [qdeck ...] -- will put them in // front of

that replaces [qdeck ...]. var m = htm.match (/\[qdeck[^\]]*\]((<\/[^>]+>\s*)*)/m, ''); if (m) { var initial_closing_tags = m[1]; new_html += initial_closing_tags; } // Delete [qdeck], any initial closing tags. htm = htm.replace (/\[qdeck[^\]]*\]((<\/[^>]+>\s*)*)/m, ''); // Delete any initial whitespace. htm = qqc.trim (htm); // Make sure there's at least one card. if (htm.search (/\[q([^\]]*)\]/m) == -1) { errmsgs.push (T ('Did not find question tags ("[q]") for') + ' qdeck ' + (i_deck + 1)); } else { // Look for [terms]...[/terms] and/or [add_terms]...[/add_terms] pairs. // Parse, and delete. Include opening tags in front and closing tags // after. htm = qqc.process_inline_textentry_terms (htm, 'terms', deckdata, i_deck); errmsgs = errmsgs.concat (deckdata.additional_errmsgs); htm = qqc.process_inline_textentry_terms (htm, 'add_terms', deckdata, i_deck); errmsgs = errmsgs.concat (deckdata.additional_errmsgs); // See if html up to first shortcode is just whitespace, including empty // paragraphs. Limit to first 2000 characters. var whitespace = parse_html_block (htm.substr (0, 2000), ['^'], ['[h]', '[i]', '[q]', '[q '], 'return whitespace'); if (whitespace) { // Yes, delete it. htm = htm.replace (whitespace, ''); } // See if header. Sets deckdata[i_deck].header_html. htm = process_header (htm, i_deck, true); // See if intro. Limit to first 2000 characters. var intro_html = parse_html_block (htm.substr (0, 2000), ['[i]'], ['[q]', '[q ']); // See if no [i]. if (intro_html == 'NA') { // No [i] -- intro may be text before [q]. See if there is. Add flag // to ignore   (empty paragraph). intro_html = parse_html_block (htm.substr (0, 2000), ['^'], ['[q]', '[q '], true); // If just tags and whitespace, then no intro. if (intro_html == '') { no_intro_i_b = true; } } else { // There was an [i]. Error if text before [i]. if (htm.substr (0, 5) != intro_html.substr (0, 5)) { errmsgs.push (T ('Text before intro') + ' [i] - qdeck ' + (i_deck + 1)); } // Delete [i] from intro. intro_html = intro_html.replace ('[i]', ''); } if (intro_html != '') { // If there's a [start] tag, replace with start button html. Otherwise // add start button html. var start_button_html = '

'; if (intro_html.indexOf ('[start]') != -1) { intro_html = intro_html.replace ('[start]', start_button_html); } else { intro_html += start_button_html; } // Add Qwiz icon to intro. intro_html += create_qwiz_icon_div (i_deck); // Save introductory html. deckdata[i_deck].intro_html = intro_html; } // card_html -- everything from first [q] on. var card_html = htm.match (/\[q [^\]]*\][\s\S]*|\[q\][\s\S]*/m)[0]; // Find topic attributes, if any, for card. First get [q] tags. var card_tags = card_html.match (/\[q[^\]]*\]/gm); if (debug[4]) { console.log ('[process_qdeck_pair] card_tags[0]: ', card_tags[0]); } var n_cards = card_tags.length; if (debug[0]) { console.log ('[process_qdeck_pair] n_cards: ', n_cards); } process_topics (i_deck, card_tags); // Capture any opening tags before each "[q...] tag. Skip "[qdeck]". var matches = htm.match (/(<[^\/][^>]*>\s*)*?\[q[ \]]/gm); var q_opening_tags = []; for (var i_card=0; i_card\n' + 'Return to page view' + '\n'; // If there's exit html, capture for summary report. var exit_html = card_html.match (/\[x\]([\s\S]*)/m); if (exit_html) { // If "[restart]" tag there, replace with restart button html. var restart_button_html = ' \n'; exit_html = exit_html[1].replace ('[restart]', restart_button_html); exit_html += '
' + exit_mobile_button_html; // Delete exit html from card html. card_html = card_html.replace (/\[x\][\s\S]*/m, ''); } else { // Add initially-non-displayed "exit-mobile-mode" button. exit_html = exit_mobile_button_html; } deckdata[i_deck].exit_html = exit_html; // Split into individual cards -- [q] (fronts) and [a] (backs). var cards_html = card_html.split (/\[q [^\]]*\]|\[q\]/); // Save each card and answer html in data array. for (var i_card=0; i_card'); } else { title += ' See qwizcards.com/share'; } } return divs.join (''); } // ----------------------------------------------------------------------------- function qwiz_icon_stop_propagation (i_deck) { // Don't let click on icon bubble to flip. $ ('#icon_qdeck' + i_deck).click (function (event) { event.stopPropagation (); }); } // ----------------------------------------------------------------------------- // Get card front and card back html, put into data array. function process_card_input (i_deck, i_card, htm, opening_tags) { // Object for this card. var card = {}; card.got_it = false; // Start with any opening tags that preceded "[q]" tag. var card_front_html = opening_tags + htm; // Get rid of everything from "[a]" (card back) on. card_front_html = card_front_html.replace (/\[a\][\s\S]*/m, ''); if (debug[1]) { console.log ('[process_card_input] card_front_html: ', card_front_html); } // If recording, save text without tags. Also, replace non-breaking spaces // and EOLs with space, multiple spaces with single space, trim. if (deckdata[i_deck].qrecord_id) { var q_and_a_text = qqc.remove_tags_eols (card_front_html); deckdata[i_deck].q_and_a_text[i_card] = q_and_a_text; } // If [textentry], change to html equivalent. Save flag if there. var new_card_front_html = card_front_textentry_html (card_front_html, i_deck); card.card_front = new_card_front_html; var front_textentry_b = new_card_front_html.length != card_front_html.length; // .......................................................................... // Find card back html. var card_back_html = htm.match (/\[a\][\s\S]*/m); if (debug[0]) { console.log ('[process_card_input] card_back_html: ', card_back_html); } // Take off initial "[a]". if (! card_back_html) { errmsgs.push (T ('Did not find answer ("[a]") -- card back -- for') + ' qdeck ' + (i_deck + 1) + ', ' + T ('card') + ' ' + (i_card + 1) + '\n' + htm); card_back_html = ''; } else { card_back_html = card_back_html[0].substring (3); } // Split into individual items. Should be just one. var card_back_items = card_back_html.split (/\[a\]/); if (card_back_items.length != 1) { errmsgs.push (T ('Got more than one answer ("[a]") -- card back -- for') + ': qdeck ' + (1 + i_deck) + ', ' + T ('card') + ' ' + (1 + i_card) + '\n' + htm); } // If recording, add card-back text to q_and_a_text. if (deckdata[i_deck].qrecord_id) { var q_and_a_text = qqc.remove_tags_eols (card_back_items[0]); deckdata[i_deck].q_and_a_text[i_card] += '\n' + q_and_a_text; } // Capture any opening tags before "[a]" tag. var a_opening_tags; var m = htm.match (/(<[^\/][^>]*>\s*)*?\[a\]/m); if (m && m[1]) { a_opening_tags = m[1]; if (debug[0]) { console.log ('[process_card_input] a_opening_tags: ', a_opening_tags); } } else { a_opening_tags = ''; } // Save html for "[a]". card.card_back = create_card_back_html (i_deck, i_card, card_back_items[0], a_opening_tags, front_textentry_b); return card; } // ----------------------------------------------------------------------------- // Process input for card with [textentry] with required input/autocomplete. function process_textentry (i_deck, i_card, htm, opening_tags) { // Object for this card. var card = {}; card.got_it = false; // Look for [textentry], see if plurals specified or minlength specified. var textentry_plural_b = false; var textentry_minlength = 3; var use_dict_b = default_use_dict == 'true'; var use_terms_b = default_use_terms == 'true'; var single_char_b = false; var m = htm.match (/\[textentry([^\]]*)\]/m); if (! m) { errmsgs.push (T ('Free-form input choices [c] or [c*] card does not have [textentry]')); } else { var attributes = m[1]; if (attributes) { // Look for "plural=" attribute. Match regular double-quote, or // left- or right-double-quote. attributes = qqc.replace_smart_quotes (attributes); textentry_plural_b = get_attr (attributes, 'plural') == 'true'; // "minlength=" attribute. var attr_val = get_attr (attributes, 'minlength'); if (attr_val != '') { textentry_minlength = attr_val; } // "use_terms=" attribute. var use_terms = get_attr (attributes, 'use_terms'); if (use_terms) { use_terms_b = use_terms != 'false'; } // "use_dict=" attribute. var use_dict = get_attr (attributes, 'use_dict'); if (use_dict) { use_dict_b = use_dict != 'false'; } // "single_char=" attribute. single_char_b = get_attr (attributes, 'single_char') == 'true'; } } // Replace [textentry] with input textbox and (hidden, initially) hint button. // Placeholder will be set later (in init_textentry_autocomplete ()). var classname; var style = ''; if (single_char_b) { classname = classname = 'qcard_single_char_entry'; style = 'style="width: 2em;" '; } else { classname = 'qcard_textentry'; } var input_and_button_htm = '
\n' + '\n' + '\n' + '
\n'; htm = htm.replace (/\[textentry([^\]]*)\]/, input_and_button_htm); // Look for choices and answers/feedback (interleaved, answer/feedback // required for each choice). Save as data, delete here. var choice_start_tags = ['[c]', '[c*]']; var choice_next_tags = ['[c]', '[c*]', '[x]']; var got_feedback_b = false; // Look for first [c], including any opening tags. var c_pos = htm.search (/\s*(<[^\/][^>]*\s*)*?\[c\*{0,1}\]/m); // Start with [c]s. var remaining_htm = htm.substr (c_pos); // Delete opening tags before first [c] and the rest. htm = htm.substr (0, c_pos); // Save as card front, set flag that entry required. card.card_front = htm; card.textentry_required_b = true; // If recording, save card front without tags. Exclude "Hint". if (deckdata[i_deck].qrecord_id) { var q_and_a_text = htm.replace (//, ''); q_and_a_text = qqc.remove_tags_eols (q_and_a_text); deckdata[i_deck].q_and_a_text[i_card] = q_and_a_text; } // Set up data for this card, create a div for each feedback alt -- so can // measure each, set front and back card size. Div to show selected in // flip () > textentry_set_card_back () depending on text entered. card.choices = []; card.textentry_plural_b = textentry_plural_b; card.textentry_minlength = textentry_minlength; card.use_terms_b = use_terms_b; card.use_dict_b = use_dict_b; card.single_char_b = single_char_b; card.feedback_htmls = []; card.all_choices = []; card.card_back = ''; var card_back = ''; // Loop over [c]s. var i_choice = 0; var default_choice_given_b = false; while (true) { var choice_html = parse_html_block (remaining_htm, choice_start_tags, choice_next_tags); if (choice_html == 'NA') { break; } remaining_htm = remaining_htm.substr (choice_html.length); // See if there's feedback within the choice html. var r = process_feedback_item (choice_html); choice_html = r.choice_html; if (r.feedback_html) { got_feedback_b = true; card.feedback_htmls.push (r.feedback_html); card_back += '
\n' + '

' + T ('You entered') + ' “     

' + r.feedback_html + '
\n'; // Check that there's not more than one feedback item accompanying // this choice. var r = process_feedback_item (choice_html); if (r.feedback_html) { errmsgs.push (T ('More than one answer or feedback shortcode [a] or [f] given with [textentry] choice') + ': qdeck ' + (1 + i_deck) + ', ' + T ('card') + ' ' + (1 + i_card) + ', ' + T ('choice') + ' ' + (1 + i_choice)); } } else { // No answers/feedback given for this choice. errmsgs.push (T ('Did not get answer/feedback [a] or [f] for [textentry] choice') + ': qdeck ' + (1 + i_deck) + ', ' + T ('card') + ' ' + (1 + i_card) + ', ' + T ('choice') + ' ' + (1 + i_choice)); card.feedback_htmls.push (''); } // Parse choice data. [c] or [c*] followed by semicolon-separated list // of potential answers. Delete up through [c] or [c*]. choice_html = choice_html.replace (/.*\[c\*{0,1}\]/m, ''); // Delete any tags and EOLs and non-breaking spaces. choice_html = choice_html.replace (/<[^>]+>|\n| /g, ''); // Error if just blanks and semicolons. if (choice_html.replace (';', '').search (/\S/) == -1) { errmsgs.push (T ('No text given for [textentry] choice') + ' - qdeck ' + (i_deck + 1) + ', ' + T ('question') + ' ' + (1 + i_card) + ', ' + T ('choice') + ' ' + (1 + i_choice)); } // Split on semicolons. var alts = choice_html.split (/\s*;\s*/); // Eliminate any blank entries. var nonblank_alts = []; for (var i=0; i=0; i--) { var chrs = question_htm.substr (i, 3); if (chrs == ']+>|\n| /g, ''); hangman_answer = qqc.trim (hangman_answer); hangman_answer_length = hangman_answer.length; if (debug[9]) { console.log ('[process_hangman] hangman_answer:', hangman_answer); } } i_choice++; } if (i_choice > 1) { errmsgs.push (T ('More than one hangman answer ([c] or [c*]) given') + ': deck ' + (1 + i_deck) + ', ' + T ('card') + ' ' + (1 + i_card)); } // Save the answer. if (! deckdata[i_deck].hangman_answer) { deckdata[i_deck].hangman_answer = {}; deckdata[i_deck].hangman_final_entry = {}; deckdata[i_deck].hangman_current_entry = {}; deckdata[i_deck].hangman_incorrect_chars = {}; } deckdata[i_deck].hangman_answer[i_card] = hangman_answer; // Thin-space-separated characters, individually underscored (except for // non-alpha characters). var hangman_final_entry = qqc.create_hangman_entry (hangman_answer); // Save. Substitute a single character (tab) for   in saved value. deckdata[i_deck].hangman_final_entry[i_card] = hangman_final_entry; // Just en-spaces for input value -- so user can click anywhere in input // text box. var input_value = new Array (hangman_answer_length).join (' '); var hangman_div = '
' + '
' + '
' + '' + '
' + '
'; question_htm = question_htm.replace ('[hangman]', hangman_div); // Add hangman message div. question_htm += '
'; card.card_front = question_htm; // .......................................................................... // Find card back html. var card_back_html = htm.match (/\[a\][\s\S]*/m); if (debug[0]) { console.log ('[process_hangman] card_back_html: ', card_back_html); } // Take off initial "[a]". if (! card_back_html) { errmsgs.push (T ('Did not find answer ("[a]") -- card back -- for') + ' qdeck ' + (i_deck + 1) + ', ' + T ('card') + ' ' + (i_card + 1) + '\n' + htm); card_back_html = ''; } else { card_back_html = card_back_html[0].substring (3); } // Split into individual items. Should be just one. var card_back_items = card_back_html.split (/\[a\]/); if (card_back_items.length != 1) { errmsgs.push (T ('Got more than one answer ("[a]") -- card back -- for') + ': qdeck ' + (1 + i_deck) + ', ' + T ('card') + ' ' + (1 + i_card) + '\n' + htm); } // If recording, add card-back text to q_and_a_text. if (deckdata[i_deck].qrecord_id) { var q_and_a_text = qqc.remove_tags_eols (card_back_items[0]); deckdata[i_deck].q_and_a_text[i_card] += '\n' + q_and_a_text; } // Capture any opening tags before "[a]" tag. var a_opening_tags; var m = htm.match (/(<[^\/][^>]*>\s*)*?\[a\]/m); if (m && m[1]) { a_opening_tags = m[1]; if (debug[0]) { console.log ('[process_card_input] a_opening_tags: ', a_opening_tags); } } else { a_opening_tags = ''; } // Save html for "[a]". card.card_back = create_card_back_html (i_deck, i_card, card_back_items[0], a_opening_tags, false); return card; } // ----------------------------------------------------------------------------- this.hangman_keyup = function (input_el, event, default_value, i_deck, ii_card) { var key = event.keyCode; // Get current input, reset to blank default. var value = input_el.value; input_el.value = default_value; if (debug[9]) { console.log ('[hangman_keyup] value.charCodeAt:', value.charCodeAt (0), value.charCodeAt (1), value.charCodeAt (2), value.charCodeAt (3)); } // Ignore if not in [A-Za-z0-9]. Typing quickly can produce more than one // character. var keychars = value.replace (/[^a-z0-9]/gi, ''); if (keychars == '') { return false; } keychars = keychars.toLowerCase (); if (debug[9]) { console.log ('[hangman_keyup] keychars:', keychars); } // Update entry. If characters are in answer, replace. If not, add to // incorrect-letters list. var current_entry = deckdata[i_deck].hangman_current_entry[ii_card]; var final_entry = deckdata[i_deck].hangman_final_entry[ii_card]; // Loop over characters. var n_chars = keychars.length; for (var i=0; i' + keychar + '<', 'i'); while (true) { var m = final_entry.substr (i_pos + 1).match (re); if (! m ) break; i_pos += m.index + 1; current_entry = qqc.setCharAt (current_entry, i_pos + 1, m[0][1]); good_char_b = true; } if (debug[9]) { console.log ('[hangman_keyup] keychar:', keychar, ', good_char_b:', good_char_b); } var hangman_incorrect_chars = deckdata[i_deck].hangman_incorrect_chars[ii_card]; if (good_char_b) { deckdata[i_deck].hangman_current_entry[ii_card] = current_entry; var local_current_entry = current_entry.replace (/\t/g, ' '); $ (input_el).siblings ('div.entry').html (local_current_entry); // Have we finished (all ""s filled in)? if (local_current_entry.indexOf ('') == -1) { // Yes. Don't accept further input. $ (input_el).attr ('disabled', true); //Enable "Check answer". $ ('button.flip-qdeck' + i_deck) .removeAttr ('disabled') .removeClass ('qbutton_disabled') .addClass ('qbutton') .html (T ('Flip')); deckdata[i_deck].check_answer_disabled_b = false; // Do flip. q.flip (i_deck); // Exit loop over characters. break; } } else { // Letter incorrect. Update status. Do if not already there. keychar = keychar.toLowerCase (); if (hangman_incorrect_chars.indexOf (keychar) == -1) { hangman_incorrect_chars += keychar; } deckdata[i_deck].hangman_incorrect_chars[ii_card] = hangman_incorrect_chars; if (debug[9]) { console.log ('[hangman_keyup] hangman_incorrect_chars:', hangman_incorrect_chars); } } } // Show status; only first 8 incorrect letters. if (hangman_incorrect_chars) { var hangman_incorrect_chars_display = qqc.create_hangman_incorrect_chars_display (hangman_incorrect_chars); $ (input_el).siblings ('div.hangman_status').html (hangman_incorrect_chars_display); } else { $ (input_el).siblings ('div.hangman_status').html (''); } return true; } // ----------------------------------------------------------------------------- function single_char_textentry_keyup (e) { var input_el = e.target; if (debug[6]) { console.log ('[single_char_textentry_keyup] input_el:', input_el); } // Get first character. Ignore if not in alphanumeric. var value = input_el.value; if (value.search (/[a-z0-9]/i) == -1) { input_el.value = ''; return false; } // Get i_deck from id. Looks like "textentry-qdeck0". var id = input_el.id; var i_deck = id.match (/qdeck([0-9]+)/)[1]; // Flip. q.flip (i_deck); } // ----------------------------------------------------------------------------- function process_feedback_item (choice_html) { // Answers/feedback. var feedback_start_tags = ['[a]', '[f]']; var feedback_next_tags = ['[a]', '[f]', '[x]']; var feedback_html = parse_html_block (choice_html, feedback_start_tags, feedback_next_tags); if (feedback_html != 'NA') { // Yes. Take out of the choice html. choice_html = choice_html.replace (feedback_html, ''); // Delete [a] or [f]. feedback_html = feedback_html.replace (/\[[af]\]/, ''); if (debug[2]) { console.log ('[process_feedback_item] feedback_html: ', feedback_html); } } else { feedback_html = ''; } if (debug[2]) { console.log ('[process_feedback_item] feedback_html:', feedback_html); console.log ('[process_feedback_item] choice_html:', choice_html); } return {'feedback_html': feedback_html, 'choice_html': choice_html}; } // ----------------------------------------------------------------------------- // Provide first letters of first correct answer as hint, up to five letters. this.textentry_hint = function (i_deck) { // Cancel any previous timer. clearTimeout (show_hint_timeout[i_deck]); show_hint_timeout[i_deck] = 0; deckdata[i_deck].textentry_n_hints++; var i_card = deckdata[i_deck].i_card; var ii_card = deckdata[i_deck].card_order[i_card]; var card = deckdata[i_deck].cards[ii_card]; var textentry_hint = card.all_choices[0].substr (0, deckdata[i_deck].textentry_n_hints); var $textentry = $ ('#textentry-qdeck' + i_deck); $textentry.val (textentry_hint).focus (); // Trigger search on entry -- handles hints that don't match anything (grays // "Check answer"/"Flip") and those that do. $textentry.autocomplete ('search'); // Disable hint button, reset label. $ ('#textentry_hint-qdeck' + i_deck) .attr ('disabled', true) .removeClass ('qbutton') .addClass ('qbutton_disabled') .html ('Another
hint'); // But set timer to show again. Closure. var show_hint_button = function () { $ ('#textentry_hint-qdeck' + i_deck) .removeAttr ('disabled') .addClass ('qbutton') .removeClass ('qbutton_disabled'); } if (hint_timeout_sec >= 0) { show_hint_timeout[i_deck] = setTimeout (show_hint_button, hint_timeout_sec*1000); } } // ----------------------------------------------------------------------------- this.set_textentry_i_deck = function (input_el) { // See which flashcard deck this is. Save in global (private) variable. // id looks like textentry-qdeck0 var id = input_el.id; textentry_i_deck = id.match (/[0-9]+/)[0]; if (debug[6]) { console.log ('[set_textentry_i_deck] textentry_i_deck: ', textentry_i_deck); } } // ----------------------------------------------------------------------------- function create_card_back_html (i_deck, i_card, htm, opening_tags, front_textentry_b) { var new_html = opening_tags + htm; // See if '[textentry]' present. if (htm.search (/\[.*textentry.*/) != -1) { // Yes. Error if no textentry on front. if (! front_textentry_b) { errmsg.push (T ('[textentry] on back of card, but not on front') + ' - qdeck ' + (i_deck+1) + ', ' + T ('card') + ' ' (i_card+1)); } // Convert to equivalent html. new_html = card_back_textentry_html (new_html); } else { // No. If there was textentry on front, create default echo. if (front_textentry_b) { var prepend_html = '

' + T ('You wrote') + ' “     

'; new_html = prepend_html + new_html; } } if (debug[2]) { console.log ('[create_card_back_html] new_html:', new_html); } return opening_tags + new_html; } // ----------------------------------------------------------------------------- function tags_to_pat (tags) { var tags_pat = '(' + tags.join (')|(') + ')'; tags_pat = tags_pat.replace (/([\[\]\*])/g, '\\$1'); tags_pat = '((' + tags_pat + ')\\s*)'; return tags_pat; } // ----------------------------------------------------------------------------- // Parse out block of html -- from opening tags, through one of qwiz/qcard // "tags" up to any opening tags of next qwiz/qcard tags. function parse_html_block (htm, qtags, qnext_tags, ignore_nbsp_b) { if (debug[0]) { console.log ('[parse_html_block] qtags: ', qtags, ', htm: ', htm); } // String is presumably "return whitespace". Flag to do so only if is all // whitespace (including empty paragraphs). var return_whitespace_b = typeof (ignore_nbsp_b) == 'string'; // Add a default "end" shortcode that will always be found. var ZendZ = '[ZendZ]'; htm += ZendZ; qnext_tags.push (ZendZ); // Include opening tags before the qwiz/qcard tags in each case. // -- a series of opening tags with possible whitespace in between, but // nothing else. var opening_pat = '\\s*(<[^/][^>]*>\\s*)*?'; var tags_pat = opening_pat + tags_to_pat (qtags); var next_tags_pat = opening_pat + tags_to_pat (qnext_tags); // Final term collects any immediate closing tags after next qtags. var closing_pat = '((]+>\\s*)*)'; var re = new RegExp ('([\\s\\S]*?)(' + tags_pat + '[\\s\\S]*?)' + next_tags_pat + closing_pat, 'im'); var htm_match = htm.match (re); var htm_block = ''; var closing_tags = ''; if (htm_match) { htm_block = htm_match[2]; // Take off default end shortcode if was found. htm_block = htm_block.replace (ZendZ, ''); // If htm is only tags and whitespace, set to empty string. var htm_wo_tags = htm_block.replace (/<[^>]+>/gm, ''); // If flag set, also ignore   if (ignore_nbsp_b != undefined) { htm_wo_tags = htm_wo_tags.replace (/ /gm, ''); } var is_whitespace_b = htm_wo_tags.search (/\S/) == -1; if (is_whitespace_b) { if (! return_whitespace_b) { htm_block = ''; } } else { var i_qnext_tag = 7 + qtags.length; var qnext_tag = htm_match[i_qnext_tag]; if (qnext_tag && qnext_tag[1] == '/') { htm_block += qnext_tag; } var i_closing_tags = i_qnext_tag + 2 + qnext_tags.length; var closing_tags = htm_match[i_closing_tags]; if (closing_tags) { htm_block += closing_tags; } } // If returning only whitespace, and not all whitespace, return empty // string. if (return_whitespace_b && ! is_whitespace_b) { htm_block = ''; } } else { // Didn't find tag-closing tag combo. htm_block = 'NA'; } if (debug[0]) { console.log ('[parse_html_block] htm_block: ', htm_block); } return htm_block; } // ----------------------------------------------------------------------------- // If [h] (or [H]), capture header tag/text, including opening tags before // [h], up to intro ([i]) if allowed, or question ([q]). Delete header from // intro. function process_header (htm, i_deck, intro_b) { var qtags = ['[h]']; var qnext_tags = ['[q]', '[q ']; if (intro_b != undefined) { qnext_tags.push ('[i]'); } // Limit to first 1000 characters. var header_html = parse_html_block (htm.substr (0, 1000), qtags, qnext_tags); if (header_html != 'NA' && header_html != '') { // Error if text before [h]. if (htm.substr (0, 5) != header_html.substr (0, 5)) { errmsgs.push (T ('Text before header') + ' [h] - qdeck ' + (i_deck + 1)); } // Delete header from htm. htm = htm.replace (header_html, ''); // Delete [h] from header. header_html = header_html.replace (/\[h\]/ig, ''); // Delete line-breaks from header. header_html = header_html.replace (//ig, ''); } deckdata[i_deck].header_html = header_html; return htm; } // ----------------------------------------------------------------------------- // Divs for card, progress, "next" buttons. function create_qdeck_divs (i_deck, qdeck_tag) { // Capture any style info or other attributes provided. If styles not set // for width, height, and border do so now. (Do as style in order to // override WordPress class for ). var m = qdeck_tag.match (/\[qdeck([^\]]*)\]/m); var attributes = m[1]; var default_style = ' style="max-width: 500px; width: auto; margin: auto; height: 300px; border: 0;"'; deckdata[i_deck].card_height_setting = '300px'; if (! attributes) { attributes = default_style; } else { // Replace any "smart quotes" with regular quotes. attributes = qqc.replace_smart_quotes (attributes); if (attributes.search (/style\s*?=/m) == -1) { attributes += default_style; } else { if (attributes.search ('width') == -1) { } else { m = attributes.match (/width\s*:\s*([^;\s]*)[;\s$]/m); if (m) { deckdata[i_deck].card_width_setting = m[1]; } } if (attributes.search ('height') == -1) { attributes = attributes.replace (/(style\s*?=\s*?["'])/m, '$1height: 300px; '); } else { m = attributes.match (/height\s*:\s*([^;\s]*)[;\s$]/m); if (m) { deckdata[i_deck].card_height_setting = m[1]; } } if (attributes.search ('border') == -1) { attributes = attributes.replace (/(style\s*?=\s*?["'])/m, '$1border: 0; '); } } // If "random=..." present, parse out true/false. var random = get_attr (attributes, 'random'); deckdata[i_deck].random_b = random == 'true'; if (debug[0]) { console.log ('[create_qdeck_divs] random:', random, ', random_b:', deckdata[i_deck].random_b); } } // Delete "align=..." from attributes. attributes = attributes.replace (/align\s*=\s*"[^"]*"/, ''); if (debug[0]) { console.log ('[create_qdeck_divs] attributes: ', attributes); } var divs = []; // Add z-index, so if large graphic expands card, will stay on top of decks // farther down the page. Also, if alignment center or right, add style. var style = 'z-index: ' + (20 - i_deck) + ';'; if (deckdata[i_deck].align == 'center') { style += ' margin: auto;'; } else if (deckdata[i_deck].align == 'right') { style += ' margin-left: auto;'; } divs.push ('
'); // Exit mobile mode panel and slide-in icon. Panel icon adjusted left to // offset left padding of qcard_window divs.push ( '
'); divs.push ( '
'); divs.push ( ''); divs.push ( '
'); divs.push ( ''); divs.push ( '(To return to this full-screen view, tap '); divs.push ( ''); divs.push ( ')'); divs.push ( ''); divs.push ( '
'); divs.push ( '
'); divs.push ( '
'); divs.push ( '
'); // Progress div. divs.push ('
'); // "Go-mobile" icon. First, large clickable target positioned absolutely, // centered on go-mobile icon. divs.push ( '
'); divs.push ( '
'); // Image is in the regular flow (float left, though). divs.push ( ''); if (deckdata[i_deck].qrecord_id) { var addclass = ''; if (no_intro_b[i_deck] || deckdata[i_deck].n_cards == 1) { addclass = ' qwiz-usermenu_icon_no_intro'; } divs.push ('
'); divs.push ( '▼'); divs.push ('
'); } divs.push (' '); divs.push (' '); if (deckdata[i_deck].qrecord_id) { // Add user menu div. Don't populate until after start/login. divs.push ('
'); divs.push ('
'); } // End qcard_progress-qdeck. divs.push ('
'); // Header. var onclick = ''; if (deckdata[i_deck].click_flip_b) { onclick = ' onclick="' + qname + '.flip (' + i_deck + ')"'; } divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push (' '); divs.push (' '); divs.push (' '); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push (' '); divs.push (' '); divs.push (' '); divs.push (' '); divs.push ('
'); divs.push ('
'); divs.push ('
'); divs.push (' '); divs.push (' '); divs.push ('
'); divs.push ('
'); // Small, almost invisible, focusable element inside a div. For Firefox issue // related to keydown event --> intercept. divs.push ('
'); divs.push (' '); divs.push ('
'); divs.push (''); return divs.join ('\n'); } // ----------------------------------------------------------------------------- function process_topics (i_deck, card_tags) { // Topic or topics each card, if any. deckdata[i_deck].card_topics = new Array (n_cards); // List of all topics. deckdata[i_deck].topics = []; // Loop over tags. var n_cards_w_topics = 0; var n_cards = card_tags.length; for (var i_card=0; i_card'); non_mobile_scrollLeft = $ (document).scrollLeft (); non_mobile_scrollTop = $ (document).scrollTop (); $deck.appendTo ('body'); window.scrollTo (0, 1); // Hide go-mobile icon and clickable target. $ ('.go-mobile-qdeck' + i_deck).hide (); // Show exit-mobile slider icon. $ ('#icon-exit-mobile-qdeck' + i_deck).show (); // Show summary report "Return to page view" button. $ ('#summary-deck' + i_deck).find ('button.summary_exit_mobile_deck').show (); // Detect window scroll - if scroll to top, show slide-in panel for exit- // mobile-mode button. Flag so ignore initiating trigger. scroll_detect_i_deck = -1; $ (window).on ('scroll', function () { //console.log ('[go_mobile > scroll] $ (window).scrollTop ():', $ (window).scrollTop ()); if (scroll_detect_i_deck != -1) { var scrollTop = $ (window).scrollTop (); if (scrollTop == 0) { // If just closed panel, assume don't want to reopen // it just yet. if (panel_exit_mobile_just_closed_b) { window.scrollTo ($ (window).scrollLeft (), 1); panel_exit_mobile_just_closed_b = false; } else { // Show panel and set flag. q.open_panel_exit_mobile (i_deck); } } else if (panel_exit_mobile_open_b) { // Any scroll will close panel (as will click). q.close_panel_exit_mobile ($ ('#overlay-exit-mobile-qdeck' + i_deck)[0]); } else if (scrollTop != 1) { // Any other user scroll means OK to open panel when // scroll to top (scrollTop 1 assumed to be panel // close scroll, which triggers scroll event). panel_exit_mobile_just_closed_b = false; } } }); scroll_detect_i_deck = i_deck; // Don't let click on "Return to page view" trigger close_panel_exit_mobile (). $ ('#panel-exit-mobile-qdeck' + i_deck + ' button') .click (function (event) { event.stopPropagation (); }); // Set global var. document_qwiz_mobile = 'mobile_'; } // ----------------------------------------------------------------------------- this.open_panel_exit_mobile = function (i_deck) { $ ('#overlay-exit-mobile-qdeck' + i_deck) .show () .animate ({top: '0px'}, 500); panel_exit_mobile_open_b = true; // Also hide default slide-icon. $ ('#icon-exit-mobile-deck' + i_deck).hide (); } // ----------------------------------------------------------------------------- this.close_panel_exit_mobile = function (overlay_el) { $ (overlay_el).animate ({top: '-100px'}, 500, function () { $ (this).hide (); // Also show default slide-icon (easier to show // all). $ ('div.icon-exit-mobile-qwiz').show (); }); // Reposition window vertically so can have a scroll to detect. window.scrollTo ($ (window).scrollLeft (), 1); // Reset flags. panel_exit_mobile_open_b = false; panel_exit_mobile_just_closed_b = true; return false; } // ----------------------------------------------------------------------------- this.exit_mobile = function (i_deck) { // Deck container: restore style, change class to standard. var $deck = deckdata[i_deck].$qcard_window; $deck.attr ('style', deckdata[i_deck].deck_style) .removeClass ('qdeck-mobile') .addClass ('qcard_window'); // Place deck back into content. Scroll back to previous position. $ ('#deck_div_placeholder').replaceWith ($deck); window.scrollTo (non_mobile_scrollLeft, non_mobile_scrollTop); // Reset exit panel. $ ('#overlay-exit-mobile-qdeck' + i_deck).css ({top: '-100px', display: 'none'}); // Turn off scroll detect. $ (window).off ('scroll'); // Hide exit-mobile slider icon (easier just to hide them all). $ ('div.icon-exit-mobile-qwiz, div.icon-panel-exit-mobile-qwiz').hide (); // Also hide summary exit-mobile button. $ ('button.summary_exit_mobile_deck').hide (); // If still on mobile device, show go-mobile icon and clickable target. if (qqc.is_mobile ()) { $ ('.go-mobile-qdeck' + i_deck).show (); } // Unset global vars. document_qwiz_mobile = ''; panel_exit_mobile_just_closed_b = false; } // ----------------------------------------------------------------------------- function check_qdeck_tag_pairs (htm) { var new_htm = ''; // Match "[qdeck]" or "[/qdeck]". var matches = htm.match (/\[qdeck|\[\/qdeck\]/gm); if (matches) { var n_tags = matches.length; var error_b = false; if (n_tags % 2 != 0) { error_b = true; } else { // Check proper pairs. for (var i=0; i'); if (debug[1]) { console.log ('[card_front_textentry_html] new_html: ', new_html); } return new_html; } // ----------------------------------------------------------------------------- function card_back_textentry_html (htm, i_deck) { // Change to appropriate html. // Spec is either // (1) [optional text textentry more optional text] // or (2) [optional text more optional text] // // If user does not make an entry on front of card, then nothing within // square brackets is shown when flip to back. // Change id="back_textentry" to id="textentry-qdeck...", otherwise expand to // equivalent. if (htm.indexOf ('id="back_textentry"') != -1) { htm = htm.replace ('back_textentry', 'back_textentry-qdeck' + i_deck); } else { htm = htm.replace ('textentry', ''); } // Convert "[" and "]" to paragraph. htm = htm.replace (/\[([^[]*textentry.*?)\]/, '

$1

'); if (debug[0]) { console.log ('[card_back_textentry_html] htm:', htm); } return htm; } // ----------------------------------------------------------------------------- function init_element_pointers (i_deck) { // jQuery element objects for this deck. deckdata[i_deck].$qcard_window = $ ('div#qcard_window-qdeck' + i_deck); deckdata[i_deck].$qcard_container = $ ('div#qcard_window-qdeck' + i_deck + ' div.card-container'); deckdata[i_deck].$flip = $ ('button.cbutton-qdeck' + i_deck); deckdata[i_deck].$progress = $ ('div#qcard_progress-qdeck' + i_deck); deckdata[i_deck].$progress_text = $ ('div#qcard_progress-qdeck' + i_deck + ' span.progress_text'); deckdata[i_deck].$header = $ ('div#qcard_header-qdeck' + i_deck); deckdata[i_deck].$qcard_card = $ ('div#qcard_card-qdeck' + i_deck); deckdata[i_deck].$qcard_table_front= $ ('div#qcard_card-qdeck' + i_deck + ' div.front table'); deckdata[i_deck].$qcard_table_back = $ ('div#qcard_card-qdeck' + i_deck + ' div.back table'); deckdata[i_deck].$qcard_card_front = $ ('div#qcard_card-qdeck' + i_deck + ' div.front td.center'); deckdata[i_deck].$qcard_card_back = $ ('div#qcard_card-qdeck' + i_deck + ' div.back td.center'); deckdata[i_deck].$next_buttons = $ ('div#qcard_next_buttons-qdeck' + i_deck); if (debug[5]) { console.log ('[init_element_pointers] $next_buttons:', deckdata[i_deck].$next_buttons); } } // ----------------------------------------------------------------------------- function init_card_order (i_deck) { var n_cards = deckdata[i_deck].n_cards; deckdata[i_deck].card_order = new Array (n_cards); for (var i=0; i' + T ('Flip') + '   '; // "Need more practice". Starts out disabled, gray. if (deckdata[i_deck].n_to_go > 1) { htm += '   '; } // "Got it". Starts out disabled, gray. if (next_button_active_b) { htm += '   '; } else { htm += '   '; } /* "Shuffle". if (deckdata[i_deck].n_to_go > 1) { htm += '   '; }*/ if (debug[5]) { console.log ('[set_next_buttons] htm:', htm); } deckdata[i_deck].$next_buttons.html (htm); }; // ----------------------------------------------------------------------------- this.process_card = function (i_deck) { // Keep running through cards until got_it true for all. if (deckdata[i_deck].n_to_go == 0) { done (i_deck); } else { var i_card = deckdata[i_deck].i_card; while (true) { // Display only those cards not yet marked got_it. var ii_card = deckdata[i_deck].card_order[i_card]; if (! deckdata[i_deck].cards[ii_card].got_it) { // Display card - not "reviewed" until "Check answer"/flip. "Need // more practice" and "Got it!" buttons start out disabled, gray. deckdata[i_deck].card_reviewed_b = false; if (deckdata[i_deck].qrecord_id) { deckdata[i_deck].current_first_flip_sec = 0; deckdata[i_deck].n_flips = 0; deckdata[i_deck].current_first_textentry_sec = 0; } if (! next_button_active_b) { $ ('button.got_it-qdeck' + i_deck + ', button.next_card-qdeck' + i_deck).attr ('disabled', true).removeClass ('qbutton').addClass ('qbutton_disabled'); } // If only one to go, disable and gray out more practice/next card // and shuffle buttons. if (deckdata[i_deck].n_to_go == 1) { $ ('button.next_card-qdeck' + i_deck + ', button.shuffle-qdeck' + i_deck).attr ('disabled', true).removeClass ('qbutton').addClass ('qbutton_disabled'); } deckdata[i_deck].i_card = i_card; q.set_card_front_and_back (i_deck, i_card); break; } else { i_card++; if (i_card >= deckdata[i_deck].n_cards) { i_card = 0; } } } } }; // ----------------------------------------------------------------------------- function done (i_deck) { // If showing back, change to front. if (! deckdata[i_deck].showing_front_b) { q.flip (i_deck); } // Progress div (and go-mobile and user-menu icons) disappears without // something in text. deckdata[i_deck].$progress_text.html (' '); deckdata[i_deck].$qcard_card_back.html (''); deckdata[i_deck].$next_buttons.html (''); var report_html = []; // Overall. var overall; var n_cards = deckdata[i_deck].n_cards; var n_reviewed = deckdata[i_deck].n_reviewed; if (n_reviewed == n_cards) { overall = T ('En este montón de %s memojis has hecho clic en') + ' “' + T ('Got it!') + '” ' + T ('al primer intento en cada memoji.'); } else { overall = T('Este montón tenía %s memojis.') + ' '; overall += T ('Te ha tomado') + ' ' + qqc.number_to_word (n_reviewed) + ' ' + Tplural ('intento', 'intentos', n_reviewed) + ' ' + T ('hasta que has hecho clic en') + ' “' + T ('Got it!') + '” ' + Tplural ('para este memoji', 'para cada una', n_cards) + '.'; } overall = overall.replace ('%s', qqc.number_to_word (n_cards)); report_html.push ('

' + overall + '

'); // By topic. var n_topics = deckdata[i_deck].topics.length; if (n_topics == 1) { var topic = deckdata[i_deck].topics[0]; var all_both_n; if (n_cards == 1) { all_both_n = T ('This'); } else if (n_cards == 2) { all_both_n = T ('Both'); } else { all_both_n = T ('All') + ' '+ qqc.number_to_word (n_cards); } report_html.push ('

' + all_both_n + ' ' + Tplural ('card was', 'cards were', n_cards) + ' about topic “' + topic + '.”

'); } else if (n_topics > 1 && n_reviewed > n_cards) { // We'll show only topics where user clicked "Need more practice". See // which. var need_more_practice_topics = []; for (var i_topic=0; i_topic i_topic_n_cards) { var topic_text = '' + topic + ': ' + qqc.number_to_word (i_topic_n_cards) + ' ' + Tplural ('card', 'cards', i_topic_n_cards) + ', ' + qqc.number_to_word (i_topic_n_reviewed) + ' ' + 'tries'; need_more_practice_topics.push (topic_text); } } var n_need_more_practice_topics = need_more_practice_topics.length; var topic_list_html = '

'; if (n_need_more_practice_topics > 1) { topic_list_html += T ('These are the topics of cards where you clicked'); for (var i=0; i'; topic_list_html += need_more_practice_topics.join ('; ') + '.'; topic_list_html += '

'; report_html.push (topic_list_html); } // Show exit text. report_html.push (deckdata[i_deck].exit_html); deckdata[i_deck].$qcard_card_front.html (report_html.join ('\n')); // Show or hide "exit-mobile" button. if (document_qwiz_mobile) { $ ('button.summary_exit_mobile_deck').show (); } else { $ ('button.summary_exit_mobile_deck').hide (); } deckdata[i_deck].no_flip_b = true; deckdata[i_deck].deck_started_b = false; // Set to match larger of front and back. set_container_width_height (i_deck); } // ----------------------------------------------------------------------------- function display_progress (i_deck) { var progress_html; progress_html = deckdata[i_deck].n_cards + ' ' + T ('cards total') + ', ' + deckdata[i_deck].n_reviewed + ' ' + Tplural ('card', 'cards', deckdata[i_deck].n_reviewed) + ' ' + T ('reviewed') + ', ' + deckdata[i_deck].n_to_go + ' ' + Tplural ('card', 'cards', deckdata[i_deck].n_to_go) + ' ' + T ('para terminar'); deckdata[i_deck].$progress_text.html (progress_html); } // ----------------------------------------------------------------------------- this.display_login = function (i_deck, add_team_member_f, progress_bars_f) { // Close menu in case came from there. $ ('#usermenu-qdeck' + i_deck).hide (); if (! add_team_member_f && ! progress_bars_f) { // Stop any bouncing icons (no-intro quizzes/flashcard decks) bouncing. $ ('div.qwiz-usermenu_icon_no_intro').removeClass ('qwiz-icon-bounce'); } // If showing back, change to front. if (! deckdata[i_deck].showing_front_b) { deckdata[i_deck].check_answer_disabled_b = false; q.flip (i_deck); } if (progress_bars_f) { // Gets data, callback sets html. qqc.create_progress_bars (qname, deckdata, i_deck); } else { deckdata[i_deck].$qcard_card_front.html (get_login_html (i_deck, add_team_member_f)); } deckdata[i_deck].deck_started_b = false; deckdata[i_deck].no_flip_b = true; // Hide buttons. $ ('#qcard_next_buttons-qdeck' + i_deck).css ('visibility', 'hidden'); // Focus on username field. $ ('#qdeck_username-qdeck' + i_deck).focus (); } // ----------------------------------------------------------------------------- function get_login_html (i_deck, add_team_member_f) { add_team_member_f = add_team_member_f ? 1 : 0; var onfocus = 'onfocus="jQuery (\'#qdeck_login-qdeck' + i_deck + ' p.login_error\').css (\'visibility\', \'hidden\')"'; var login_html = '\n'; return login_html; } // ----------------------------------------------------------------------------- this.login = function (i_deck, add_team_member_f) { add_team_member_f = add_team_member_f ? 1 : 0; // In case previously declined login option, unset cookie and local flag. $.removeCookie ('qwiz_declined_login', {path: '/'}); document_qwiz_declined_login_b = false; // Have we got username and password? var username_obj = $ ('#qdeck_username-qdeck' + i_deck); var username = username_obj.val (); if (! username ) { alert (T ('Please enter User name')); username_obj.focus (); return; } if (add_team_member_f) { // Check if this username already on team list. var usernames = document_qwiz_username.split ('; '); if (usernames.indexOf (username) != -1) { alert ('User ' + username + ' is already on your team.'); return false; } } var password_obj = $ ('#qdeck_password-qdeck' + i_deck); var password = password_obj.val (); if (! password) { alert (T ('Please enter Password')); password_obj.focus (); return; } // We'll send "SHA3" of password. var sha3_password = CryptoJS.SHA3 (password).toString (); var remember_f; if (add_team_member_f) { remember_f = document_qwiz_remember_f; } else { // Pass state of "Remember" checkbox. remember_f = $ ('#qdeck_login-qdeck' + i_deck + ' input[type="checkbox"]').prop('checked') ? 1 : 0; document_qwiz_remember_f = remember_f; } // Do jjax call. var data = {username: username, sha3_password: sha3_password, remember_f: remember_f, add_team_member_f: add_team_member_f}; if (add_team_member_f) { data.previous_username = document_qwiz_username; } qqc.jjax (qname, i_deck, deckdata[i_deck].qrecord_id, 'login', data); } // ----------------------------------------------------------------------------- this.login_ok = function (i_deck, session_id, remember_f) { // Success. Create session cookie, valid for this session, or -- if flag // set -- 1 day, good for whole site. Value set by server. Callback // script also saves session ID as global (document) variable // document_qwiz_session_id. var options = {path: '/'}; if (remember_f == 1) { options.expires = 1; } $.cookie ('qwiz_session_id', document_qwiz_session_id, options); // Set flag, record time. document_qwiz_user_logged_in_b = true; document_qwiz_current_login_sec = new Date ().getTime ()/1000.0; // Set user menus. qqc.set_user_menus_and_icons (); // Hide login. $ ('#qdeck_login-qdeck' + i_deck).hide (); // If recording any decks, reset flag to record start time on first // interaction. if (qrecord_b) { for (var ii_deck=0; ii_deck). $textentry.blur ().css ('visibility', 'hidden'); if (card.textentry_required_b) { // Find with which choice the user textentry is associated, set card // back to answer for that choice. textentry_set_card_back (i_deck, card); } else { // If something entered in text box, then set back-side element to // what was entered. var textentry = $textentry.val (); if (textentry) { // Show what was within square brackets, insert user entry. //$('#back_textentry_p-qdeck' + i_deck).show (); $('#back_textentry_p-qdeck' + i_deck).css ('visibility', 'visible'); $('#back_textentry-qdeck' + i_deck).html (textentry); } else { // No entry on front. Don't show any of paragraph on back, but // keep spacing. $('#back_textentry_p-qdeck' + i_deck).css ('visibility', 'hidden'); //$('#back_textentry_p-qdeck' + i_deck).hide (); } } } set_front_back = 'back'; } else { set_front_back = 'front'; // "Flip"/"Check answer" button - for front, change back to current // setting (might be "Type 3+ letters" for required-input textentry). $ ('button.flip-qdeck' + i_deck).html (card.check_answer); } deckdata[i_deck].$flip.trigger ('click'); // Closure for setTimeout (). var showFrontElements = function () { if ($textentry.length) { $textentry.css ('visibility', 'visible'); } $front.css ('visibility', 'visible'); $front.find ('sup, sub').css ('visibility', 'visible'); }; // Doing explicit show/hide for whole front back -- Chrome seemed to // randomly ignore "backface-visibility." Also do for front text box and // super/subscripts ("flashing" in Chrome). Wait till after flip is halfway // through (Time based on that in flipCard.css.) if (deckdata[i_deck].showing_front_b) { setTimeout (hideFrontElements, 300); } else { setTimeout (showFrontElements, 300); } // Keep track whether showing front or back. deckdata[i_deck].showing_front_b = ! deckdata[i_deck].showing_front_b; }; // ----------------------------------------------------------------------------- function set_header (i_deck, front_back, init_b) { // If no initial header, hide. if (init_b != undefined) { var header_html = deckdata[i_deck].header_html; if (header_html == '' || header_html == 'NA') { deckdata[i_deck].$header.hide (); } else { deckdata[i_deck].$header.html (header_html); } } // Set the widths of the progress div and header div to match card side. var qcard_width = $('#qcard_card-qdeck' + i_deck + ' div.' + front_back + ' table.qcard_table').outerWidth (); if (debug[0]) { console.log ('[set_header] qcard_width: ', qcard_width); } deckdata[i_deck].$header.width (qcard_width); deckdata[i_deck].$next_buttons.width (qcard_width); // If alignment center or right, set width of deck window div. Don't do if // mobile view. if (deckdata[i_deck].align == 'center' || deckdata[i_deck].align == 'right') { if (! document_qwiz_mobile) { deckdata[i_deck].$qcard_window.width (qcard_width); } } return qcard_width; } // ----------------------------------------------------------------------------- this.set_card_front_and_back = function (i_deck, i_card) { // Reset card width and height to original settings (so possible resize of // previous card won't persist). deckdata[i_deck].$qcard_table_front.css ({ width: deckdata[i_deck].card_width_setting, height: deckdata[i_deck].card_height_setting}); deckdata[i_deck].$qcard_table_back.css ({ width: deckdata[i_deck].card_width_setting, height: deckdata[i_deck].card_height_setting}); var card_front_back = ['card_front', 'card_back']; // Show progress. display_progress (i_deck); var ii_card = deckdata[i_deck].card_order[i_card]; var card = deckdata[i_deck].cards[ii_card]; // Default: "Check answer"/flip enabled. $ ('button.flip-qdeck' + i_deck) .removeAttr ('disabled') .removeClass ('qbutton_disabled') .addClass ('qbutton'); deckdata[i_deck].check_answer_disabled_b = false; for (var i_side=0; i_side<2; i_side++) { var side = card_front_back[i_side]; // Card front/back text. if (side == 'card_front') { // If no intro, and this is first card, add Qwiz icon to front. var qwiz_icon_div = ''; if (no_intro_b[i_deck] && deckdata[i_deck].n_reviewed == 0) { qwiz_icon_div = create_qwiz_icon_div (i_deck); } deckdata[i_deck].$qcard_card_front.html (card[side] + qwiz_icon_div); if (qwiz_icon_div) { qwiz_icon_stop_propagation (i_deck); } } else { deckdata[i_deck].$qcard_card_back.html (card[side]); } } // Firefox issue: keydown event disappears after "next card" until this is // done. Do before textentry focus! if (document_active_qwiz_qdeck) { $ (document_active_qwiz_qdeck).find ('div.focusable input').focus ().blur (); } // Reset value of textentry box, if there is one. var $textentry = $('#textentry-qdeck' + i_deck); if ($textentry.length) { $textentry.val (''); // Set focus to textentry box. Don't do if first card and no intro // (avoid scrolling page to this flashcard deck). Focus can trigger // scroll > panel open in mobile view, so set flag not to open panel. if (i_card != 0 || ! no_intro_b[i_deck]) { panel_exit_mobile_just_closed_b = true; $textentry.focus (); } // Don't let click bubble (to "flip"). if (deckdata[i_deck].click_flip_b) { $textentry.click (function (event) { event.stopPropagation (); if (debug[0]) { console.log ('[set_card_front_and_back] click event:', event); } }); } } else if (deckdata[i_deck].hangman_answer && deckdata[i_deck].hangman_answer[ii_card]) { // Disable flip. $ ('button.flip-qdeck' + i_deck).removeClass ('qbutton').addClass ('qbutton_disabled').attr ('disabled', true); deckdata[i_deck].check_answer_disabled_b = true; $hangman = deckdata[i_deck].$qcard_card_front.find ('div.qdeck_hangman'); // Initial entry - just underscores for each letter. var hangman_final_entry = deckdata[i_deck].hangman_final_entry[ii_card] var hangman_current_entry = hangman_final_entry.replace (/>[a-z0-9] <'); deckdata[i_deck].hangman_current_entry[ii_card] = hangman_current_entry.replace (/> \t<'); $hangman.find ('div.entry').html (hangman_current_entry); // Enable input in case previously disabled. Reset incorrect // characters. var $hangman_input = $hangman.find ('input'); $hangman_input.removeAttr ('disabled'); deckdata[i_deck].hangman_incorrect_chars[ii_card] = ''; // Don't let click bubble (to "flip"). if (deckdata[i_deck].click_flip_b) { $hangman_input.click (function (event) { event.stopPropagation (); if (debug[0]) { console.log ('[set_card_front_and_back] click event:', event); } }); } // Reset hangman status, hide message in case was shown. var msg; var hangman_answer = deckdata[i_deck].hangman_answer[ii_card]; if (hangman_answer.search (/[a-z]/i) != -1) { msg = T ('Type letters in the box'); } else { msg = T ('Type numbers in the box'); } $hangman.find ('div.hangman_status').html ('' + msg + ''); deckdata[i_deck].$qcard_card_front.find ('div.qdeck_hangman_msg').hide (); // Set focus to hangman input box. Don't do if first card and no intro // (avoid scrolling page to this flashcard deck). Focus can trigger // scroll > panel-open in mobile view, so set flag not to open panel. // In mobile view (at least in Android default browswer), highlights // input box and doesn't respond to first letter, so deselect. if (i_card != 0 || ! no_intro_b[i_deck]) { var $hangman_input = deckdata[i_deck].$qcard_card_front.find ('div.qdeck_hangman input'); panel_exit_mobile_just_closed_b = true; $hangman_input.focus (); $hangman_input[0].setSelectionRange (0, 0); } } // If textentry with required input/autocomplete set up autocomplete (since // just set new html). Also load metaphone.js, and -- if needed -- terms, if // haven't done so already. if (card.textentry_required_b) { init_textentry_autocomplete (i_deck, ii_card); } else { // In case previous card was textentry with required input, set button // text and title back to defaults, and reset saved text. $ ('button.flip-qdeck' + i_deck).html (T ('Flip')).attr ('title', T ('Show the other side')); card.check_answer = T ('Flip'); } // How soon does new html show? Test. /* var ms_count = 0; var width_front = 0; var width_back = 0; var now = new Date (); var start_ms = now.getTime (); var size_test = function () { var new_width_front = deckdata[i_deck].$qcard_table_front.outerWidth (); var new_width_back = deckdata[i_deck].$qcard_table_back.outerWidth (); if (new_width_front != width_front || new_width_back != width_back) { width_front = new_width_front; width_back = new_width_back; var now = new Date (); var e_ms = now.getTime () - start_ms; console.log ('[size_test] ms_count: ', ms_count, ', e_ms: ', e_ms, ', width_front: ', width_front, ', width_back: ', width_back); } if (ms_count < 200) { setTimeout (size_test, 5); } ms_count += 5; } size_test (); */ // Set card size to larger of front or back. set_container_width_height (i_deck, card.textentry_required_b); }; // ----------------------------------------------------------------------------- function textentry_set_card_back (i_deck, card) { // See with which choice the user textentry is associated, make div for // feedback for that choice visible. Hide others. var $textentry = $ ('#textentry-qdeck' + i_deck); var entry = $textentry.val (); // See if entry among choices; identify default choice ("*"). var i_choice = -1; var n_choices = card.choices.length; var i_default_choice = 0; for (var i=0; i= deckdata[i_deck].n_cards) { deckdata[i_deck].i_card = 0; } setTimeout (qname + '.process_card (' + i_deck + ')', 375); }; // ----------------------------------------------------------------------------- /* function decode_qdeck (htm, qdeck_tag) { // Get html after [qdeck] tag and before [/qdeck] tag. var len = htm.length; htm = htm.substring (qdeck_tag.length, htm.length-8); while (true) { // See if non-base64 character (blank, for now) in html. var cpos = htm.search (' '); if (cpos != -1) { break; } else { htm = atob (htm); } } // Add back [qdeck] [/qdeck] tags. htm = qdeck_tag + htm + '[/qdeck]'; return htm; } */ // ----------------------------------------------------------------------------- this.shuffle_order = function (i_deck) { // Shuffle, but make sure current card changes! var i_card = deckdata[i_deck].i_card; var ii_card = deckdata[i_deck].card_order[i_card]; while (true) { var new_ii_card = deckdata[i_deck].card_order[i_card]; if (new_ii_card != ii_card && ! deckdata[i_deck].cards[new_ii_card].got_it) { break; } deckdata[i_deck].card_order = shuffle (deckdata[i_deck].card_order); } // If showing back, change to front. if (! deckdata[i_deck].showing_front_b) { q.flip (i_deck); } q.process_card (i_deck); }; // ----------------------------------------------------------------------------- function shuffle (array) { var currentIndex = array.length , temporaryValue , randomIndex ; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor (Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } // ----------------------------------------------------------------------------- var find_matching_terms = function (request, response) { var entry = request.term.toLowerCase (); var entry_metaphone = qqc.metaphone (entry); if (debug[6]) { console.log ('[find_matching_terms] entry_metaphone; ', entry_metaphone); } // If recording this deck, record (locally) time of first interaction with // free-format input (textentry_i_deck set on focus in set_textentry_i_deck ()). if (deckdata[textentry_i_deck].qrecord_id) { if (! deckdata[textentry_i_deck].current_first_textentry_sec) { var now_sec = new Date ().getTime ()/1000.0; deckdata[textentry_i_deck].current_first_textentry_sec = now_sec; } } // See if first character of entry metaphone matches first // character of any answer metaphone. If so, determine shortest // answer metaphone that matches. var required_entry_length = 100; var required_metaphone_length = 100; var i_card = deckdata[textentry_i_deck].i_card; var ii_card = deckdata[textentry_i_deck].card_order[i_card]; var card = deckdata[textentry_i_deck].cards[ii_card]; var minlength = card.textentry_minlength; for (var i=0; i 4) { required_metaphone_length = 4; } } if (debug[6]) { console.log ('[find_matching_terms] required_entry_length:', required_entry_length, ', required_metaphone_length:', required_metaphone_length); } // Entry consisting of repeated single character doesn't count as "long". // Replace any three or more of same character in a row with just one. var deduped_entry = entry.replace (/(.)\1{2,}/gi, '\$1'); if (deduped_entry.length < required_entry_length && entry_metaphone.length < required_metaphone_length) { textentry_matches[textentry_i_deck] = []; } else { if (debug[6]) { console.log ('[find_matching_terms] request.term:', request.term, entry_metaphone, entry_metaphone.length); } textentry_matches[textentry_i_deck] = $.map (current_card_textentry_terms_metaphones[textentry_i_deck], function (term_i) { var ok_f; if (entry_metaphone == '') { // A number, or perhaps other non-alpha characters. Match similar // terms. ok_f = term_i[1] == '' || term_i[0].toLowerCase ().indexOf (entry) === 0; } else { ok_f = term_i[1].indexOf (entry_metaphone) === 0 || term_i[0].toLowerCase ().indexOf (entry) === 0; } if (ok_f) { if (debug[6]) { console.log ('[find_matching_terms] term_i:', term_i); } return term_i[0]; } }); if (debug[6]) { console.log ('[find_matching_terms] textentry_matches[textentry_i_deck]:', textentry_matches[textentry_i_deck]); } // Add dictionary result, unless flag set. if (card.use_dict_b) { // Add terms to dictionary processing. var plural_f = card.textentry_plural_b ? 1 : 0; var data = 'action=' + 'textentry_suggestions' + '&entry=' + encodeURIComponent (entry) + '&entry_metaphone=' + encodeURIComponent (entry_metaphone) + '&n_hints=' + deckdata[textentry_i_deck].textentry_n_hints + '&terms=' + encodeURIComponent (JSON.stringify (textentry_matches[textentry_i_deck])) + '&plural_f=' + plural_f; var ajaxurl = qqc.get_qwiz_param ('ajaxurl', ''); $.ajax ({ type: 'POST', url: ajaxurl, data: data, dataType: 'json', error: function (xhr, desc, exceptionobj) { if (debug[0]) { console.log ('[find_matching_terms] error desc:', desc, exceptionobj); } }, success: function (data) { textentry_matches[textentry_i_deck] = data; find_matching_terms2 (response, deduped_entry); } }); } else { find_matching_terms2 (response, deduped_entry); } } } // ----------------------------------------------------------------------------- function find_matching_terms2 (response, deduped_entry) { if (textentry_matches[textentry_i_deck].length) { lc_textentry_matches[textentry_i_deck] = textentry_matches[textentry_i_deck].map (function (item) { return item.toLowerCase (); }); if (debug[6]) { console.log ('[find_matching_terms2] textentry_matches[textentry_i_deck]:', textentry_matches[textentry_i_deck]); } } // If entry length is minlength (default 3) or more, and matches-list does // not include first correct answer, and haven't used up hints, enable hint. if (debug[6]) { console.log ('[find_matching_terms] deduped_entry.length: ', deduped_entry.length, ', textentry_matches[textentry_i_deck].length: ', textentry_matches[textentry_i_deck].length, ', deckdata[textentry_i_deck].textentry_n_hints: ', deckdata[textentry_i_deck].textentry_n_hints); } var i_card = deckdata[textentry_i_deck].i_card; var ii_card = deckdata[textentry_i_deck].card_order[i_card]; var card = deckdata[textentry_i_deck].cards[ii_card]; var minlength = card.textentry_minlength; if (deduped_entry.length >= minlength && deckdata[textentry_i_deck].textentry_n_hints < 5) { var lc_first_choice = card.all_choices[0]; if (typeof (lc_textentry_matches[textentry_i_deck]) == 'undefined' || lc_textentry_matches[textentry_i_deck].indexOf (lc_first_choice) == -1) { $ ('#textentry_hint-qdeck' + textentry_i_deck) .removeAttr ('disabled') .removeClass ('qbutton_disabled') .addClass ('qbutton').show (); } } response (textentry_matches[textentry_i_deck]); } // ----------------------------------------------------------------------------- // When menu closed: if current entry doesn't fully match anything on the last // set of matches, disable "Check answer". function menu_closed (e) { var i_card = deckdata[textentry_i_deck].i_card; var ii_card = deckdata[textentry_i_deck].card_order[i_card]; var card = deckdata[textentry_i_deck].cards[ii_card]; var lc_entry = e.target.value.toLowerCase (); // Since triggered by keyup, if entry is shorter than number of hints (user // has deleted characters), restore to hint value. var n_hints = deckdata[textentry_i_deck].textentry_n_hints; if (lc_entry.length < n_hints) { var textentry_hint = card.all_choices[0].substr (0, n_hints); e.target.value = textentry_hint; } // Do only if "Check answer" not already disabled. if (! deckdata[textentry_i_deck].check_answer_disabled_b) { if (debug[6]) { console.log ('[menu_closed] textentry_matches[textentry_i_deck]: ', textentry_matches[textentry_i_deck]); } if (lc_textentry_matches[textentry_i_deck].indexOf (lc_entry) == -1) { $ ('button.flip-qdeck' + textentry_i_deck).removeClass ('qbutton').addClass ('qbutton_disabled'); deckdata[textentry_i_deck].check_answer_disabled_b = true; } } // Since done on keyup (that is, if any typing), cancel automatic // presentation (if in progress) if have minlength characters. // Do only if there is a timeout in progress. if (show_hint_timeout[textentry_i_deck]) { var $textentry = $ ('#textentry-qdeck' + textentry_i_deck); var n_chars = $textentry.val ().length; var minlength = card.textentry_minlength; if (n_chars >= minlength) { clearTimeout (show_hint_timeout[textentry_i_deck]); show_hint_timeout[textentry_i_deck] = 0; } } } // ----------------------------------------------------------------------------- // When suggestion menu shown: (1) if the matches list shown includes the first // correct answer, then set flag that hint not needed; (2) if current entry // _fully_ matches anything on the matches list shown, then enable "Check // answer"; otherwise disable "Check answer". function menu_shown (e) { // If recording and this is first interaction (no-intro, single-card deck), // record as start time. if (debug[0]) { console.log ('[menu_shown] textentry_i_deck:', textentry_i_deck, ', document_qwiz_user_logged_in_b:', document_qwiz_user_logged_in_b); console.log ('[menu_shown] deckdata[textentry_i_deck].record_start_b:', deckdata[textentry_i_deck].record_start_b); } if (deckdata[textentry_i_deck].record_start_b && document_qwiz_user_logged_in_b) { deckdata[textentry_i_deck].record_start_b = false; var now_sec = new Date ().getTime ()/1000.0; var data = {qrecord_id_ok: deckdata[textentry_i_deck].qrecord_id_ok, type: 'start', now_sec: now_sec}; qqc.jjax (qname, textentry_i_deck, deckdata[textentry_i_deck].qrecord_id, 'record_qcard', data); } // Lowercase entry and matches list. var lc_entry = e.target.value.toLowerCase (); // Does matches list include first choice in list of possible choices? var i_card = deckdata[textentry_i_deck].i_card; var ii_card = deckdata[textentry_i_deck].card_order[i_card]; var card = deckdata[textentry_i_deck].cards[ii_card]; var lc_first_choice = card.all_choices[0]; if (lc_textentry_matches[textentry_i_deck].indexOf (lc_first_choice) != -1) { $ ('#textentry_hint-qdeck' + textentry_i_deck) .attr ('disabled', true) .removeClass ('qbutton') .addClass ('qbutton_disabled'); } // Enable/disable "Flip/Check answer", and toggle button text between // "Flip/Check answer" and "Type 3+ letters" or alternative. if (lc_textentry_matches[textentry_i_deck].indexOf (lc_entry) != -1) { $ ('button.flip-qdeck' + textentry_i_deck) .removeAttr ('disabled') .removeClass ('qbutton_disabled') .addClass ('qbutton') .html (T ('Flip')); card.check_answer = T ('Flip'); deckdata[textentry_i_deck].check_answer_disabled_b = false; } else { card.check_answer = card.save_check_answer; $ ('button.flip-qdeck' + textentry_i_deck) .removeClass ('qbutton') .addClass ('qbutton_disabled') .html (card.check_answer); deckdata[textentry_i_deck].check_answer_disabled_b = true; } } // ----------------------------------------------------------------------------- // When item selected, enable check answer and set text. function item_selected () { $ ('button.flip-qdeck' + textentry_i_deck) .removeAttr ('disabled') .removeClass ('qbutton_disabled') .addClass ('qbutton') .html (T ('Flip')); deckdata[textentry_i_deck].check_answer_disabled_b = false; } // ----------------------------------------------------------------------------- this.keep_next_button_active = function () { next_button_active_b = true; $ ('button.got_it').attr ('disabled', false).removeClass ('qbutton_disabled').addClass ('qbutton'); } // ----------------------------------------------------------------------------- function get_attr (htm, attr_name) { var attr_value = qqc.get_attr (htm, attr_name, deckdata); errmsgs = errmsgs.concat (deckdata.additional_errmsgs); return attr_value; } // ----------------------------------------------------------------------------- this.set_deckdata = function (i_deck, variable, value) { deckdata[i_deck][variable] = value; } // ----------------------------------------------------------------------------- function T (string) { return qqc.T (string); } // ----------------------------------------------------------------------------- function Tplural (word, plural_word, n) { return qqc.Tplural (word, plural_word, n); } // ============================================================================= // Close - isolate namespace. }; qcardf.call (qcard_); /* ============================================================================= * Flipping Cards 3D * By David Blanco * * Contact: http://codecanyon.net/user/davidbo90 * * Created: January 2013 * * Copyright (c) 2013, David Blanco. All rights reserved. * Released under CodeCanyon License http://codecanyon.net/ * * ======================================================= */ flipping_cards = function() { $ = jQuery; // CHECKS FOR FALLBACK ----------------------- var debug = false; var fallback = false; var dk_fallback = false; var supportsPerspective; var testDiv; var is_chrome; var is_safari; // ----------------------------------------------------------------------- function getInternetExplorerVersion() // Returns the version of Windows Internet Explorer or a -1 // (indicating the use of another browser). { var rv = -1; // Return value assumes failure. if (navigator.appName == 'Microsoft Internet Explorer'); { var ua = navigator.userAgent; var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); if (re.exec(ua) != null) rv = parseFloat( RegExp.$1 ); } var isAtLeastIE11 = !!(navigator.userAgent.match(/Trident/) && !navigator.userAgent.match(/MSIE/)); if(isAtLeastIE11){ rv = 11; //if it is IE 11 } return rv; } // ----------------------------------------------------------------------- if( getInternetExplorerVersion() != -1 ){ //IF IS IE if (debug) { console.log ('is IE'); } fallback = true; dk_fallback = true; } // ----------------------------------------------------------------------- var supports = (function() { var div = document.createElement('div'), vendors = 'Khtml Ms O Moz Webkit'.split(' '), len = vendors.length; return function(prop) { if ( prop in div.style ) return true; prop = prop.replace(/^[a-z]/, function(val) { return val.toUpperCase(); }); while(len--) { if ( vendors[len] + prop in div.style ) { // browser supports box-shadow. Do what you need. // Or use a bang (!) to test if the browser doesn't. return true; } } return false; }; })(); // ----------------------------------------------------------------------- if ( !supports('backfaceVisibility') ) { //IF IT DOES NOT SUPPORT BACKFACE VISIBILITY if (debug) { console.log ('! backfaceVisibility'); } fallback = true; } is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1 && !!window.chrome; is_safari = navigator.userAgent.toLowerCase().indexOf('safari') > -1 && !window.chrome; // If is Chrome or Safari, set up div to test 3D support. if (is_chrome || is_safari) { testDiv = document.createElement('div'); var properties = ['perspectiveProperty', 'WebkitPerspective']; for (var i = properties.length - 1; i >= 0; i--){ supportsPerspective = supportsPerspective ? supportsPerspective : testDiv.style[properties[i]] != undefined; }; if (! supportsPerspective) { fallback = true; if (debug) { console.log ('!perspectiveProperty'); } } else { var testStyle = document.createElement('style'); testStyle.textContent = '@media (-webkit-transform-3d){#test3d{height:3px}}'; document.getElementsByTagName('head')[0].appendChild(testStyle); testDiv.id = 'test3d'; document.body.appendChild(testDiv); } } // ----------------------------------------------------------------------- flipping_cards_part2 = function () { // If is Chrome or is Safari, and good so far, check if supports // 3D transform. if (is_chrome || is_safari){ if (! fallback) { // If Chrome on Mac, give up. DK 2014-09-05. var is_mac = navigator.platform.toLowerCase().indexOf('mac') != -1; if (is_chrome && is_mac) { fallback = true; } retOffsetHeight = testDiv.offsetHeight === 3; if (! retOffsetHeight) { fallback = true; if (debug) { console.log ('!3D_transform'); } } // Clean-up. testStyle.parentNode.removeChild(testStyle); testDiv.parentNode.removeChild(testDiv); } } if( fallback ){ jQuery('div.card-container').addClass('noCSS3Container'); jQuery('.card').addClass('noCSS3Card'); jQuery('.card').children('div').addClass('noCSS3Sides'); jQuery('.back').hide(); } $('.over').parents('.card-container').on('mouseenter',function(){ $this = $(this); if(!$this.hasClass('mouseenter')){ $this.addClass('mouseenter'); } direction($this.find('.over')); }); $('.over').parents('.card-container').on('mouseleave',function(){ $this = $(this); if($this.hasClass('mouseenter')){ direction($this.find('.over')); } }); $('.click').on('click', function(){ $this = $(this); direction($this); }); //Stop propagation $('.click').on('click', '.ignoreEvent', function(e){ e.stopPropagation(); }); $('.card').on('click', '.cbutton', function(e){ e.preventDefault(); $this = $(this); direction($this.parents('.card')); }); var intervals = Array(); function direction($this, index){ $this.stop(true, true); if($this.data('autoflip') != undefined && index != undefined){ intervals[index] = setTimeout(function(){ direction($this, index); }, $this.data('autoflip')); } //In auto flip feature if it has a mouseover if($this.data('mouse') == 'true'){ return; } if( fallback ){ var div_front = $this.find ('div.front'); var div_back = $this.find ('div.back'); var toggle_front = function () { div_front.toggle ('clip'); }; var toggle_back = function () { div_back.toggle ('clip'); }; if (div_front.is (':visible')) { toggle_front (); setTimeout (toggle_back, 500); } else { toggle_back (); setTimeout (toggle_front, 500); } //$this.find('div.front').fadeToggle(); //$this.find('div.back').fadeToggle(); return; } if($this.data('direction') === 'right'){ $this.toggleClass('flipping-right'); }else if($this.data('direction') === 'left'){ $this.toggleClass('flipping-left'); }else if($this.data('direction') === 'top'){ $this.toggleClass('flipping-top'); }else if($this.data('direction') === 'bottom'){ $this.toggleClass('flipping-bottom'); } } //AUTO FLIP FEATURE -----------------------> var card = $('.card[data-autoflip]'); function start(){ card.each(function(index){ $this = $(this); (function(c){ var autoStart = c.data('start'); if(autoStart == undefined){ autoStart = c.data('autoflip'); } intervals[index] = setTimeout(function(){ direction(c, index); }, autoStart); })($this); }); } start(); var restart = function() { //clear all intervals and start again for(var i=0; i