{"_id":"56a2c51906150b0d002ad264","project":"5668fab608f90021008e882f","__v":15,"category":{"_id":"569742bd0b09a41900b2446c","__v":9,"pages":["5697455824490c3700170a69","569747128400d52d00dd56a4","5697476b8560a60d00e2c262","569747a4c8ded91700307b92","5697489c59a6692d003fad96","56a1500244f3d80d00a2c388","56a2c51906150b0d002ad264","56b548b75f1cf00d00cc4773","56b54ae37719bb1900143093"],"project":"5668fab608f90021008e882f","version":"5668fab608f90021008e8832","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2016-01-14T06:39:57.496Z","from_sync":false,"order":6,"slug":"tuning-liftigniter-javascript","title":"Rendering,Tracking and A/B Testing Widgets (JavaScript)"},"parentDoc":null,"user":"56839cf74aecbd0d00a4659e","version":{"_id":"5668fab608f90021008e8832","__v":19,"project":"5668fab608f90021008e882f","createdAt":"2015-12-10T04:08:22.769Z","releaseDate":"2015-12-10T04:08:22.769Z","categories":["5668fab708f90021008e8833","569740f124490c3700170a64","569742b58560a60d00e2c25d","569742bd0b09a41900b2446c","569742cd69393517000c82b3","569742f459a6692d003fad8f","569743020b09a41900b2446d","5697430b69393517000c82b5","56a17776470ae00d00c30642","56a2c48a831e2a0d0069b1ad","56b535757bccae0d00e9a1cd","56e1ff6aa49fdc0e005746b5","57e1c88115bf6522002a5e4e","57fa65275ba65a17008b988f","57fbeea34002550e004c032e","58474584889b6c2d00fb86e9","58475dcc64157f0f002f1907","587e7b5158666c2700965d4e","58a349fc30852819007ba083"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"1.18.0","version":"1.18"},"updates":[],"next":{"pages":[],"description":""},"createdAt":"2016-01-23T00:11:05.974Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"settings":"","results":{"codes":[]},"auth":"required","params":[],"url":""},"isReference":false,"order":1,"body":"[block:callout]\n{\n  \"type\": \"success\",\n  \"title\": \"Checklist\",\n  \"body\": \"So far, you have:\\n<ul><li>Installed our Javascript beacon and verified that we are collecting data. </li>\\n<li>Tested that you are receiving recommendations. </li>\\n<li>Verified that the items and fields we are returning are correct. </li>\\n</ul>\"\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"A/B Testing Methodology\",\n  \"body\": \"We strongly recommend using our user hash for A/B testing. **If you are using any other method for A/B testing, we request that you get in touch with the LiftIgniter team so we can review your bucketing methodology and understand exactly how it might affect the interpretability of results.**\"\n}\n[/block]\nBelow, we include sample instructions that can be used for either of these two use cases:\n\n* **You want to overwrite an existing recommendation widget** on 50% (or an appropriate percentage) of your users with LiftIgniter recommendations, while preserving the same styling.\n* **You want to power a new recommendation widget with LiftIgniter recommendations** on 50% (or an appropriate percentage) of your users, you want to populate that HTML template with LiftIgniter recommendations. For the other 50% of users, you want to show nothing, replicating your pre-LiftIgniter user experience.\n\nThe sample code on this page covers not just the A/B testing but also all the other code components needed to launch LiftIgniter: [registering and fetching recommendations](doc:quick-testing-liftigniter), [rendering recommendations](doc:rendering-widgets), and [tracking recommendations](doc:tracking-widgets). You can read the documentations on those components separately in order to get more detail on how they work.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"1. How we split Users\"\n}\n[/block]\nThe standard approach with A/B testing is to split by user. This makes the most sense, because we want to compare a world where users consistently see our recommendations, versus a world where they consistently do not see our recommendations.\n\nLiftIgniter SDK offers a user hash that you can use to split users between our slice and the control slice. The user hash is a 32-bit integer hash of the user cookie. It is accessible as:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"$p('userHash')\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nLiftIgniter SDK also offers its own A/B test slice that is obtained by taking the hash modulo 100. This creates a total of 100 bins numbered 0 through 99. This slice is accessible using a callback function in our SDK:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"$p(\\\"abTestSlice\\\", {callback:function(x){console.log(x);}})\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nWe strongly recommend using our user hash for A/B testing. If you are using any other method for A/B testing, **we request that you get in touch with the LiftIgniter team** so we can review your bucketing methodology and understand exactly how it might affect the interpretability of results.\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Forcing A/B hash\",\n  \"body\": \"If you would like to force a specific hash value to make Q/A or recommendation testing easier, you can add URL parameter `igniter_abhash`. i.e. adding `&igniter_abhash=10` to URL on browser will force the hash value to be 10. So if you've setup the `abTestSlice` logic to show LI recommendations when value is less than 50, setting it to any value b/w 0~49 will show LI recommendation.\"\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"2. LiftIgniter vs Your Recommendation (Or Nothing)\"\n}\n[/block]\nThe instructions below will work in both the cases we cover, namely:\n\n* You want to overwrite an existing recommendation widget\n* You want to power a new recommendation widget with LiftIgniter recommendations\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<div id=\\\"li-recommendation-unit\\\">\\n  <div class='recommended_item'>\\n    <a class='headline' href='//url-1'>Title 1</a>\\n  </div>\\n\\n  <div class='recommended_item'>\\n    <a class='headline' href='//url-2'>Title 1</a>\\n  </div>\\n\\n  <div class='recommended_item'>\\n    <a class='headline' href='//url-3'>Title 3</a>\\n  </div>\\n\\n  <div class='recommended_item'>\\n    <a class='headline' href='//url-4'>Title 4</a>\\n  </div>\\n</div>\",\n      \"language\": \"html\"\n    }\n  ]\n}\n[/block]\nSuppose you want to run an A/B test on this area: you want to compare the recommendations you show by default against LiftIgniter’s recommendations.\n\n**Step 1.** Tag the items you want to replace (e.g. by adding `li-widget-item` to the class):\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<div id=\\\"li-recommendation-unit\\\">\\n  <div class='recommended_item li-widget-item'>\\n    <a class='headline' href='//url-1'>Title 1</a>\\n  </div>\\n\\n  <div class='recommended_item li-widget-item'>\\n    <a class='headline' href='//url-2'>Title 1</a>\\n  </div>\\n\\n  <div class='recommended_item li-widget-item'>\\n    <a class='headline' href='//url-3'>Title 3</a>\\n  </div>\\n\\n  <div class='recommended_item li-widget-item'>\\n    <a class='headline' href='//url-4'>Title 4</a>\\n  </div>\\n</div>\",\n      \"language\": \"html\"\n    }\n  ]\n}\n[/block]\n**Step 2.** Add a mustache template matching the tagged units to your page:\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Overwriting vs Rendering new widget\",\n  \"body\": \"For overwriting an existing widget, the template below will be a HTML copy of the existing widget in a mustache template (to make sure that newly rendered recommendation looks the same).\\n\\nFor rendering a new widget, you can determine the template based on the HTML design you want. You can see some general suggestions on placement in our guidelines on [Click-Through Rate (CTR)](doc:click-through-rate-ctr).\"\n}\n[/block]\n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<script type=\\\"application/mustache\\\" id=\\\"recommended-item-template\\\">\\n  <div class='recommended_item'>\\n    <a class='headline' href='{{url}}'>{{title}}</a>\\n  </div>\\n</script>\",\n      \"language\": \"html\"\n    }\n  ]\n}\n[/block]\n**Step 3.** Write callback function to render recommendations\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"// Renders the recommendations and overwrites innerHTML of area marked with 'li-widget-item'.\\nvar rendering_callback = function(resp) {\\n    var els = document.querySelectorAll('#li-recommendation-unit > div.li-widget-item');\\n    var template = document.querySelector('#recommended-item-template').innerHTML;\\n  \\n    for (var i = 0; i < els.length && i < resp.items.length; ++i) {\\n        // Basically Mustache.render(template, resp.items[i]); \\n      \\tels[i].innerHTML = $p('render', template, resp.items[i]);\\n    }\\n}\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n**Step 4.** Put in the registering and tracking logic for all slices\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"var trackAlgo = function(algorithm) {\\n  \\t$p('track', {\\n    // marked for replacement during A/B test.\\n    \\telements: document.querySelectorAll('#li-recommendation-unit > div.li-widget-item'),\\n    \\tname: 'default-widget',\\n    \\tsource: algorithm,\\n    \\t_debug: true\\n  \\t});\\n}\\n\\nvar abTestHandler = function(slice) {\\n    // Slice is modulo 100 of the hash.\\n  \\t// So you can set slice to 0 and 100 to check LI and base slice respectively\\n    if (slice < 50) {\\n        // Register call to overwrite widget being tested with LI recommendations,\\n        // and track metrics appropriately.\\n    \\t\\t$p('register', {\\n        \\t\\tmax: 4,\\n          \\twidget: 'default-widget',\\n          \\tcallback: function(resp) {\\n                // You might wish to wrap the code in this callback inside jQuery, to handle load order issues\\n              \\t// See code above\\n            \\t\\trendering_callback(resp);\\n            \\t\\t// track overwritten areas as 'LI' slice.\\n            \\t\\ttrackAlgo('LI');\\n         \\t\\t}\\n        });\\n    } else {\\n        // You might wish to wrap the code in this callback inside jQuery, to handle load order issues\\n        // track marked areas as 'base' or original slice.\\n        trackAlgo('base');\\n    }\\n    // Executes all registered calls.\\n    $p('fetch');\\n}\\n\\n// Decide which slice the current user is on.\\n// $p('userHash') hashes user cookie id to a random 32-bit integer.\\n// $p('abTestSlice') returns the absolute value of this number mod 100\\n$p('abTestSlice', {\\n                 callback: abTestHandler\\n               }\\n);\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"danger\",\n  \"title\": \"Load order issues\",\n  \"body\": \"Keep in mind that if your recommendations that you intend to overwrite have not loaded when the callback is executed, LiftIgniter's recommendations will not be able to replace them. You can remedy the problem by wrapping the execution inside a jQuery. For more, see our documentation on [load order](doc:load-order).\"\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"body\": \"Specific names in Steps 1-3 will differ based on the names you are using for your widget design. In Step 4, the name of the query selector in the `trackAlgo` function also needs to match these names. The abTestHandler code is completely generic.\",\n  \"title\": \"Notes\"\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Check that tracking works\",\n  \"body\": \"You can test that tracking works by setting _debug to true and seeing that you get alerts for the visible and click events. More detailed information is in our [tracking documentation](doc:tracking-widgets).\"\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Making changes based on your needs\",\n  \"body\": \"You should be able to use the above code as a starting point and modify it for other use cases. In particular:\\n<ul>\\n<li>If you want to use your own bucketing strategy, edit the first line (the if condition) of your abTestHandler accordingly.</li>\\n<li>If you want to take other specific actions based on the slice, include them in the if block of the abTestHandler (for our slice) and in the else block (for the control slice). Examples of additional actions you might want to take are callbacks to other analytics services so you can use them to verify our numbers.</li>\\n</ul>\"\n}\n[/block]\nYou can observe the results of the A/B test on the LiftIgniter [dashboard](https://lab.liftigniter.com).","excerpt":"","slug":"ab-testing","type":"basic","title":"Full Integration with A/B Testing"}

Full Integration with A/B Testing


[block:callout] { "type": "success", "title": "Checklist", "body": "So far, you have:\n<ul><li>Installed our Javascript beacon and verified that we are collecting data. </li>\n<li>Tested that you are receiving recommendations. </li>\n<li>Verified that the items and fields we are returning are correct. </li>\n</ul>" } [/block] [block:callout] { "type": "warning", "title": "A/B Testing Methodology", "body": "We strongly recommend using our user hash for A/B testing. **If you are using any other method for A/B testing, we request that you get in touch with the LiftIgniter team so we can review your bucketing methodology and understand exactly how it might affect the interpretability of results.**" } [/block] Below, we include sample instructions that can be used for either of these two use cases: * **You want to overwrite an existing recommendation widget** on 50% (or an appropriate percentage) of your users with LiftIgniter recommendations, while preserving the same styling. * **You want to power a new recommendation widget with LiftIgniter recommendations** on 50% (or an appropriate percentage) of your users, you want to populate that HTML template with LiftIgniter recommendations. For the other 50% of users, you want to show nothing, replicating your pre-LiftIgniter user experience. The sample code on this page covers not just the A/B testing but also all the other code components needed to launch LiftIgniter: [registering and fetching recommendations](doc:quick-testing-liftigniter), [rendering recommendations](doc:rendering-widgets), and [tracking recommendations](doc:tracking-widgets). You can read the documentations on those components separately in order to get more detail on how they work. [block:api-header] { "type": "basic", "title": "1. How we split Users" } [/block] The standard approach with A/B testing is to split by user. This makes the most sense, because we want to compare a world where users consistently see our recommendations, versus a world where they consistently do not see our recommendations. LiftIgniter SDK offers a user hash that you can use to split users between our slice and the control slice. The user hash is a 32-bit integer hash of the user cookie. It is accessible as: [block:code] { "codes": [ { "code": "$p('userHash')", "language": "javascript" } ] } [/block] LiftIgniter SDK also offers its own A/B test slice that is obtained by taking the hash modulo 100. This creates a total of 100 bins numbered 0 through 99. This slice is accessible using a callback function in our SDK: [block:code] { "codes": [ { "code": "$p(\"abTestSlice\", {callback:function(x){console.log(x);}})", "language": "javascript" } ] } [/block] We strongly recommend using our user hash for A/B testing. If you are using any other method for A/B testing, **we request that you get in touch with the LiftIgniter team** so we can review your bucketing methodology and understand exactly how it might affect the interpretability of results. [block:callout] { "type": "info", "title": "Forcing A/B hash", "body": "If you would like to force a specific hash value to make Q/A or recommendation testing easier, you can add URL parameter `igniter_abhash`. i.e. adding `&igniter_abhash=10` to URL on browser will force the hash value to be 10. So if you've setup the `abTestSlice` logic to show LI recommendations when value is less than 50, setting it to any value b/w 0~49 will show LI recommendation." } [/block] [block:api-header] { "type": "basic", "title": "2. LiftIgniter vs Your Recommendation (Or Nothing)" } [/block] The instructions below will work in both the cases we cover, namely: * You want to overwrite an existing recommendation widget * You want to power a new recommendation widget with LiftIgniter recommendations [block:code] { "codes": [ { "code": "<div id=\"li-recommendation-unit\">\n <div class='recommended_item'>\n <a class='headline' href='//url-1'>Title 1</a>\n </div>\n\n <div class='recommended_item'>\n <a class='headline' href='//url-2'>Title 1</a>\n </div>\n\n <div class='recommended_item'>\n <a class='headline' href='//url-3'>Title 3</a>\n </div>\n\n <div class='recommended_item'>\n <a class='headline' href='//url-4'>Title 4</a>\n </div>\n</div>", "language": "html" } ] } [/block] Suppose you want to run an A/B test on this area: you want to compare the recommendations you show by default against LiftIgniter’s recommendations. **Step 1.** Tag the items you want to replace (e.g. by adding `li-widget-item` to the class): [block:code] { "codes": [ { "code": "<div id=\"li-recommendation-unit\">\n <div class='recommended_item li-widget-item'>\n <a class='headline' href='//url-1'>Title 1</a>\n </div>\n\n <div class='recommended_item li-widget-item'>\n <a class='headline' href='//url-2'>Title 1</a>\n </div>\n\n <div class='recommended_item li-widget-item'>\n <a class='headline' href='//url-3'>Title 3</a>\n </div>\n\n <div class='recommended_item li-widget-item'>\n <a class='headline' href='//url-4'>Title 4</a>\n </div>\n</div>", "language": "html" } ] } [/block] **Step 2.** Add a mustache template matching the tagged units to your page: [block:callout] { "type": "info", "title": "Overwriting vs Rendering new widget", "body": "For overwriting an existing widget, the template below will be a HTML copy of the existing widget in a mustache template (to make sure that newly rendered recommendation looks the same).\n\nFor rendering a new widget, you can determine the template based on the HTML design you want. You can see some general suggestions on placement in our guidelines on [Click-Through Rate (CTR)](doc:click-through-rate-ctr)." } [/block] [block:code] { "codes": [ { "code": "<script type=\"application/mustache\" id=\"recommended-item-template\">\n <div class='recommended_item'>\n <a class='headline' href='{{url}}'>{{title}}</a>\n </div>\n</script>", "language": "html" } ] } [/block] **Step 3.** Write callback function to render recommendations [block:code] { "codes": [ { "code": "// Renders the recommendations and overwrites innerHTML of area marked with 'li-widget-item'.\nvar rendering_callback = function(resp) {\n var els = document.querySelectorAll('#li-recommendation-unit > div.li-widget-item');\n var template = document.querySelector('#recommended-item-template').innerHTML;\n \n for (var i = 0; i < els.length && i < resp.items.length; ++i) {\n // Basically Mustache.render(template, resp.items[i]); \n \tels[i].innerHTML = $p('render', template, resp.items[i]);\n }\n}", "language": "javascript" } ] } [/block] **Step 4.** Put in the registering and tracking logic for all slices [block:code] { "codes": [ { "code": "var trackAlgo = function(algorithm) {\n \t$p('track', {\n // marked for replacement during A/B test.\n \telements: document.querySelectorAll('#li-recommendation-unit > div.li-widget-item'),\n \tname: 'default-widget',\n \tsource: algorithm,\n \t_debug: true\n \t});\n}\n\nvar abTestHandler = function(slice) {\n // Slice is modulo 100 of the hash.\n \t// So you can set slice to 0 and 100 to check LI and base slice respectively\n if (slice < 50) {\n // Register call to overwrite widget being tested with LI recommendations,\n // and track metrics appropriately.\n \t\t$p('register', {\n \t\tmax: 4,\n \twidget: 'default-widget',\n \tcallback: function(resp) {\n // You might wish to wrap the code in this callback inside jQuery, to handle load order issues\n \t// See code above\n \t\trendering_callback(resp);\n \t\t// track overwritten areas as 'LI' slice.\n \t\ttrackAlgo('LI');\n \t\t}\n });\n } else {\n // You might wish to wrap the code in this callback inside jQuery, to handle load order issues\n // track marked areas as 'base' or original slice.\n trackAlgo('base');\n }\n // Executes all registered calls.\n $p('fetch');\n}\n\n// Decide which slice the current user is on.\n// $p('userHash') hashes user cookie id to a random 32-bit integer.\n// $p('abTestSlice') returns the absolute value of this number mod 100\n$p('abTestSlice', {\n callback: abTestHandler\n }\n);", "language": "javascript" } ] } [/block] [block:callout] { "type": "danger", "title": "Load order issues", "body": "Keep in mind that if your recommendations that you intend to overwrite have not loaded when the callback is executed, LiftIgniter's recommendations will not be able to replace them. You can remedy the problem by wrapping the execution inside a jQuery. For more, see our documentation on [load order](doc:load-order)." } [/block] [block:callout] { "type": "info", "body": "Specific names in Steps 1-3 will differ based on the names you are using for your widget design. In Step 4, the name of the query selector in the `trackAlgo` function also needs to match these names. The abTestHandler code is completely generic.", "title": "Notes" } [/block] [block:callout] { "type": "warning", "title": "Check that tracking works", "body": "You can test that tracking works by setting _debug to true and seeing that you get alerts for the visible and click events. More detailed information is in our [tracking documentation](doc:tracking-widgets)." } [/block] [block:callout] { "type": "info", "title": "Making changes based on your needs", "body": "You should be able to use the above code as a starting point and modify it for other use cases. In particular:\n<ul>\n<li>If you want to use your own bucketing strategy, edit the first line (the if condition) of your abTestHandler accordingly.</li>\n<li>If you want to take other specific actions based on the slice, include them in the if block of the abTestHandler (for our slice) and in the else block (for the control slice). Examples of additional actions you might want to take are callbacks to other analytics services so you can use them to verify our numbers.</li>\n</ul>" } [/block] You can observe the results of the A/B test on the LiftIgniter [dashboard](https://lab.liftigniter.com).