1/** 2 * sso_login_order.js â DOM-reorder social login buttons on /login (Google first). 3 * 4 * Fetches per-provider Display Order integers from the DB (via a guest endpoint) 5 * and PHYSICALLY RE-ORDERS the provider button wrappers in the DOM so the 6 * lower-order provider (Google=10) precedes the higher (Facebook=20). 7 * 8 * NATIVE LAYOUT PRESERVED: this script writes NO layout CSS. It does not set 9 * display:flex, flex-direction, or style.order on the container or any wrapper. 10 * Reordering is done by swapping the wrappers into their existing sibling slots 11 * (via comment-node placeholders), so Frappe's native spacing â including 12 * first-child / last-child / adjacent-sibling (.login-button-wrapper + ...) 13 * margin rules â is untouched. (Earlier revision forced flex `order`, which 14 * collapsed that native inter-button spacing.) 15 * 16 * Frappe serves /login server-rendered (no_cache=1 for ERP-097 safety). 17 * Buttons are present at DOMContentLoaded â no waiting for dynamic injection. 18 * 19 * ERP-090 lesson: no jQuery. Pure vanilla JS only. 20 */ 21(function () { 22 "use strict"; 23 24 // Only run on /login 25 if (window.location.pathname.replace(/\/+$/, "") !== "/login") { 26 return; 27 } 28 29 /** Fallback order constants if the DB fetch fails or a provider has no value. */ 30 var FALLBACK = { google: 10, facebook: 20 }; 31 var DEFAULT_ORDER = 100; 32 33 /** 34 * Determine the effective order for a provider. 35 * DB value wins if present and positive; then FALLBACK; then DEFAULT_ORDER. 36 */ 37 function effectiveOrder(providerName, dbVal) { 38 if (dbVal && dbVal > 0) { 39 return dbVal; 40 } 41 if (FALLBACK[providerName] != null) { 42 return FALLBACK[providerName]; 43 } 44 return DEFAULT_ORDER; 45 } 46 47 /** 48 * Given the anchor element's classList and the order map keys, return the 49 * matching provider key (e.g. "google", "facebook"), or null. 50 * Matches btn-<key> ONLY for keys present in orderMap to avoid false matches 51 * against btn-block, btn-sm, btn-default, btn-login-option, etc. 52 */ 53 function detectProviderKey(anchor, mapKeys) { 54 var classes = Array.prototype.slice.call(anchor.classList); 55 for (var i = 0; i < mapKeys.length; i++) { 56 var key = mapKeys[i]; 57 if (classes.indexOf("btn-" + key) !== -1) { 58 return key; 59 } 60 } 61 return null; 62 } 63 64 function applyOrder(orderMap) { 65 var mapKeys = Object.keys(orderMap); 66 if (!mapKeys.length) return; 67 68 // All .social-login-buttons containers (there may be multiple: OAuth + email-link) 69 var containers = document.querySelectorAll(".social-login-buttons"); 70 for (var ci = 0; ci < containers.length; ci++) { 71 var container = containers[ci]; 72 var children = Array.prototype.slice.call(container.children); 73 74 // Collect provider wrappers in current DOM order, with their order value. 75 var providers = []; 76 for (var i = 0; i < children.length; i++) { 77 var child = children[i]; 78 if (!child.classList || !child.classList.contains("login-button-wrapper")) { 79 continue; 80 } 81 var anchor = child.querySelector("a"); 82 if (!anchor) continue; 83 var key = detectProviderKey(anchor, mapKeys); 84 if (key === null) { 85 // Non-provider wrapper (e.g. email-link button) â leave untouched. 86 continue; 87 } 88 providers.push({ el: child, order: effectiveOrder(key, orderMap[key]) }); 89 } 90 91 // Single provider (or none) â nothing to reorder; native layout untouched. 92 if (providers.length < 2) continue; 93 94 // Desired order: ascending by order value (stable for equal values). 95 var sorted = providers.slice().sort(function (a, b) { 96 return a.order - b.order; 97 }); 98 99 // Already in desired order â skip to avoid needless DOM churn. 100 var alreadySorted = true; 101 for (var s = 0; s < providers.length; s++) { 102 if (providers[s].el !== sorted[s].el) { 103 alreadySorted = false;
104 break; 105 } 106 } 107 if (alreadySorted) continue; 108 109 // Reorder by swapping providers into their OWN existing sibling slots, 110 // preserving the positions of any non-provider siblings. Place a comment 111 // placeholder at each provider slot, then drop the sorted providers in. 112 var placeholders = []; 113 for (var p = 0; p < providers.length; p++) { 114 var ph = document.createComment("sso-slot"); 115 container.replaceChild(ph, providers[p].el); // detaches providers[p].el 116 placeholders.push(ph); 117 } 118 for (var k = 0; k < placeholders.length; k++) { 119 container.replaceChild(sorted[k].el, placeholders[k]); 120 } 121 } 122 } 123 124 function run() { 125 try { 126 fetch( 127 "/api/method/cslshoes_erp.api.social_login_order.get_display_order", 128 { headers: { Accept: "application/json" } } 129 ) 130 .then(function (resp) { 131 if (!resp.ok) return null; 132 return resp.json(); 133 }) 134 .then(function (data) { 135 if (data && data.message) { 136 applyOrder(data.message); 137 } else { 138 // Fetch succeeded but no map â apply FALLBACK for known providers 139 applyOrder(FALLBACK); 140 } 141 }) 142 .catch(function () { 143 // Network error â fail silent, leave buttons in stock order 144 }); 145 } catch (e) { 146 // Defensive top-level catch â never break the login page 147 } 148 } 149 150 if (document.readyState === "loading") { 151 document.addEventListener("DOMContentLoaded", run); 152 } else { 153 run(); 154 } 155})();
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.