[{"data":1,"prerenderedAt":3643},["ShallowReactive",2],{"article_list_how-to_":3},[4,1219,2007],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"publishDate":6,"tags":11,"excerpt":15,"body":16,"_type":1210,"_id":1211,"_source":1212,"_file":1213,"_stem":1214,"_extension":1215,"author":1216},"/dpopowich/2021-07-30/data-collector","2021-07-30",false,"","Asynchronous Python - A Real World Example","A dive into a real example of async Python usage.",[12,13,14],"python","how-to","async","We have a customer that developed a hardware device to make physical measurements.  Some years ago we wrote a suite of software tools for the customer: a tablet application for configuring the hardware device, a django web server to receive uploaded XML documents generated by the device, and a user-facing web application (using the same django server), providing reporting and data analytics.",{"type":17,"children":18,"toc":1205},"root",[19,24,32,37,42,51,97,103,119,124,180,185,193,198,234,240,263,268,395,408,418,423,790,795,1182,1186,1191,1199],{"type":20,"tag":21,"props":22,"children":23},"element","toc",{},[],{"type":20,"tag":25,"props":26,"children":28},"h2",{"id":27},"introduction",[29],{"type":30,"value":31},"text","Introduction",{"type":20,"tag":33,"props":34,"children":35},"p",{},[36],{"type":30,"value":15},{"type":20,"tag":33,"props":38,"children":39},{},[40],{"type":30,"value":41},"The architecture roughly looks like this (simplified for clarity):",{"type":20,"tag":33,"props":43,"children":44},{},[45],{"type":20,"tag":46,"props":47,"children":50},"img",{"alt":48,"src":49},"Legacy App Architecture","/dpopowich/2021-07-30/img/legacy.png",[],{"type":20,"tag":52,"props":53,"children":54},"ol",{},[55,67,77,87],{"type":20,"tag":56,"props":57,"children":58},"li",{},[59,65],{"type":20,"tag":60,"props":61,"children":62},"strong",{},[63],{"type":30,"value":64},"H/W device:",{"type":30,"value":66}," takes data measurements, generates an XML document, and uploads the XML document to the (3) Web App via a cellular WAN.",{"type":20,"tag":56,"props":68,"children":69},{},[70,75],{"type":20,"tag":60,"props":71,"children":72},{},[73],{"type":30,"value":74},"Tablet Application:",{"type":30,"value":76}," used in the field to configure the device, setting parameters for daily operation and authentication tokens for secure uploads.",{"type":20,"tag":56,"props":78,"children":79},{},[80,85],{"type":20,"tag":60,"props":81,"children":82},{},[83],{"type":30,"value":84},"DJANGO Web App:",{"type":30,"value":86}," the web app receives the XML documents, parses them, and stores the data in a relational database.  It also serves requests from a client-facing web application offering reports and analytics.",{"type":20,"tag":56,"props":88,"children":89},{},[90,95],{"type":20,"tag":60,"props":91,"children":92},{},[93],{"type":30,"value":94},"Web Application:",{"type":30,"value":96}," a modern browser web application for viewing data and analytics.",{"type":20,"tag":25,"props":98,"children":100},{"id":99},"the-challenge",[101],{"type":30,"value":102},"The Challenge",{"type":20,"tag":33,"props":104,"children":105},{},[106,108,117],{"type":30,"value":107},"After several years of successful operation our customer came back to us with a new requirement: they wanted to use devices with a new mode of communication to ease the burden and costs of using a cellular network, devices based on ",{"type":20,"tag":109,"props":110,"children":114},"a",{"href":111,"rel":112},"https://en.wikipedia.org/wiki/LoRa",[113],"nofollow",[115],{"type":30,"value":116},"LoRaWAN",{"type":30,"value":118},".",{"type":20,"tag":33,"props":120,"children":121},{},[122],{"type":30,"value":123},"LoRaWAN, a low-power, wide-area network, presents several challenges:",{"type":20,"tag":52,"props":125,"children":126},{},[127,141,154,167],{"type":20,"tag":56,"props":128,"children":129},{},[130,132],{"type":30,"value":131},"It can only transmit 10s to 100s of bytes at a time.  This is too small to deliver our 2-3K XML documents, so immediately we know our messages will need to be broken up into several payloads.\n",{"type":20,"tag":133,"props":134,"children":135},"ul",{},[136],{"type":20,"tag":56,"props":137,"children":138},{},[139],{"type":30,"value":140},"Our solution: if we don't send XML, but rather the data within the XML as a packed struct, we can shrink the message from several K to approximately 600 bytes, reducing greatly the number of payloads we need to deliver to complete a message.",{"type":20,"tag":56,"props":142,"children":143},{},[144,146],{"type":30,"value":145},"When connecting, the device and LoRa gateway will calculate the bandwidth, so each message may be delivered at different data rates and therefore different payload sizes.  E.g., message m1 may be delivered in 11-byte payloads, while m2, connecting at a different time, may deliver 242-byte payloads.\n",{"type":20,"tag":133,"props":147,"children":148},{},[149],{"type":20,"tag":56,"props":150,"children":151},{},[152],{"type":30,"value":153},"Our solution: if each payload is given a header we can identify which message each payload belongs to.  The first payload can include an expected payload count for the complete message. (Metadata delivered by the LoRa gateway identifies the device, so which device is sending the message we get for \"free\".)",{"type":20,"tag":56,"props":155,"children":156},{},[157,159],{"type":30,"value":158},"Payloads may arrive out-of-order.\n",{"type":20,"tag":133,"props":160,"children":161},{},[162],{"type":20,"tag":56,"props":163,"children":164},{},[165],{"type":30,"value":166},"Our solution: use the header to identify the payload order, 0, 1, 2, etc.  On receiving a complete message (known based on count from payload-0) we can sort the payloads to reassemble the packed struct.",{"type":20,"tag":56,"props":168,"children":169},{},[170,172],{"type":30,"value":171},"Individual payloads may be lost.\n",{"type":20,"tag":133,"props":173,"children":174},{},[175],{"type":20,"tag":56,"props":176,"children":177},{},[178],{"type":30,"value":179},"Our solution: some data is required, some data is merely metadata not critical to a successful upload.  If we're missing a required payload (particularly payload-0) we log the incident with the message we did received, but otherwise we post the message.",{"type":20,"tag":33,"props":181,"children":182},{},[183],{"type":30,"value":184},"Putting this all together it becomes clear we need a data processing pipeline; something like this:",{"type":20,"tag":33,"props":186,"children":187},{},[188],{"type":20,"tag":46,"props":189,"children":192},{"alt":190,"src":191},"Pipeline","/dpopowich/2021-07-30/img/pipeline.png",[],{"type":20,"tag":33,"props":194,"children":195},{},[196],{"type":30,"value":197},"The components of the data-collector:",{"type":20,"tag":52,"props":199,"children":200},{},[201,206,224,229],{"type":20,"tag":56,"props":202,"children":203},{},[204],{"type":30,"value":205},"An HTTP server accepting POSTs of payloads.  After validating a payload, it is stored in (2) cache.",{"type":20,"tag":56,"props":207,"children":208},{},[209,211],{"type":30,"value":210},"The cache will store payloads for a message and will pass on the message ID to the (3) aggregator when one of two conditions are met:\n",{"type":20,"tag":52,"props":212,"children":213},{},[214,219],{"type":20,"tag":56,"props":215,"children":216},{},[217],{"type":30,"value":218},"When it has received all payloads, based on having received the expected count of payloads for a message given by payload-0.",{"type":20,"tag":56,"props":220,"children":221},{},[222],{"type":30,"value":223},"Because payloads may be lost, we may never receive a full message, so the cache is configured with a timeout per message.  If a full message is never received the timeout will send the message, as-is, to the aggregator.",{"type":20,"tag":56,"props":225,"children":226},{},[227],{"type":30,"value":228},"The aggregator will collect all payloads, assemble them in order and, when possible, generate an XML document passing it on to (4) the Delivery Agent.",{"type":20,"tag":56,"props":230,"children":231},{},[232],{"type":30,"value":233},"The Delivery Agent, on receiving an XML document will manage a connection (and necessary authentication) with the Web App and upload the XML (via a POST call), managing retries and logging on any failure.",{"type":20,"tag":25,"props":235,"children":237},{"id":236},"implementing-with-asynchronous-python",[238],{"type":30,"value":239},"Implementing with Asynchronous Python",{"type":20,"tag":33,"props":241,"children":242},{},[243,245,252,254,261],{"type":30,"value":244},"Modeling a pipeline in asynchronous Python is a simple matter of creating ",{"type":20,"tag":109,"props":246,"children":249},{"href":247,"rel":248},"https://docs.python.org/3.9/library/asyncio-task.html#creating-tasks",[113],[250],{"type":30,"value":251},"tasks",{"type":30,"value":253}," and ",{"type":20,"tag":109,"props":255,"children":258},{"href":256,"rel":257},"https://docs.python.org/3.9/library/asyncio-queue.html#queue",[113],[259],{"type":30,"value":260},"queues",{"type":30,"value":262},".  Let's look at the last two stages of the pipeline, the aggregator and delivery agent as examples.",{"type":20,"tag":33,"props":264,"children":265},{},[266],{"type":30,"value":267},"We will create two queues on application startup and two long-running tasks:",{"type":20,"tag":269,"props":270,"children":273},"pre",{"className":271,"code":272,"language":12,"meta":8,"style":8},"language-python shiki shiki-themes github-light github-dark","\nimport asyncio\n\n# create queues - for simplicity we ignore sizing the queue here\naggregator_queue = asyncio.Queue()\ndelivery_queue = asyncio.Queue()\n\n# create tasks - two async functions discussed below\nasyncio.create_task(aggregator)\nasyncio.create_task(delivery_agent)\n\n",[274],{"type":20,"tag":275,"props":276,"children":277},"code",{"__ignoreMap":8},[278,290,306,314,324,343,360,368,377,386],{"type":20,"tag":279,"props":280,"children":283},"span",{"class":281,"line":282},"line",1,[284],{"type":20,"tag":279,"props":285,"children":287},{"emptyLinePlaceholder":286},true,[288],{"type":30,"value":289},"\n",{"type":20,"tag":279,"props":291,"children":293},{"class":281,"line":292},2,[294,300],{"type":20,"tag":279,"props":295,"children":297},{"style":296},"--shiki-default:#D73A49;--shiki-dark:#F97583",[298],{"type":30,"value":299},"import",{"type":20,"tag":279,"props":301,"children":303},{"style":302},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[304],{"type":30,"value":305}," asyncio\n",{"type":20,"tag":279,"props":307,"children":309},{"class":281,"line":308},3,[310],{"type":20,"tag":279,"props":311,"children":312},{"emptyLinePlaceholder":286},[313],{"type":30,"value":289},{"type":20,"tag":279,"props":315,"children":317},{"class":281,"line":316},4,[318],{"type":20,"tag":279,"props":319,"children":321},{"style":320},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[322],{"type":30,"value":323},"# create queues - for simplicity we ignore sizing the queue here\n",{"type":20,"tag":279,"props":325,"children":327},{"class":281,"line":326},5,[328,333,338],{"type":20,"tag":279,"props":329,"children":330},{"style":302},[331],{"type":30,"value":332},"aggregator_queue ",{"type":20,"tag":279,"props":334,"children":335},{"style":296},[336],{"type":30,"value":337},"=",{"type":20,"tag":279,"props":339,"children":340},{"style":302},[341],{"type":30,"value":342}," asyncio.Queue()\n",{"type":20,"tag":279,"props":344,"children":346},{"class":281,"line":345},6,[347,352,356],{"type":20,"tag":279,"props":348,"children":349},{"style":302},[350],{"type":30,"value":351},"delivery_queue ",{"type":20,"tag":279,"props":353,"children":354},{"style":296},[355],{"type":30,"value":337},{"type":20,"tag":279,"props":357,"children":358},{"style":302},[359],{"type":30,"value":342},{"type":20,"tag":279,"props":361,"children":363},{"class":281,"line":362},7,[364],{"type":20,"tag":279,"props":365,"children":366},{"emptyLinePlaceholder":286},[367],{"type":30,"value":289},{"type":20,"tag":279,"props":369,"children":371},{"class":281,"line":370},8,[372],{"type":20,"tag":279,"props":373,"children":374},{"style":320},[375],{"type":30,"value":376},"# create tasks - two async functions discussed below\n",{"type":20,"tag":279,"props":378,"children":380},{"class":281,"line":379},9,[381],{"type":20,"tag":279,"props":382,"children":383},{"style":302},[384],{"type":30,"value":385},"asyncio.create_task(aggregator)\n",{"type":20,"tag":279,"props":387,"children":389},{"class":281,"line":388},10,[390],{"type":20,"tag":279,"props":391,"children":392},{"style":302},[393],{"type":30,"value":394},"asyncio.create_task(delivery_agent)\n",{"type":20,"tag":33,"props":396,"children":397},{},[398,400,406],{"type":30,"value":399},"The cache can push message IDs onto ",{"type":20,"tag":275,"props":401,"children":403},{"className":402},[],[404],{"type":30,"value":405},"aggregator_queue",{"type":30,"value":407}," whenever a\nmessage completes or times out:",{"type":20,"tag":269,"props":409,"children":413},{"className":410,"code":412,"language":30},[411],"language-text","# in cache implementation...\naggregator_queue.put_nowait(message_id)\n",[414],{"type":20,"tag":275,"props":415,"children":416},{"__ignoreMap":8},[417],{"type":30,"value":412},{"type":20,"tag":33,"props":419,"children":420},{},[421],{"type":30,"value":422},"Meanwhile, the aggregator can be implemented structurally like this:",{"type":20,"tag":269,"props":424,"children":426},{"className":271,"code":425,"language":12,"meta":8,"style":8},"async def aggregator():\n   \"\"\"Aggregator: collect messages and generate XML documents.\n   \"\"\"\n   while True:\n      # wait for incoming message\n      message_id = await aggregator_queue.get()\n\n      try:\n         # fetch payloads and expected count from cache\n         payloads, count = await cache.get_message(message_id)\n\n         # generate full message from payloads\n         message = generate_message(payloads, count)\n\n         # we have a full message: generate XML\n         xml = generate_xml(message)\n\n         # pass on to the next step in the pipeline, a tuple of our\n         # message ID and generated xml\n         delivery_queue.put_nowait((message_id, xml))\n\n      except Exception as exc:\n         # we log any error that might have occurred from preceding calls\n         logger.error('Error processing message id %s: %s', message_id, exc)\n\n      finally:\n         # we tell the queue the task is complete\n         aggregator_queue.task_done()\n",[427],{"type":20,"tag":275,"props":428,"children":429},{"__ignoreMap":8},[430,453,462,470,489,497,519,526,538,546,567,575,584,602,610,619,637,645,654,663,672,680,704,713,751,759,772,781],{"type":20,"tag":279,"props":431,"children":432},{"class":281,"line":282},[433,437,442,448],{"type":20,"tag":279,"props":434,"children":435},{"style":296},[436],{"type":30,"value":14},{"type":20,"tag":279,"props":438,"children":439},{"style":296},[440],{"type":30,"value":441}," def",{"type":20,"tag":279,"props":443,"children":445},{"style":444},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[446],{"type":30,"value":447}," aggregator",{"type":20,"tag":279,"props":449,"children":450},{"style":302},[451],{"type":30,"value":452},"():\n",{"type":20,"tag":279,"props":454,"children":455},{"class":281,"line":292},[456],{"type":20,"tag":279,"props":457,"children":459},{"style":458},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[460],{"type":30,"value":461},"   \"\"\"Aggregator: collect messages and generate XML documents.\n",{"type":20,"tag":279,"props":463,"children":464},{"class":281,"line":308},[465],{"type":20,"tag":279,"props":466,"children":467},{"style":458},[468],{"type":30,"value":469},"   \"\"\"\n",{"type":20,"tag":279,"props":471,"children":472},{"class":281,"line":316},[473,478,484],{"type":20,"tag":279,"props":474,"children":475},{"style":296},[476],{"type":30,"value":477},"   while",{"type":20,"tag":279,"props":479,"children":481},{"style":480},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[482],{"type":30,"value":483}," True",{"type":20,"tag":279,"props":485,"children":486},{"style":302},[487],{"type":30,"value":488},":\n",{"type":20,"tag":279,"props":490,"children":491},{"class":281,"line":326},[492],{"type":20,"tag":279,"props":493,"children":494},{"style":320},[495],{"type":30,"value":496},"      # wait for incoming message\n",{"type":20,"tag":279,"props":498,"children":499},{"class":281,"line":345},[500,505,509,514],{"type":20,"tag":279,"props":501,"children":502},{"style":302},[503],{"type":30,"value":504},"      message_id ",{"type":20,"tag":279,"props":506,"children":507},{"style":296},[508],{"type":30,"value":337},{"type":20,"tag":279,"props":510,"children":511},{"style":296},[512],{"type":30,"value":513}," await",{"type":20,"tag":279,"props":515,"children":516},{"style":302},[517],{"type":30,"value":518}," aggregator_queue.get()\n",{"type":20,"tag":279,"props":520,"children":521},{"class":281,"line":362},[522],{"type":20,"tag":279,"props":523,"children":524},{"emptyLinePlaceholder":286},[525],{"type":30,"value":289},{"type":20,"tag":279,"props":527,"children":528},{"class":281,"line":370},[529,534],{"type":20,"tag":279,"props":530,"children":531},{"style":296},[532],{"type":30,"value":533},"      try",{"type":20,"tag":279,"props":535,"children":536},{"style":302},[537],{"type":30,"value":488},{"type":20,"tag":279,"props":539,"children":540},{"class":281,"line":379},[541],{"type":20,"tag":279,"props":542,"children":543},{"style":320},[544],{"type":30,"value":545},"         # fetch payloads and expected count from cache\n",{"type":20,"tag":279,"props":547,"children":548},{"class":281,"line":388},[549,554,558,562],{"type":20,"tag":279,"props":550,"children":551},{"style":302},[552],{"type":30,"value":553},"         payloads, count ",{"type":20,"tag":279,"props":555,"children":556},{"style":296},[557],{"type":30,"value":337},{"type":20,"tag":279,"props":559,"children":560},{"style":296},[561],{"type":30,"value":513},{"type":20,"tag":279,"props":563,"children":564},{"style":302},[565],{"type":30,"value":566}," cache.get_message(message_id)\n",{"type":20,"tag":279,"props":568,"children":570},{"class":281,"line":569},11,[571],{"type":20,"tag":279,"props":572,"children":573},{"emptyLinePlaceholder":286},[574],{"type":30,"value":289},{"type":20,"tag":279,"props":576,"children":578},{"class":281,"line":577},12,[579],{"type":20,"tag":279,"props":580,"children":581},{"style":320},[582],{"type":30,"value":583},"         # generate full message from payloads\n",{"type":20,"tag":279,"props":585,"children":587},{"class":281,"line":586},13,[588,593,597],{"type":20,"tag":279,"props":589,"children":590},{"style":302},[591],{"type":30,"value":592},"         message ",{"type":20,"tag":279,"props":594,"children":595},{"style":296},[596],{"type":30,"value":337},{"type":20,"tag":279,"props":598,"children":599},{"style":302},[600],{"type":30,"value":601}," generate_message(payloads, count)\n",{"type":20,"tag":279,"props":603,"children":605},{"class":281,"line":604},14,[606],{"type":20,"tag":279,"props":607,"children":608},{"emptyLinePlaceholder":286},[609],{"type":30,"value":289},{"type":20,"tag":279,"props":611,"children":613},{"class":281,"line":612},15,[614],{"type":20,"tag":279,"props":615,"children":616},{"style":320},[617],{"type":30,"value":618},"         # we have a full message: generate XML\n",{"type":20,"tag":279,"props":620,"children":622},{"class":281,"line":621},16,[623,628,632],{"type":20,"tag":279,"props":624,"children":625},{"style":302},[626],{"type":30,"value":627},"         xml ",{"type":20,"tag":279,"props":629,"children":630},{"style":296},[631],{"type":30,"value":337},{"type":20,"tag":279,"props":633,"children":634},{"style":302},[635],{"type":30,"value":636}," generate_xml(message)\n",{"type":20,"tag":279,"props":638,"children":640},{"class":281,"line":639},17,[641],{"type":20,"tag":279,"props":642,"children":643},{"emptyLinePlaceholder":286},[644],{"type":30,"value":289},{"type":20,"tag":279,"props":646,"children":648},{"class":281,"line":647},18,[649],{"type":20,"tag":279,"props":650,"children":651},{"style":320},[652],{"type":30,"value":653},"         # pass on to the next step in the pipeline, a tuple of our\n",{"type":20,"tag":279,"props":655,"children":657},{"class":281,"line":656},19,[658],{"type":20,"tag":279,"props":659,"children":660},{"style":320},[661],{"type":30,"value":662},"         # message ID and generated xml\n",{"type":20,"tag":279,"props":664,"children":666},{"class":281,"line":665},20,[667],{"type":20,"tag":279,"props":668,"children":669},{"style":302},[670],{"type":30,"value":671},"         delivery_queue.put_nowait((message_id, xml))\n",{"type":20,"tag":279,"props":673,"children":675},{"class":281,"line":674},21,[676],{"type":20,"tag":279,"props":677,"children":678},{"emptyLinePlaceholder":286},[679],{"type":30,"value":289},{"type":20,"tag":279,"props":681,"children":683},{"class":281,"line":682},22,[684,689,694,699],{"type":20,"tag":279,"props":685,"children":686},{"style":296},[687],{"type":30,"value":688},"      except",{"type":20,"tag":279,"props":690,"children":691},{"style":480},[692],{"type":30,"value":693}," Exception",{"type":20,"tag":279,"props":695,"children":696},{"style":296},[697],{"type":30,"value":698}," as",{"type":20,"tag":279,"props":700,"children":701},{"style":302},[702],{"type":30,"value":703}," exc:\n",{"type":20,"tag":279,"props":705,"children":707},{"class":281,"line":706},23,[708],{"type":20,"tag":279,"props":709,"children":710},{"style":320},[711],{"type":30,"value":712},"         # we log any error that might have occurred from preceding calls\n",{"type":20,"tag":279,"props":714,"children":716},{"class":281,"line":715},24,[717,722,727,732,737,741,746],{"type":20,"tag":279,"props":718,"children":719},{"style":302},[720],{"type":30,"value":721},"         logger.error(",{"type":20,"tag":279,"props":723,"children":724},{"style":458},[725],{"type":30,"value":726},"'Error processing message id ",{"type":20,"tag":279,"props":728,"children":729},{"style":480},[730],{"type":30,"value":731},"%s",{"type":20,"tag":279,"props":733,"children":734},{"style":458},[735],{"type":30,"value":736},": ",{"type":20,"tag":279,"props":738,"children":739},{"style":480},[740],{"type":30,"value":731},{"type":20,"tag":279,"props":742,"children":743},{"style":458},[744],{"type":30,"value":745},"'",{"type":20,"tag":279,"props":747,"children":748},{"style":302},[749],{"type":30,"value":750},", message_id, exc)\n",{"type":20,"tag":279,"props":752,"children":754},{"class":281,"line":753},25,[755],{"type":20,"tag":279,"props":756,"children":757},{"emptyLinePlaceholder":286},[758],{"type":30,"value":289},{"type":20,"tag":279,"props":760,"children":762},{"class":281,"line":761},26,[763,768],{"type":20,"tag":279,"props":764,"children":765},{"style":296},[766],{"type":30,"value":767},"      finally",{"type":20,"tag":279,"props":769,"children":770},{"style":302},[771],{"type":30,"value":488},{"type":20,"tag":279,"props":773,"children":775},{"class":281,"line":774},27,[776],{"type":20,"tag":279,"props":777,"children":778},{"style":320},[779],{"type":30,"value":780},"         # we tell the queue the task is complete\n",{"type":20,"tag":279,"props":782,"children":784},{"class":281,"line":783},28,[785],{"type":20,"tag":279,"props":786,"children":787},{"style":302},[788],{"type":30,"value":789},"         aggregator_queue.task_done()\n",{"type":20,"tag":33,"props":791,"children":792},{},[793],{"type":30,"value":794},"With a similar structure we can implement the delivery agent:",{"type":20,"tag":269,"props":796,"children":798},{"className":271,"code":797,"language":12,"meta":8,"style":8},"async def delivery_agent():\n   \"\"\"Delivery agent: listen for incoming XML docs and deliver to Web App\n   \"\"\"\n   # set up initial connection/authentication here.  Let's imagine we\n   # have a factory function, `web_app()` that returns our current\n   # connection to the Web App by way of an [aiohttp\n   # ClientSession](https://docs.aiohttp.org/en/stable/client_advanced.html#client-session).\n\n   # having set up the connection management, we enter our loop:\n   while True:\n      # wait for incoming message\n      message_id, xml = await delivery_queue.get()\n\n      try:\n         # function to generate POST data for REST call\n         data = gen_post_data(xml)\n\n         # get current session from factory and upload\n         session = web_app()\n         resp = await session.post(URL, data=data)\n         # process resp here, handling non 2XX responses, perhaps\n         # running the above POST in a loop for N-attempts for\n         # recoverable errors, otherwise raising an appropriate exception.\n\n      except Exception as exc:\n         # we log any error that might have occurred from preceding calls\n         logger.error('Could not upload xml for message_id %s: %s', message_id, exc)\n         # perhaps we save the generated XML for review of the failure?\n\n      finally:\n         # we tell the queue the task is complete\n         delivery_queue.task_done()\n",[799],{"type":20,"tag":275,"props":800,"children":801},{"__ignoreMap":8},[802,822,830,837,845,853,861,869,876,884,899,906,927,934,945,953,970,977,985,1002,1048,1056,1064,1072,1079,1098,1105,1137,1145,1153,1165,1173],{"type":20,"tag":279,"props":803,"children":804},{"class":281,"line":282},[805,809,813,818],{"type":20,"tag":279,"props":806,"children":807},{"style":296},[808],{"type":30,"value":14},{"type":20,"tag":279,"props":810,"children":811},{"style":296},[812],{"type":30,"value":441},{"type":20,"tag":279,"props":814,"children":815},{"style":444},[816],{"type":30,"value":817}," delivery_agent",{"type":20,"tag":279,"props":819,"children":820},{"style":302},[821],{"type":30,"value":452},{"type":20,"tag":279,"props":823,"children":824},{"class":281,"line":292},[825],{"type":20,"tag":279,"props":826,"children":827},{"style":458},[828],{"type":30,"value":829},"   \"\"\"Delivery agent: listen for incoming XML docs and deliver to Web App\n",{"type":20,"tag":279,"props":831,"children":832},{"class":281,"line":308},[833],{"type":20,"tag":279,"props":834,"children":835},{"style":458},[836],{"type":30,"value":469},{"type":20,"tag":279,"props":838,"children":839},{"class":281,"line":316},[840],{"type":20,"tag":279,"props":841,"children":842},{"style":320},[843],{"type":30,"value":844},"   # set up initial connection/authentication here.  Let's imagine we\n",{"type":20,"tag":279,"props":846,"children":847},{"class":281,"line":326},[848],{"type":20,"tag":279,"props":849,"children":850},{"style":320},[851],{"type":30,"value":852},"   # have a factory function, `web_app()` that returns our current\n",{"type":20,"tag":279,"props":854,"children":855},{"class":281,"line":345},[856],{"type":20,"tag":279,"props":857,"children":858},{"style":320},[859],{"type":30,"value":860},"   # connection to the Web App by way of an [aiohttp\n",{"type":20,"tag":279,"props":862,"children":863},{"class":281,"line":362},[864],{"type":20,"tag":279,"props":865,"children":866},{"style":320},[867],{"type":30,"value":868},"   # ClientSession](https://docs.aiohttp.org/en/stable/client_advanced.html#client-session).\n",{"type":20,"tag":279,"props":870,"children":871},{"class":281,"line":370},[872],{"type":20,"tag":279,"props":873,"children":874},{"emptyLinePlaceholder":286},[875],{"type":30,"value":289},{"type":20,"tag":279,"props":877,"children":878},{"class":281,"line":379},[879],{"type":20,"tag":279,"props":880,"children":881},{"style":320},[882],{"type":30,"value":883},"   # having set up the connection management, we enter our loop:\n",{"type":20,"tag":279,"props":885,"children":886},{"class":281,"line":388},[887,891,895],{"type":20,"tag":279,"props":888,"children":889},{"style":296},[890],{"type":30,"value":477},{"type":20,"tag":279,"props":892,"children":893},{"style":480},[894],{"type":30,"value":483},{"type":20,"tag":279,"props":896,"children":897},{"style":302},[898],{"type":30,"value":488},{"type":20,"tag":279,"props":900,"children":901},{"class":281,"line":569},[902],{"type":20,"tag":279,"props":903,"children":904},{"style":320},[905],{"type":30,"value":496},{"type":20,"tag":279,"props":907,"children":908},{"class":281,"line":577},[909,914,918,922],{"type":20,"tag":279,"props":910,"children":911},{"style":302},[912],{"type":30,"value":913},"      message_id, xml ",{"type":20,"tag":279,"props":915,"children":916},{"style":296},[917],{"type":30,"value":337},{"type":20,"tag":279,"props":919,"children":920},{"style":296},[921],{"type":30,"value":513},{"type":20,"tag":279,"props":923,"children":924},{"style":302},[925],{"type":30,"value":926}," delivery_queue.get()\n",{"type":20,"tag":279,"props":928,"children":929},{"class":281,"line":586},[930],{"type":20,"tag":279,"props":931,"children":932},{"emptyLinePlaceholder":286},[933],{"type":30,"value":289},{"type":20,"tag":279,"props":935,"children":936},{"class":281,"line":604},[937,941],{"type":20,"tag":279,"props":938,"children":939},{"style":296},[940],{"type":30,"value":533},{"type":20,"tag":279,"props":942,"children":943},{"style":302},[944],{"type":30,"value":488},{"type":20,"tag":279,"props":946,"children":947},{"class":281,"line":612},[948],{"type":20,"tag":279,"props":949,"children":950},{"style":320},[951],{"type":30,"value":952},"         # function to generate POST data for REST call\n",{"type":20,"tag":279,"props":954,"children":955},{"class":281,"line":621},[956,961,965],{"type":20,"tag":279,"props":957,"children":958},{"style":302},[959],{"type":30,"value":960},"         data ",{"type":20,"tag":279,"props":962,"children":963},{"style":296},[964],{"type":30,"value":337},{"type":20,"tag":279,"props":966,"children":967},{"style":302},[968],{"type":30,"value":969}," gen_post_data(xml)\n",{"type":20,"tag":279,"props":971,"children":972},{"class":281,"line":639},[973],{"type":20,"tag":279,"props":974,"children":975},{"emptyLinePlaceholder":286},[976],{"type":30,"value":289},{"type":20,"tag":279,"props":978,"children":979},{"class":281,"line":647},[980],{"type":20,"tag":279,"props":981,"children":982},{"style":320},[983],{"type":30,"value":984},"         # get current session from factory and upload\n",{"type":20,"tag":279,"props":986,"children":987},{"class":281,"line":656},[988,993,997],{"type":20,"tag":279,"props":989,"children":990},{"style":302},[991],{"type":30,"value":992},"         session ",{"type":20,"tag":279,"props":994,"children":995},{"style":296},[996],{"type":30,"value":337},{"type":20,"tag":279,"props":998,"children":999},{"style":302},[1000],{"type":30,"value":1001}," web_app()\n",{"type":20,"tag":279,"props":1003,"children":1004},{"class":281,"line":665},[1005,1010,1014,1018,1023,1028,1033,1039,1043],{"type":20,"tag":279,"props":1006,"children":1007},{"style":302},[1008],{"type":30,"value":1009},"         resp ",{"type":20,"tag":279,"props":1011,"children":1012},{"style":296},[1013],{"type":30,"value":337},{"type":20,"tag":279,"props":1015,"children":1016},{"style":296},[1017],{"type":30,"value":513},{"type":20,"tag":279,"props":1019,"children":1020},{"style":302},[1021],{"type":30,"value":1022}," session.post(",{"type":20,"tag":279,"props":1024,"children":1025},{"style":480},[1026],{"type":30,"value":1027},"URL",{"type":20,"tag":279,"props":1029,"children":1030},{"style":302},[1031],{"type":30,"value":1032},", ",{"type":20,"tag":279,"props":1034,"children":1036},{"style":1035},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[1037],{"type":30,"value":1038},"data",{"type":20,"tag":279,"props":1040,"children":1041},{"style":296},[1042],{"type":30,"value":337},{"type":20,"tag":279,"props":1044,"children":1045},{"style":302},[1046],{"type":30,"value":1047},"data)\n",{"type":20,"tag":279,"props":1049,"children":1050},{"class":281,"line":674},[1051],{"type":20,"tag":279,"props":1052,"children":1053},{"style":320},[1054],{"type":30,"value":1055},"         # process resp here, handling non 2XX responses, perhaps\n",{"type":20,"tag":279,"props":1057,"children":1058},{"class":281,"line":682},[1059],{"type":20,"tag":279,"props":1060,"children":1061},{"style":320},[1062],{"type":30,"value":1063},"         # running the above POST in a loop for N-attempts for\n",{"type":20,"tag":279,"props":1065,"children":1066},{"class":281,"line":706},[1067],{"type":20,"tag":279,"props":1068,"children":1069},{"style":320},[1070],{"type":30,"value":1071},"         # recoverable errors, otherwise raising an appropriate exception.\n",{"type":20,"tag":279,"props":1073,"children":1074},{"class":281,"line":715},[1075],{"type":20,"tag":279,"props":1076,"children":1077},{"emptyLinePlaceholder":286},[1078],{"type":30,"value":289},{"type":20,"tag":279,"props":1080,"children":1081},{"class":281,"line":753},[1082,1086,1090,1094],{"type":20,"tag":279,"props":1083,"children":1084},{"style":296},[1085],{"type":30,"value":688},{"type":20,"tag":279,"props":1087,"children":1088},{"style":480},[1089],{"type":30,"value":693},{"type":20,"tag":279,"props":1091,"children":1092},{"style":296},[1093],{"type":30,"value":698},{"type":20,"tag":279,"props":1095,"children":1096},{"style":302},[1097],{"type":30,"value":703},{"type":20,"tag":279,"props":1099,"children":1100},{"class":281,"line":761},[1101],{"type":20,"tag":279,"props":1102,"children":1103},{"style":320},[1104],{"type":30,"value":712},{"type":20,"tag":279,"props":1106,"children":1107},{"class":281,"line":774},[1108,1112,1117,1121,1125,1129,1133],{"type":20,"tag":279,"props":1109,"children":1110},{"style":302},[1111],{"type":30,"value":721},{"type":20,"tag":279,"props":1113,"children":1114},{"style":458},[1115],{"type":30,"value":1116},"'Could not upload xml for message_id ",{"type":20,"tag":279,"props":1118,"children":1119},{"style":480},[1120],{"type":30,"value":731},{"type":20,"tag":279,"props":1122,"children":1123},{"style":458},[1124],{"type":30,"value":736},{"type":20,"tag":279,"props":1126,"children":1127},{"style":480},[1128],{"type":30,"value":731},{"type":20,"tag":279,"props":1130,"children":1131},{"style":458},[1132],{"type":30,"value":745},{"type":20,"tag":279,"props":1134,"children":1135},{"style":302},[1136],{"type":30,"value":750},{"type":20,"tag":279,"props":1138,"children":1139},{"class":281,"line":783},[1140],{"type":20,"tag":279,"props":1141,"children":1142},{"style":320},[1143],{"type":30,"value":1144},"         # perhaps we save the generated XML for review of the failure?\n",{"type":20,"tag":279,"props":1146,"children":1148},{"class":281,"line":1147},29,[1149],{"type":20,"tag":279,"props":1150,"children":1151},{"emptyLinePlaceholder":286},[1152],{"type":30,"value":289},{"type":20,"tag":279,"props":1154,"children":1156},{"class":281,"line":1155},30,[1157,1161],{"type":20,"tag":279,"props":1158,"children":1159},{"style":296},[1160],{"type":30,"value":767},{"type":20,"tag":279,"props":1162,"children":1163},{"style":302},[1164],{"type":30,"value":488},{"type":20,"tag":279,"props":1166,"children":1168},{"class":281,"line":1167},31,[1169],{"type":20,"tag":279,"props":1170,"children":1171},{"style":320},[1172],{"type":30,"value":780},{"type":20,"tag":279,"props":1174,"children":1176},{"class":281,"line":1175},32,[1177],{"type":20,"tag":279,"props":1178,"children":1179},{"style":302},[1180],{"type":30,"value":1181},"         delivery_queue.task_done()\n",{"type":20,"tag":1183,"props":1184,"children":1185},"hr",{},[],{"type":20,"tag":33,"props":1187,"children":1188},{},[1189],{"type":30,"value":1190},"As we complete our implementation we end up with a software architecture that mirrors our designed pipeline:",{"type":20,"tag":33,"props":1192,"children":1193},{},[1194],{"type":20,"tag":46,"props":1195,"children":1198},{"alt":1196,"src":1197},"Data Collector Architecture","/dpopowich/2021-07-30/img/data-collector.png",[],{"type":20,"tag":1200,"props":1201,"children":1202},"style",{},[1203],{"type":30,"value":1204},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":308,"depth":308,"links":1206},[1207,1208,1209],{"id":27,"depth":292,"text":31},{"id":99,"depth":292,"text":102},{"id":236,"depth":292,"text":239},"markdown","content:dpopowich:2021-07-30:data-collector.md","content","dpopowich/2021-07-30/data-collector.md","dpopowich/2021-07-30/data-collector","md",{"user":1217,"name":1218},"dpopowich","Daniel Popowich",{"_path":1220,"_dir":1221,"_draft":7,"_partial":7,"_locale":8,"title":1222,"description":1223,"tags":1224,"publishDate":1227,"image":1228,"excerpt":1223,"body":1229,"_type":1210,"_id":2001,"_source":1212,"_file":2002,"_stem":2003,"_extension":1215,"author":2004},"/phendry/2021-06/smoothupgradestovue3","2021-06","Smooth Upgrades to Vue 3","This post assumes basic familiarity with Vue.js v2.x.",[1225,1226,13],"js","vue","2021-07-01","/phendry/2021-06/img/vue-transition.jpg",{"type":17,"children":1230,"toc":1995},[1231,1248,1253,1392,1406,1420,1426,1438,1509,1521,1527,1532,1583,1588,1594,1608,1613,1710,1724,1787,1800,1868,1874,1879,1969,1991],{"type":20,"tag":33,"props":1232,"children":1233},{},[1234],{"type":20,"tag":1235,"props":1236,"children":1237},"em",{},[1238,1240,1247],{"type":30,"value":1239},"This post assumes basic familiarity with ",{"type":20,"tag":109,"props":1241,"children":1244},{"href":1242,"rel":1243},"https://vuejs.org/",[113],[1245],{"type":30,"value":1246},"Vue.js v2.x",{"type":30,"value":118},{"type":20,"tag":33,"props":1249,"children":1250},{},[1251],{"type":30,"value":1252},"Vue 3, the Progressive JavaScript Framework, introduces some compelling new features:",{"type":20,"tag":133,"props":1254,"children":1255},{},[1256,1270,1293,1315,1327,1339,1357,1379],{"type":20,"tag":56,"props":1257,"children":1258},{},[1259,1261,1268],{"type":30,"value":1260},"The ",{"type":20,"tag":109,"props":1262,"children":1265},{"href":1263,"rel":1264},"https://v3.vuejs.org/guide/composition-api-introduction.html",[113],[1266],{"type":30,"value":1267},"Composition API",{"type":30,"value":1269}," introduces a new, more flexible, TypeScript-friendly syntax for defining components (albeit at the cost of a bit more complexity);",{"type":20,"tag":56,"props":1271,"children":1272},{},[1273,1275,1282,1284,1291],{"type":30,"value":1274},"Significant ",{"type":20,"tag":109,"props":1276,"children":1279},{"href":1277,"rel":1278},"https://docs.google.com/spreadsheets/d/1VJFx-kQ4KjJmnpDXIEaig-cVAAJtpIGLZNbv3Lr4CR0/edit#gid=0",[113],[1280],{"type":30,"value":1281},"reported performance improvements",{"type":30,"value":1283}," over v2, in addition to reduced bundle sizes as a result of ",{"type":20,"tag":109,"props":1285,"children":1288},{"href":1286,"rel":1287},"https://v3.vuejs.org/guide/migration/global-api-treeshaking.html",[113],[1289],{"type":30,"value":1290},"global API tree-shaking",{"type":30,"value":1292},";",{"type":20,"tag":56,"props":1294,"children":1295},{},[1296,1298,1305,1307,1313],{"type":30,"value":1297},"An updated ",{"type":20,"tag":109,"props":1299,"children":1302},{"href":1300,"rel":1301},"https://v3.vuejs.org/guide/reactivity.html#what-is-reactivity",[113],[1303],{"type":30,"value":1304},"reactivity system",{"type":30,"value":1306}," based on ",{"type":20,"tag":275,"props":1308,"children":1310},{"className":1309},[],[1311],{"type":30,"value":1312},"Proxy",{"type":30,"value":1314},", which eliminates a common source of bugs when using arrays and objects;",{"type":20,"tag":56,"props":1316,"children":1317},{},[1318,1325],{"type":20,"tag":109,"props":1319,"children":1322},{"href":1320,"rel":1321},"https://vitejs.dev/",[113],[1323],{"type":30,"value":1324},"Vite",{"type":30,"value":1326},", a new set of frontend tooling for a lightning-fast development experience;",{"type":20,"tag":56,"props":1328,"children":1329},{},[1330,1337],{"type":20,"tag":109,"props":1331,"children":1334},{"href":1332,"rel":1333},"https://v3.vuejs.org/guide/migration/fragments.html",[113],[1335],{"type":30,"value":1336},"Fragments",{"type":30,"value":1338},", allowing multiple root elements in Vue components;",{"type":20,"tag":56,"props":1340,"children":1341},{},[1342,1344,1355],{"type":30,"value":1343},"[Experimental] The ",{"type":20,"tag":109,"props":1345,"children":1348},{"href":1346,"rel":1347},"https://github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/0000-script-setup.md",[113],[1349],{"type":20,"tag":275,"props":1350,"children":1352},{"className":1351},[],[1353],{"type":30,"value":1354},"\u003Cscript setup>",{"type":30,"value":1356}," syntax, for a nicer syntax when committing fully to the Composition API in a component;",{"type":20,"tag":56,"props":1358,"children":1359},{},[1360,1362,1369,1371,1377],{"type":30,"value":1361},"[Experimental] ",{"type":20,"tag":109,"props":1363,"children":1366},{"href":1364,"rel":1365},"https://github.com/vuejs/rfcs/blob/style-vars-2/active-rfcs/0000-sfc-style-variables.md",[113],[1367],{"type":30,"value":1368},"State-driven CSS Variables",{"type":30,"value":1370},", allowing data bindings within the ",{"type":20,"tag":275,"props":1372,"children":1374},{"className":1373},[],[1375],{"type":30,"value":1376},"\u003Cstyle>",{"type":30,"value":1378}," section of components;",{"type":20,"tag":56,"props":1380,"children":1381},{},[1382,1383,1390],{"type":30,"value":1343},{"type":20,"tag":109,"props":1384,"children":1387},{"href":1385,"rel":1386},"https://v3.vuejs.org/guide/migration/suspense.html",[113],[1388],{"type":30,"value":1389},"Suspense",{"type":30,"value":1391}," component, which makes it much easier to write asynchronous components that display a \"loading\" UI while they're processing.",{"type":20,"tag":33,"props":1393,"children":1394},{},[1395,1397,1404],{"type":30,"value":1396},"It's a valuable upgrade in terms of performance and developer productivity. The question is, how do you get there? While v3 does not make any drastic API changes compared to v2, the changes it does make are ",{"type":20,"tag":109,"props":1398,"children":1401},{"href":1399,"rel":1400},"https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes",[113],[1402],{"type":30,"value":1403},"numerous",{"type":30,"value":1405},", and as of the initial v3.0 release, these breaking changes would need to be migrated in one fell swoop.",{"type":20,"tag":33,"props":1407,"children":1408},{},[1409,1411,1418],{"type":30,"value":1410},"Fortunately, the Vue.js team has recently released the ",{"type":20,"tag":109,"props":1412,"children":1415},{"href":1413,"rel":1414},"https://v3.vuejs.org/guide/migration/migration-build.html",[113],[1416],{"type":30,"value":1417},"Migration Build",{"type":30,"value":1419},", which makes it possible (and easy) to make a smooth transition from v2 to v3.",{"type":20,"tag":25,"props":1421,"children":1423},{"id":1422},"vue-3-compatibility-caveats",[1424],{"type":30,"value":1425},"Vue 3 Compatibility Caveats",{"type":20,"tag":33,"props":1427,"children":1428},{},[1429,1431,1436],{"type":30,"value":1430},"There are a few reasons why you might ",{"type":20,"tag":1235,"props":1432,"children":1433},{},[1434],{"type":30,"value":1435},"not",{"type":30,"value":1437}," be ready to introduce Vue 3 into your project:",{"type":20,"tag":133,"props":1439,"children":1440},{},[1441,1460],{"type":20,"tag":56,"props":1442,"children":1443},{},[1444,1449,1451,1458],{"type":20,"tag":60,"props":1445,"children":1446},{},[1447],{"type":30,"value":1448},"IE11 Compatibility:",{"type":30,"value":1450}," Support for Internet Exporer 11 and other \"legacy\" browsers was previously promised in a subsequent update, but it has been ",{"type":20,"tag":109,"props":1452,"children":1455},{"href":1453,"rel":1454},"https://github.com/vuejs/rfcs/blob/master/active-rfcs/0038-vue3-ie11-support.md",[113],[1456],{"type":30,"value":1457},"formally dropped",{"type":30,"value":1459}," from the Vue 3 roadmap. If your project needs to support IE or other \"legacy\" browsers, you'll likely be sticking to Vue v2.x indefinitely.",{"type":20,"tag":56,"props":1461,"children":1462},{},[1463,1468,1470],{"type":20,"tag":60,"props":1464,"children":1465},{},[1466],{"type":30,"value":1467},"Framework Compatibiilty:",{"type":30,"value":1469}," Many major component libraries and frameworks need time to make the upgrade. If you depend on one of them, you'll be dependent on their upgrade to v3. For example:\n",{"type":20,"tag":133,"props":1471,"children":1472},{},[1473,1485,1497],{"type":20,"tag":56,"props":1474,"children":1475},{},[1476,1478],{"type":30,"value":1477},"Bootstrap-Vue: ",{"type":20,"tag":109,"props":1479,"children":1482},{"href":1480,"rel":1481},"https://github.com/bootstrap-vue/bootstrap-vue/issues/5196",[113],[1483],{"type":30,"value":1484},"On roadmap, but no timeline",{"type":20,"tag":56,"props":1486,"children":1487},{},[1488,1490],{"type":30,"value":1489},"Nuxt.js: ",{"type":20,"tag":109,"props":1491,"children":1494},{"href":1492,"rel":1493},"https://github.com/nuxt/nuxt.js/issues/5708",[113],[1495],{"type":30,"value":1496},"On roadmap for Nuxt v3, but no timeline",{"type":20,"tag":56,"props":1498,"children":1499},{},[1500,1502],{"type":30,"value":1501},"Vuetify: ",{"type":20,"tag":109,"props":1503,"children":1506},{"href":1504,"rel":1505},"https://vuetifyjs.com/en/introduction/roadmap/",[113],[1507],{"type":30,"value":1508},"support available in Vuetify v3.0 alpha; full support in Q3 2021",{"type":20,"tag":33,"props":1510,"children":1511},{},[1512,1514,1520],{"type":30,"value":1513},"Make sure that your project's dependencies and supported platforms allow for the upgrade. If not, keep in mind that some v3 features are being backported to v2, such as the ",{"type":20,"tag":109,"props":1515,"children":1518},{"href":1516,"rel":1517},"https://github.com/vuejs/composition-api",[113],[1519],{"type":30,"value":1267},{"type":30,"value":118},{"type":20,"tag":25,"props":1522,"children":1524},{"id":1523},"what-is-the-migration-build",[1525],{"type":30,"value":1526},"What is the Migration Build?",{"type":20,"tag":33,"props":1528,"children":1529},{},[1530],{"type":30,"value":1531},"The Migration Build is an alternative build of Vue 3 which provides configurable Vue 2 compatible behaviour. It enables a gradual migration process:",{"type":20,"tag":52,"props":1533,"children":1534},{},[1535,1554,1559,1564],{"type":20,"tag":56,"props":1536,"children":1537},{},[1538,1540,1545,1547,1553],{"type":30,"value":1539},"Swap out your ",{"type":20,"tag":275,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":30,"value":1226},{"type":30,"value":1546}," v2.x dependency with ",{"type":20,"tag":275,"props":1548,"children":1550},{"className":1549},[],[1551],{"type":30,"value":1552},"@vue/compat",{"type":30,"value":1292},{"type":20,"tag":56,"props":1555,"children":1556},{},[1557],{"type":30,"value":1558},"Configure the Migration Build to be fully v2-compatible;",{"type":20,"tag":56,"props":1560,"children":1561},{},[1562],{"type":30,"value":1563},"One breaking change at a time, configure the Migration Build to be v3-compatible for that feature, and fix any occurrences of it in your project;",{"type":20,"tag":56,"props":1565,"children":1566},{},[1567,1569,1574,1576,1581],{"type":30,"value":1568},"Swap out ",{"type":20,"tag":275,"props":1570,"children":1572},{"className":1571},[],[1573],{"type":30,"value":1552},{"type":30,"value":1575}," for ",{"type":20,"tag":275,"props":1577,"children":1579},{"className":1578},[],[1580],{"type":30,"value":1226},{"type":30,"value":1582}," 3.x once your project is fully migrated to v3.",{"type":20,"tag":33,"props":1584,"children":1585},{},[1586],{"type":30,"value":1587},"The amount of overhead introduced by this compability build is relatively small, so it's even possible to make releases while in the intermediate state between v2 and v3 compatibility. The Vue.js team has guaranteed to maintain this migration build at least until the end of 2021, but after that no firm commitments have been made, so if you've made the decision to upgrade, it's best not to delay for too long.",{"type":20,"tag":25,"props":1589,"children":1591},{"id":1590},"using-the-migration-build",[1592],{"type":30,"value":1593},"Using the Migration Build",{"type":20,"tag":33,"props":1595,"children":1596},{},[1597,1599,1606],{"type":30,"value":1598},"The first step of course is installation, and the ",{"type":20,"tag":109,"props":1600,"children":1603},{"href":1601,"rel":1602},"https://v3.vuejs.org/guide/migration/migration-build.html#installation",[113],[1604],{"type":30,"value":1605},"official installation documentation",{"type":30,"value":1607}," is easy to follow.",{"type":20,"tag":33,"props":1609,"children":1610},{},[1611],{"type":30,"value":1612},"Configuration is straightforward, and can be done both globally or on a per-component basis. A global configuration can be created with a snippet like this in the root of the application:",{"type":20,"tag":269,"props":1614,"children":1617},{"className":1615,"code":1616,"language":1225,"meta":8,"style":8},"language-js shiki shiki-themes github-light github-dark","import { configureCompat } from 'vue';\n\nconfigureCompat({\n   // By default, each feature is v2 compatible. Set individual features to\n   // `false` like so to switch them to v3 compatibility.\n   GLOBAL_MOUNT: false,\n});\n",[1618],{"type":20,"tag":275,"props":1619,"children":1620},{"__ignoreMap":8},[1621,1648,1655,1668,1676,1684,1702],{"type":20,"tag":279,"props":1622,"children":1623},{"class":281,"line":282},[1624,1628,1633,1638,1643],{"type":20,"tag":279,"props":1625,"children":1626},{"style":296},[1627],{"type":30,"value":299},{"type":20,"tag":279,"props":1629,"children":1630},{"style":302},[1631],{"type":30,"value":1632}," { configureCompat } ",{"type":20,"tag":279,"props":1634,"children":1635},{"style":296},[1636],{"type":30,"value":1637},"from",{"type":20,"tag":279,"props":1639,"children":1640},{"style":458},[1641],{"type":30,"value":1642}," 'vue'",{"type":20,"tag":279,"props":1644,"children":1645},{"style":302},[1646],{"type":30,"value":1647},";\n",{"type":20,"tag":279,"props":1649,"children":1650},{"class":281,"line":292},[1651],{"type":20,"tag":279,"props":1652,"children":1653},{"emptyLinePlaceholder":286},[1654],{"type":30,"value":289},{"type":20,"tag":279,"props":1656,"children":1657},{"class":281,"line":308},[1658,1663],{"type":20,"tag":279,"props":1659,"children":1660},{"style":444},[1661],{"type":30,"value":1662},"configureCompat",{"type":20,"tag":279,"props":1664,"children":1665},{"style":302},[1666],{"type":30,"value":1667},"({\n",{"type":20,"tag":279,"props":1669,"children":1670},{"class":281,"line":316},[1671],{"type":20,"tag":279,"props":1672,"children":1673},{"style":320},[1674],{"type":30,"value":1675},"   // By default, each feature is v2 compatible. Set individual features to\n",{"type":20,"tag":279,"props":1677,"children":1678},{"class":281,"line":326},[1679],{"type":20,"tag":279,"props":1680,"children":1681},{"style":320},[1682],{"type":30,"value":1683},"   // `false` like so to switch them to v3 compatibility.\n",{"type":20,"tag":279,"props":1685,"children":1686},{"class":281,"line":345},[1687,1692,1697],{"type":20,"tag":279,"props":1688,"children":1689},{"style":302},[1690],{"type":30,"value":1691},"   GLOBAL_MOUNT: ",{"type":20,"tag":279,"props":1693,"children":1694},{"style":480},[1695],{"type":30,"value":1696},"false",{"type":20,"tag":279,"props":1698,"children":1699},{"style":302},[1700],{"type":30,"value":1701},",\n",{"type":20,"tag":279,"props":1703,"children":1704},{"class":281,"line":362},[1705],{"type":20,"tag":279,"props":1706,"children":1707},{"style":302},[1708],{"type":30,"value":1709},"});\n",{"type":20,"tag":33,"props":1711,"children":1712},{},[1713,1715,1722],{"type":30,"value":1714},"Once installed and configured, your v3-upgraded application should, for the most part, function just the same as before. From here, the ",{"type":20,"tag":109,"props":1716,"children":1719},{"href":1717,"rel":1718},"https://v3.vuejs.org/guide/migration/migration-build.html#feature-reference",[113],[1720],{"type":30,"value":1721},"Feature Reference",{"type":30,"value":1723}," in the official documentation is where you'll find a list of the individual v3 features to enable in order to migrate your project. They're separated into four \"Compatibility Types\", in a sensible order for sequentially working through them. These are:",{"type":20,"tag":133,"props":1725,"children":1726},{},[1727,1743,1753,1777],{"type":20,"tag":56,"props":1728,"children":1729},{},[1730,1735,1737,1741],{"type":20,"tag":60,"props":1731,"children":1732},{},[1733],{"type":30,"value":1734},"Incompatible:",{"type":30,"value":1736}," These are a handful of features for which the Migration Build does ",{"type":20,"tag":1235,"props":1738,"children":1739},{},[1740],{"type":30,"value":1435},{"type":30,"value":1742}," provide v2 compatibility; it only provides warnings for easy identification of code that requires migrating. If any of these warnings appear, they'll need to be addressed upfront.",{"type":20,"tag":56,"props":1744,"children":1745},{},[1746,1751],{"type":20,"tag":60,"props":1747,"children":1748},{},[1749],{"type":30,"value":1750},"Partially Compatible With Caveats:",{"type":30,"value":1752}," These features don't behave 100% identically to v2 in compatibility mode, but as long as you aren't relying on private Vue APIs, this is unlikely to be a problem.",{"type":20,"tag":56,"props":1754,"children":1755},{},[1756,1761,1763,1767,1769,1775],{"type":20,"tag":60,"props":1757,"children":1758},{},[1759],{"type":30,"value":1760},"Compat Only (No Warning)",{"type":30,"value":1762},": These features are v2-compatible but do ",{"type":20,"tag":1235,"props":1764,"children":1765},{},[1766],{"type":30,"value":1435},{"type":30,"value":1768}," issue a warning to identify code which needs migrating. Curently this is only the ",{"type":20,"tag":275,"props":1770,"children":1772},{"className":1771},[],[1773],{"type":30,"value":1774},"TRANSITION_CLASSES",{"type":30,"value":1776}," feature, instances of which are easy to find via text search.",{"type":20,"tag":56,"props":1778,"children":1779},{},[1780,1785],{"type":20,"tag":60,"props":1781,"children":1782},{},[1783],{"type":30,"value":1784},"Fully Compatible",{"type":30,"value":1786},": These features ought to behave identically to v2.",{"type":20,"tag":33,"props":1788,"children":1789},{},[1790,1792,1798],{"type":30,"value":1791},"For each feature, simply migrate each v2-style occurrence, then set ",{"type":20,"tag":275,"props":1793,"children":1795},{"className":1794},[],[1796],{"type":30,"value":1797},"FEATURE_NAME: false",{"type":30,"value":1799}," in your compatibility configuration in order to switch the Vue build to use the v3 behavior.",{"type":20,"tag":33,"props":1801,"children":1802},{},[1803,1805,1811,1813,1820,1822,1828,1830,1835,1837,1843,1845,1851,1852,1858,1860,1866],{"type":30,"value":1804},"For example, consider the ",{"type":20,"tag":275,"props":1806,"children":1808},{"className":1807},[],[1809],{"type":30,"value":1810},"INSTANCE_SCOPED_SLOTS",{"type":30,"value":1812}," compatibility feature. The Feature Reference provides a ",{"type":20,"tag":109,"props":1814,"children":1817},{"href":1815,"rel":1816},"https://v3.vuejs.org/guide/migration/slots-unification.html",[113],[1818],{"type":30,"value":1819},"link",{"type":30,"value":1821}," to the relevant migration documentation, which tells you that ",{"type":20,"tag":275,"props":1823,"children":1825},{"className":1824},[],[1826],{"type":30,"value":1827},"this.$scopedSlots",{"type":30,"value":1829}," has been removed due to the unification of normal/scoped slots in v3. The \"Migration Strategy\" section at the bottom describes simple steps to take: replace occurrences of ",{"type":20,"tag":275,"props":1831,"children":1833},{"className":1832},[],[1834],{"type":30,"value":1827},{"type":30,"value":1836}," with ",{"type":20,"tag":275,"props":1838,"children":1840},{"className":1839},[],[1841],{"type":30,"value":1842},"this.$slots",{"type":30,"value":1844},", and then replace occurrences of ",{"type":20,"tag":275,"props":1846,"children":1848},{"className":1847},[],[1849],{"type":30,"value":1850},"this.$slots.mySlot",{"type":30,"value":1836},{"type":20,"tag":275,"props":1853,"children":1855},{"className":1854},[],[1856],{"type":30,"value":1857},"this.$slots.mySlot()",{"type":30,"value":1859},". This can easily be done in a few moments using find-and-replace, after which ",{"type":20,"tag":275,"props":1861,"children":1863},{"className":1862},[],[1864],{"type":30,"value":1865},"INSTANCE_SCOPED_SLOTS: false",{"type":30,"value":1867}," can be set in your compatibility configuration, one step closer to v3 compatibility.",{"type":20,"tag":25,"props":1869,"children":1871},{"id":1870},"recap",[1872],{"type":30,"value":1873},"Recap",{"type":20,"tag":33,"props":1875,"children":1876},{},[1877],{"type":30,"value":1878},"Overall, the Migration Build allows for a gradual development process of migration that looks something like this:",{"type":20,"tag":52,"props":1880,"children":1881},{},[1882,1910,1915],{"type":20,"tag":56,"props":1883,"children":1884},{},[1885,1887],{"type":30,"value":1886},"Replace your project's Vue 2.x dependency with the Vue 3 Migration Build\n",{"type":20,"tag":52,"props":1888,"children":1889},{},[1890,1895,1900,1905],{"type":20,"tag":56,"props":1891,"children":1892},{},[1893],{"type":30,"value":1894},"Install and configure it for full v2 compatibility",{"type":20,"tag":56,"props":1896,"children":1897},{},[1898],{"type":30,"value":1899},"Migrate the handful of features marked \"Incompatible\" in order to restore your application to full function",{"type":20,"tag":56,"props":1901,"children":1902},{},[1903],{"type":30,"value":1904},"Peform some smoke testing to ensure that everything is back to normal",{"type":20,"tag":56,"props":1906,"children":1907},{},[1908],{"type":30,"value":1909},"Merge this change into your mainline development branch",{"type":20,"tag":56,"props":1911,"children":1912},{},[1913],{"type":30,"value":1914},"Continue regular development as required, potentially even making releases along the way",{"type":20,"tag":56,"props":1916,"children":1917},{},[1918,1920,1926,1927],{"type":30,"value":1919},"While ",{"type":20,"tag":275,"props":1921,"children":1923},{"className":1922},[],[1924],{"type":30,"value":1925},"numUnmigratedFeatures > 0",{"type":30,"value":1701},{"type":20,"tag":52,"props":1928,"children":1929},{},[1930,1942,1954,1959,1964],{"type":20,"tag":56,"props":1931,"children":1932},{},[1933,1935,1940],{"type":30,"value":1934},"Use the ",{"type":20,"tag":109,"props":1936,"children":1938},{"href":1717,"rel":1937},[113],[1939],{"type":30,"value":1721},{"type":30,"value":1941}," documentation to identify the next compatibility feature to disable",{"type":20,"tag":56,"props":1943,"children":1944},{},[1945,1947,1952],{"type":30,"value":1946},"Switch to v3 behavior by setting ",{"type":20,"tag":275,"props":1948,"children":1950},{"className":1949},[],[1951],{"type":30,"value":1797},{"type":30,"value":1953}," in your Migration Build configuration",{"type":20,"tag":56,"props":1955,"children":1956},{},[1957],{"type":30,"value":1958},"Use the documentation link in the Feature Reference to jump to relevant information about how to migrate to v3",{"type":20,"tag":56,"props":1960,"children":1961},{},[1962],{"type":30,"value":1963},"Migrate all occurrences in your project",{"type":20,"tag":56,"props":1965,"children":1966},{},[1967],{"type":30,"value":1968},"Merge this change into your mainline development branch (which ensures that any subsequent commits must use the v3 behavior)",{"type":20,"tag":33,"props":1970,"children":1971},{},[1972,1974,1981,1983,1990],{"type":30,"value":1973},"This easy and comprehensive upgrade process, combined with how infrequently Vue makes breaking API changes to begin with, arguably makes it best-in-class among JavaScript UI framworks in terms of long-term maintenance (although React is ",{"type":20,"tag":109,"props":1975,"children":1978},{"href":1976,"rel":1977},"https://reactjs.org/blog/2020/10/20/react-v17.html#gradual-upgrades",[113],[1979],{"type":30,"value":1980},"introducing a similar upgrade process",{"type":30,"value":1982}," for the upcoming version 18). It's one of the reasons ",{"type":20,"tag":109,"props":1984,"children":1987},{"href":1985,"rel":1986},"https://artandlogic.com/2020/02/why-vue/",[113],[1988],{"type":30,"value":1989},"why we recommend Vue",{"type":30,"value":118},{"type":20,"tag":1200,"props":1992,"children":1993},{},[1994],{"type":30,"value":1204},{"title":8,"searchDepth":308,"depth":308,"links":1996},[1997,1998,1999,2000],{"id":1422,"depth":292,"text":1425},{"id":1523,"depth":292,"text":1526},{"id":1590,"depth":292,"text":1593},{"id":1870,"depth":292,"text":1873},"content:phendry:2021-06:SmoothUpgradesToVue3.md","phendry/2021-06/SmoothUpgradesToVue3.md","phendry/2021-06/SmoothUpgradesToVue3",{"user":2005,"name":2006},"phendry","Paul Hendry",{"_path":2008,"_dir":2009,"_draft":7,"_partial":7,"_locale":8,"title":2010,"description":2011,"publishDate":2012,"tags":2013,"excerpt":2011,"body":2015,"_type":1210,"_id":3637,"_source":1212,"_file":3638,"_stem":3639,"_extension":1215,"author":3640},"/ckeefer/2014-4/hidden-options","2014-4","Hidden Options: A Workaround","Here's the situation: You've got a select. Maybe a whole bunch of selects, with a ton of options each (metric ton - let's keep our imaginary hyperbolic units straight here); and these are meant to be complex interactive elements, with options made visible or not as some programmatic condition dictates.","2014-04-23",[13,1225,2014],"jquery",{"type":17,"children":2016,"toc":3635},[2017,2021,2026,2039,2102,2107,2112,2156,2161,3439,3444,3500,3505,3567,3593,3607,3612,3626,3631],{"type":20,"tag":33,"props":2018,"children":2019},{},[2020],{"type":30,"value":2011},{"type":20,"tag":33,"props":2022,"children":2023},{},[2024],{"type":30,"value":2025},"Traditionally, if you wanted to selectively display options, you had to do it the hard way - remove the non-visible option nodes entirely. What, did you want to filter on some state information stored on the node? Too bad - you'll have to keep track of the full structure outside of the DOM and filter on that, inserting or removing elements as needed.",{"type":20,"tag":33,"props":2027,"children":2028},{},[2029,2031,2037],{"type":30,"value":2030},"This is sub-optimal. It's much tidier if we can just set ",{"type":20,"tag":275,"props":2032,"children":2034},{"className":2033},[],[2035],{"type":30,"value":2036},"display:none",{"type":30,"value":2038}," on our options elements, and have them hidden like any other DOM element:",{"type":20,"tag":269,"props":2040,"children":2044},{"className":2041,"code":2042,"language":2043,"meta":8,"style":8},"language-css shiki shiki-themes github-light github-dark","option[disabled]{\n    display:none;\n}\n","css",[2045],{"type":20,"tag":275,"props":2046,"children":2047},{"__ignoreMap":8},[2048,2072,2094],{"type":20,"tag":279,"props":2049,"children":2050},{"class":281,"line":282},[2051,2057,2062,2067],{"type":20,"tag":279,"props":2052,"children":2054},{"style":2053},"--shiki-default:#22863A;--shiki-dark:#85E89D",[2055],{"type":30,"value":2056},"option",{"type":20,"tag":279,"props":2058,"children":2059},{"style":302},[2060],{"type":30,"value":2061},"[",{"type":20,"tag":279,"props":2063,"children":2064},{"style":444},[2065],{"type":30,"value":2066},"disabled",{"type":20,"tag":279,"props":2068,"children":2069},{"style":302},[2070],{"type":30,"value":2071},"]{\n",{"type":20,"tag":279,"props":2073,"children":2074},{"class":281,"line":292},[2075,2080,2085,2090],{"type":20,"tag":279,"props":2076,"children":2077},{"style":480},[2078],{"type":30,"value":2079},"    display",{"type":20,"tag":279,"props":2081,"children":2082},{"style":302},[2083],{"type":30,"value":2084},":",{"type":20,"tag":279,"props":2086,"children":2087},{"style":480},[2088],{"type":30,"value":2089},"none",{"type":20,"tag":279,"props":2091,"children":2092},{"style":302},[2093],{"type":30,"value":1647},{"type":20,"tag":279,"props":2095,"children":2096},{"class":281,"line":308},[2097],{"type":20,"tag":279,"props":2098,"children":2099},{"style":302},[2100],{"type":30,"value":2101},"}\n",{"type":20,"tag":33,"props":2103,"children":2104},{},[2105],{"type":30,"value":2106},"and in most modern browsers (Firefox, IE9+, Safari), this works just fine. We can then filter in place on the elements, and selectively display them.",{"type":20,"tag":33,"props":2108,"children":2109},{},[2110],{"type":30,"value":2111},"Have you noticed the glaring omission yet? Yes, Chrome isn't among the browsers this works 'just fine' in.",{"type":20,"tag":33,"props":2113,"children":2114},{},[2115,2117,2122,2124,2129,2131,2138,2140,2147,2149,2154],{"type":30,"value":2116},"You ",{"type":20,"tag":1235,"props":2118,"children":2119},{},[2120],{"type":30,"value":2121},"can",{"type":30,"value":2123}," set ",{"type":20,"tag":275,"props":2125,"children":2127},{"className":2126},[],[2128],{"type":30,"value":2036},{"type":30,"value":2130}," on your disabled options to hide them, and that will work - but as ",{"type":20,"tag":109,"props":2132,"children":2135},{"href":2133,"rel":2134},"http://stackoverflow.com/questions/17203826/chrome-bug-on-select-element-dropdown-when-many-options-are-hidden",[113],[2136],{"type":30,"value":2137},"stackoverflow user JMack discovered",{"type":30,"value":2139},", and per ",{"type":20,"tag":109,"props":2141,"children":2144},{"href":2142,"rel":2143},"http://code.google.com/p/chromium/issues/detail?id=139595",[113],[2145],{"type":30,"value":2146},"this long-standing chromium bug",{"type":30,"value":2148}," (open since Jul 30 2012, with the most recent activity on it a ",{"type":20,"tag":60,"props":2150,"children":2151},{},[2152],{"type":30,"value":2153},"downgrade",{"type":30,"value":2155}," in priority), when you have hidden options in your select, the select dropdown will fail to resize itself appropriately - to the point that the dropdown may not show anything beyond the initial visible option, with the rest of the visible options hidden beneath. They're still selectable, and can be scrolled to, but the dropdown list will be tiny and scrolling won't work quite right, often leaving you with the top and bottom of the next two options visible.",{"type":20,"tag":33,"props":2157,"children":2158},{},[2159],{"type":30,"value":2160},"We're not here to complain, though - we're here to get things done. Let's whip up a workaround, and then discuss how to use it, how and why it works.",{"type":20,"tag":269,"props":2162,"children":2166},{"className":2163,"code":2164,"language":2165,"meta":8,"style":8},"language-javascript shiki shiki-themes github-light github-dark","(function($){\n    var userAgent = window.navigator.userAgent,\n        needsWrap = (userAgent.indexOf('Trident') !== -1 || userAgent.indexOf('AppleWebKit') !== -1);\n    /**\n     * Workaround for browsers that respect hidden options elements, but then fail to resize the select dropdown\n     * to display any visible elements beyond those that appear before any hidden elements - namely, Chrome.\n     * Based on the filter function, we either select all options that match (or, if invert is true, all that\n     * don't match), set them to disabled (with the expectation that there's a css rule hiding disabled options\n     * somewhere), and then pull the disabled options out of the DOM and insert them back in at the end of the\n     * select - this is tested as working in the most recent version of Chrome (as of this writing, v34).\n     * See also http://code.google.com/p/chromium/issues/detail?id=139595 and\n     * http://stackoverflow.com/questions/17203826/chrome-bug-on-select-element-dropdown-when-many-options-are-hidden\n     * for reports of the browser bug this works around.\n     *\n     * Additionally, we handle browsers that DON'T respect hidden options, by agent-sniffing such browsers\n     * and wrapping and unwrapping as necessary options that we want hidden in a span tag.\n     *\n     * @note This works, but DOM manipulation in IE is SLOW - much slower to perform an appendChild operation\n     * than any of the other major browsers. Wrapping/unwrapping large sets of options will take a relative long time (>2s)\n     * and have the potential to hang the UI thread.\n     * @param {string|HtmlElement|jQuery} el\n     * @param {function} filter\n     * @param {boolean=} invert\n     * @returns {jQuery}\n     */\n    $.elideOptions = function(el, filter, invert){\n        var $el = (el instanceof $) ? el : $(el);\n\n        $el.each(function(){\n            if (this.tagName !== 'SELECT') return;\n            var $this = $(this),\n                opts = $this.find('option').prop('disabled', false),\n                spans;\n\n            // Unwrap all options from their span tags\n            if (needsWrap && (spans = $this.find('span')) && spans.length){\n                spans.children().unwrap();\n            }\n\n            opts = (invert) ? opts.not(filter).prop('disabled', true) :\n                opts.filter(filter).prop('disabled', true);\n\n            // Wrap options in a single hidden span to hide them on browsers that don't support\n            // display:none for options\n            if (needsWrap) {\n                opts.wrapAll('\u003Cspan class=\"hide\">\u003C/span>');\n                opts = opts.parent('span');\n            }\n\n            opts.detach().appendTo($this);\n        });\n\n        return $el;\n    };\n\n    /**\n     * Allow for the $(element) form of invocation.\n     * @param {function} filter\n     * @param {boolean=} invert\n     * @returns {jQuery}\n     */\n    $.fn.elideOptions = function(filter, invert){\n        return $.elideOptions(this, filter, invert);\n    };\n})(jQuery);\n","javascript",[2167],{"type":20,"tag":275,"props":2168,"children":2169},{"__ignoreMap":8},[2170,2197,2219,2314,2322,2330,2338,2346,2354,2362,2370,2378,2386,2394,2402,2410,2418,2425,2443,2451,2459,2481,2502,2523,2540,2548,2602,2661,2668,2694,2739,2773,2835,2844,2852,2861,2928,2957,2966,2974,3039,3080,3088,3097,3106,3119,3145,3178,3186,3194,3222,3231,3239,3253,3262,3270,3278,3287,3307,3327,3343,3351,3392,3422,3430],{"type":20,"tag":279,"props":2171,"children":2172},{"class":281,"line":282},[2173,2178,2183,2187,2192],{"type":20,"tag":279,"props":2174,"children":2175},{"style":302},[2176],{"type":30,"value":2177},"(",{"type":20,"tag":279,"props":2179,"children":2180},{"style":296},[2181],{"type":30,"value":2182},"function",{"type":20,"tag":279,"props":2184,"children":2185},{"style":302},[2186],{"type":30,"value":2177},{"type":20,"tag":279,"props":2188,"children":2189},{"style":1035},[2190],{"type":30,"value":2191},"$",{"type":20,"tag":279,"props":2193,"children":2194},{"style":302},[2195],{"type":30,"value":2196},"){\n",{"type":20,"tag":279,"props":2198,"children":2199},{"class":281,"line":292},[2200,2205,2210,2214],{"type":20,"tag":279,"props":2201,"children":2202},{"style":296},[2203],{"type":30,"value":2204},"    var",{"type":20,"tag":279,"props":2206,"children":2207},{"style":302},[2208],{"type":30,"value":2209}," userAgent ",{"type":20,"tag":279,"props":2211,"children":2212},{"style":296},[2213],{"type":30,"value":337},{"type":20,"tag":279,"props":2215,"children":2216},{"style":302},[2217],{"type":30,"value":2218}," window.navigator.userAgent,\n",{"type":20,"tag":279,"props":2220,"children":2221},{"class":281,"line":308},[2222,2227,2231,2236,2241,2245,2250,2255,2260,2265,2270,2275,2280,2284,2288,2293,2297,2301,2305,2309],{"type":20,"tag":279,"props":2223,"children":2224},{"style":302},[2225],{"type":30,"value":2226},"        needsWrap ",{"type":20,"tag":279,"props":2228,"children":2229},{"style":296},[2230],{"type":30,"value":337},{"type":20,"tag":279,"props":2232,"children":2233},{"style":302},[2234],{"type":30,"value":2235}," (userAgent.",{"type":20,"tag":279,"props":2237,"children":2238},{"style":444},[2239],{"type":30,"value":2240},"indexOf",{"type":20,"tag":279,"props":2242,"children":2243},{"style":302},[2244],{"type":30,"value":2177},{"type":20,"tag":279,"props":2246,"children":2247},{"style":458},[2248],{"type":30,"value":2249},"'Trident'",{"type":20,"tag":279,"props":2251,"children":2252},{"style":302},[2253],{"type":30,"value":2254},") ",{"type":20,"tag":279,"props":2256,"children":2257},{"style":296},[2258],{"type":30,"value":2259},"!==",{"type":20,"tag":279,"props":2261,"children":2262},{"style":296},[2263],{"type":30,"value":2264}," -",{"type":20,"tag":279,"props":2266,"children":2267},{"style":480},[2268],{"type":30,"value":2269},"1",{"type":20,"tag":279,"props":2271,"children":2272},{"style":296},[2273],{"type":30,"value":2274}," ||",{"type":20,"tag":279,"props":2276,"children":2277},{"style":302},[2278],{"type":30,"value":2279}," userAgent.",{"type":20,"tag":279,"props":2281,"children":2282},{"style":444},[2283],{"type":30,"value":2240},{"type":20,"tag":279,"props":2285,"children":2286},{"style":302},[2287],{"type":30,"value":2177},{"type":20,"tag":279,"props":2289,"children":2290},{"style":458},[2291],{"type":30,"value":2292},"'AppleWebKit'",{"type":20,"tag":279,"props":2294,"children":2295},{"style":302},[2296],{"type":30,"value":2254},{"type":20,"tag":279,"props":2298,"children":2299},{"style":296},[2300],{"type":30,"value":2259},{"type":20,"tag":279,"props":2302,"children":2303},{"style":296},[2304],{"type":30,"value":2264},{"type":20,"tag":279,"props":2306,"children":2307},{"style":480},[2308],{"type":30,"value":2269},{"type":20,"tag":279,"props":2310,"children":2311},{"style":302},[2312],{"type":30,"value":2313},");\n",{"type":20,"tag":279,"props":2315,"children":2316},{"class":281,"line":316},[2317],{"type":20,"tag":279,"props":2318,"children":2319},{"style":320},[2320],{"type":30,"value":2321},"    /**\n",{"type":20,"tag":279,"props":2323,"children":2324},{"class":281,"line":326},[2325],{"type":20,"tag":279,"props":2326,"children":2327},{"style":320},[2328],{"type":30,"value":2329},"     * Workaround for browsers that respect hidden options elements, but then fail to resize the select dropdown\n",{"type":20,"tag":279,"props":2331,"children":2332},{"class":281,"line":345},[2333],{"type":20,"tag":279,"props":2334,"children":2335},{"style":320},[2336],{"type":30,"value":2337},"     * to display any visible elements beyond those that appear before any hidden elements - namely, Chrome.\n",{"type":20,"tag":279,"props":2339,"children":2340},{"class":281,"line":362},[2341],{"type":20,"tag":279,"props":2342,"children":2343},{"style":320},[2344],{"type":30,"value":2345},"     * Based on the filter function, we either select all options that match (or, if invert is true, all that\n",{"type":20,"tag":279,"props":2347,"children":2348},{"class":281,"line":370},[2349],{"type":20,"tag":279,"props":2350,"children":2351},{"style":320},[2352],{"type":30,"value":2353},"     * don't match), set them to disabled (with the expectation that there's a css rule hiding disabled options\n",{"type":20,"tag":279,"props":2355,"children":2356},{"class":281,"line":379},[2357],{"type":20,"tag":279,"props":2358,"children":2359},{"style":320},[2360],{"type":30,"value":2361},"     * somewhere), and then pull the disabled options out of the DOM and insert them back in at the end of the\n",{"type":20,"tag":279,"props":2363,"children":2364},{"class":281,"line":388},[2365],{"type":20,"tag":279,"props":2366,"children":2367},{"style":320},[2368],{"type":30,"value":2369},"     * select - this is tested as working in the most recent version of Chrome (as of this writing, v34).\n",{"type":20,"tag":279,"props":2371,"children":2372},{"class":281,"line":569},[2373],{"type":20,"tag":279,"props":2374,"children":2375},{"style":320},[2376],{"type":30,"value":2377},"     * See also http://code.google.com/p/chromium/issues/detail?id=139595 and\n",{"type":20,"tag":279,"props":2379,"children":2380},{"class":281,"line":577},[2381],{"type":20,"tag":279,"props":2382,"children":2383},{"style":320},[2384],{"type":30,"value":2385},"     * http://stackoverflow.com/questions/17203826/chrome-bug-on-select-element-dropdown-when-many-options-are-hidden\n",{"type":20,"tag":279,"props":2387,"children":2388},{"class":281,"line":586},[2389],{"type":20,"tag":279,"props":2390,"children":2391},{"style":320},[2392],{"type":30,"value":2393},"     * for reports of the browser bug this works around.\n",{"type":20,"tag":279,"props":2395,"children":2396},{"class":281,"line":604},[2397],{"type":20,"tag":279,"props":2398,"children":2399},{"style":320},[2400],{"type":30,"value":2401},"     *\n",{"type":20,"tag":279,"props":2403,"children":2404},{"class":281,"line":612},[2405],{"type":20,"tag":279,"props":2406,"children":2407},{"style":320},[2408],{"type":30,"value":2409},"     * Additionally, we handle browsers that DON'T respect hidden options, by agent-sniffing such browsers\n",{"type":20,"tag":279,"props":2411,"children":2412},{"class":281,"line":621},[2413],{"type":20,"tag":279,"props":2414,"children":2415},{"style":320},[2416],{"type":30,"value":2417},"     * and wrapping and unwrapping as necessary options that we want hidden in a span tag.\n",{"type":20,"tag":279,"props":2419,"children":2420},{"class":281,"line":639},[2421],{"type":20,"tag":279,"props":2422,"children":2423},{"style":320},[2424],{"type":30,"value":2401},{"type":20,"tag":279,"props":2426,"children":2427},{"class":281,"line":647},[2428,2433,2438],{"type":20,"tag":279,"props":2429,"children":2430},{"style":320},[2431],{"type":30,"value":2432},"     * ",{"type":20,"tag":279,"props":2434,"children":2435},{"style":296},[2436],{"type":30,"value":2437},"@note",{"type":20,"tag":279,"props":2439,"children":2440},{"style":320},[2441],{"type":30,"value":2442}," This works, but DOM manipulation in IE is SLOW - much slower to perform an appendChild operation\n",{"type":20,"tag":279,"props":2444,"children":2445},{"class":281,"line":656},[2446],{"type":20,"tag":279,"props":2447,"children":2448},{"style":320},[2449],{"type":30,"value":2450},"     * than any of the other major browsers. Wrapping/unwrapping large sets of options will take a relative long time (>2s)\n",{"type":20,"tag":279,"props":2452,"children":2453},{"class":281,"line":665},[2454],{"type":20,"tag":279,"props":2455,"children":2456},{"style":320},[2457],{"type":30,"value":2458},"     * and have the potential to hang the UI thread.\n",{"type":20,"tag":279,"props":2460,"children":2461},{"class":281,"line":674},[2462,2466,2471,2476],{"type":20,"tag":279,"props":2463,"children":2464},{"style":320},[2465],{"type":30,"value":2432},{"type":20,"tag":279,"props":2467,"children":2468},{"style":296},[2469],{"type":30,"value":2470},"@param",{"type":20,"tag":279,"props":2472,"children":2473},{"style":444},[2474],{"type":30,"value":2475}," {string|HtmlElement|jQuery}",{"type":20,"tag":279,"props":2477,"children":2478},{"style":302},[2479],{"type":30,"value":2480}," el\n",{"type":20,"tag":279,"props":2482,"children":2483},{"class":281,"line":682},[2484,2488,2492,2497],{"type":20,"tag":279,"props":2485,"children":2486},{"style":320},[2487],{"type":30,"value":2432},{"type":20,"tag":279,"props":2489,"children":2490},{"style":296},[2491],{"type":30,"value":2470},{"type":20,"tag":279,"props":2493,"children":2494},{"style":444},[2495],{"type":30,"value":2496}," {function}",{"type":20,"tag":279,"props":2498,"children":2499},{"style":302},[2500],{"type":30,"value":2501}," filter\n",{"type":20,"tag":279,"props":2503,"children":2504},{"class":281,"line":706},[2505,2509,2513,2518],{"type":20,"tag":279,"props":2506,"children":2507},{"style":320},[2508],{"type":30,"value":2432},{"type":20,"tag":279,"props":2510,"children":2511},{"style":296},[2512],{"type":30,"value":2470},{"type":20,"tag":279,"props":2514,"children":2515},{"style":444},[2516],{"type":30,"value":2517}," {boolean=}",{"type":20,"tag":279,"props":2519,"children":2520},{"style":302},[2521],{"type":30,"value":2522}," invert\n",{"type":20,"tag":279,"props":2524,"children":2525},{"class":281,"line":715},[2526,2530,2535],{"type":20,"tag":279,"props":2527,"children":2528},{"style":320},[2529],{"type":30,"value":2432},{"type":20,"tag":279,"props":2531,"children":2532},{"style":296},[2533],{"type":30,"value":2534},"@returns",{"type":20,"tag":279,"props":2536,"children":2537},{"style":444},[2538],{"type":30,"value":2539}," {jQuery}\n",{"type":20,"tag":279,"props":2541,"children":2542},{"class":281,"line":753},[2543],{"type":20,"tag":279,"props":2544,"children":2545},{"style":320},[2546],{"type":30,"value":2547},"     */\n",{"type":20,"tag":279,"props":2549,"children":2550},{"class":281,"line":761},[2551,2556,2561,2566,2571,2575,2580,2584,2589,2593,2598],{"type":20,"tag":279,"props":2552,"children":2553},{"style":302},[2554],{"type":30,"value":2555},"    $.",{"type":20,"tag":279,"props":2557,"children":2558},{"style":444},[2559],{"type":30,"value":2560},"elideOptions",{"type":20,"tag":279,"props":2562,"children":2563},{"style":296},[2564],{"type":30,"value":2565}," =",{"type":20,"tag":279,"props":2567,"children":2568},{"style":296},[2569],{"type":30,"value":2570}," function",{"type":20,"tag":279,"props":2572,"children":2573},{"style":302},[2574],{"type":30,"value":2177},{"type":20,"tag":279,"props":2576,"children":2577},{"style":1035},[2578],{"type":30,"value":2579},"el",{"type":20,"tag":279,"props":2581,"children":2582},{"style":302},[2583],{"type":30,"value":1032},{"type":20,"tag":279,"props":2585,"children":2586},{"style":1035},[2587],{"type":30,"value":2588},"filter",{"type":20,"tag":279,"props":2590,"children":2591},{"style":302},[2592],{"type":30,"value":1032},{"type":20,"tag":279,"props":2594,"children":2595},{"style":1035},[2596],{"type":30,"value":2597},"invert",{"type":20,"tag":279,"props":2599,"children":2600},{"style":302},[2601],{"type":30,"value":2196},{"type":20,"tag":279,"props":2603,"children":2604},{"class":281,"line":774},[2605,2610,2615,2619,2624,2629,2634,2638,2643,2648,2652,2656],{"type":20,"tag":279,"props":2606,"children":2607},{"style":296},[2608],{"type":30,"value":2609},"        var",{"type":20,"tag":279,"props":2611,"children":2612},{"style":302},[2613],{"type":30,"value":2614}," $el ",{"type":20,"tag":279,"props":2616,"children":2617},{"style":296},[2618],{"type":30,"value":337},{"type":20,"tag":279,"props":2620,"children":2621},{"style":302},[2622],{"type":30,"value":2623}," (el ",{"type":20,"tag":279,"props":2625,"children":2626},{"style":296},[2627],{"type":30,"value":2628},"instanceof",{"type":20,"tag":279,"props":2630,"children":2631},{"style":444},[2632],{"type":30,"value":2633}," $",{"type":20,"tag":279,"props":2635,"children":2636},{"style":302},[2637],{"type":30,"value":2254},{"type":20,"tag":279,"props":2639,"children":2640},{"style":296},[2641],{"type":30,"value":2642},"?",{"type":20,"tag":279,"props":2644,"children":2645},{"style":302},[2646],{"type":30,"value":2647}," el ",{"type":20,"tag":279,"props":2649,"children":2650},{"style":296},[2651],{"type":30,"value":2084},{"type":20,"tag":279,"props":2653,"children":2654},{"style":444},[2655],{"type":30,"value":2633},{"type":20,"tag":279,"props":2657,"children":2658},{"style":302},[2659],{"type":30,"value":2660},"(el);\n",{"type":20,"tag":279,"props":2662,"children":2663},{"class":281,"line":783},[2664],{"type":20,"tag":279,"props":2665,"children":2666},{"emptyLinePlaceholder":286},[2667],{"type":30,"value":289},{"type":20,"tag":279,"props":2669,"children":2670},{"class":281,"line":1147},[2671,2676,2681,2685,2689],{"type":20,"tag":279,"props":2672,"children":2673},{"style":302},[2674],{"type":30,"value":2675},"        $el.",{"type":20,"tag":279,"props":2677,"children":2678},{"style":444},[2679],{"type":30,"value":2680},"each",{"type":20,"tag":279,"props":2682,"children":2683},{"style":302},[2684],{"type":30,"value":2177},{"type":20,"tag":279,"props":2686,"children":2687},{"style":296},[2688],{"type":30,"value":2182},{"type":20,"tag":279,"props":2690,"children":2691},{"style":302},[2692],{"type":30,"value":2693},"(){\n",{"type":20,"tag":279,"props":2695,"children":2696},{"class":281,"line":1155},[2697,2702,2707,2712,2717,2721,2726,2730,2735],{"type":20,"tag":279,"props":2698,"children":2699},{"style":296},[2700],{"type":30,"value":2701},"            if",{"type":20,"tag":279,"props":2703,"children":2704},{"style":302},[2705],{"type":30,"value":2706}," (",{"type":20,"tag":279,"props":2708,"children":2709},{"style":480},[2710],{"type":30,"value":2711},"this",{"type":20,"tag":279,"props":2713,"children":2714},{"style":302},[2715],{"type":30,"value":2716},".tagName ",{"type":20,"tag":279,"props":2718,"children":2719},{"style":296},[2720],{"type":30,"value":2259},{"type":20,"tag":279,"props":2722,"children":2723},{"style":458},[2724],{"type":30,"value":2725}," 'SELECT'",{"type":20,"tag":279,"props":2727,"children":2728},{"style":302},[2729],{"type":30,"value":2254},{"type":20,"tag":279,"props":2731,"children":2732},{"style":296},[2733],{"type":30,"value":2734},"return",{"type":20,"tag":279,"props":2736,"children":2737},{"style":302},[2738],{"type":30,"value":1647},{"type":20,"tag":279,"props":2740,"children":2741},{"class":281,"line":1167},[2742,2747,2752,2756,2760,2764,2768],{"type":20,"tag":279,"props":2743,"children":2744},{"style":296},[2745],{"type":30,"value":2746},"            var",{"type":20,"tag":279,"props":2748,"children":2749},{"style":302},[2750],{"type":30,"value":2751}," $this ",{"type":20,"tag":279,"props":2753,"children":2754},{"style":296},[2755],{"type":30,"value":337},{"type":20,"tag":279,"props":2757,"children":2758},{"style":444},[2759],{"type":30,"value":2633},{"type":20,"tag":279,"props":2761,"children":2762},{"style":302},[2763],{"type":30,"value":2177},{"type":20,"tag":279,"props":2765,"children":2766},{"style":480},[2767],{"type":30,"value":2711},{"type":20,"tag":279,"props":2769,"children":2770},{"style":302},[2771],{"type":30,"value":2772},"),\n",{"type":20,"tag":279,"props":2774,"children":2775},{"class":281,"line":1175},[2776,2781,2785,2790,2795,2799,2804,2809,2814,2818,2823,2827,2831],{"type":20,"tag":279,"props":2777,"children":2778},{"style":302},[2779],{"type":30,"value":2780},"                opts ",{"type":20,"tag":279,"props":2782,"children":2783},{"style":296},[2784],{"type":30,"value":337},{"type":20,"tag":279,"props":2786,"children":2787},{"style":302},[2788],{"type":30,"value":2789}," $this.",{"type":20,"tag":279,"props":2791,"children":2792},{"style":444},[2793],{"type":30,"value":2794},"find",{"type":20,"tag":279,"props":2796,"children":2797},{"style":302},[2798],{"type":30,"value":2177},{"type":20,"tag":279,"props":2800,"children":2801},{"style":458},[2802],{"type":30,"value":2803},"'option'",{"type":20,"tag":279,"props":2805,"children":2806},{"style":302},[2807],{"type":30,"value":2808},").",{"type":20,"tag":279,"props":2810,"children":2811},{"style":444},[2812],{"type":30,"value":2813},"prop",{"type":20,"tag":279,"props":2815,"children":2816},{"style":302},[2817],{"type":30,"value":2177},{"type":20,"tag":279,"props":2819,"children":2820},{"style":458},[2821],{"type":30,"value":2822},"'disabled'",{"type":20,"tag":279,"props":2824,"children":2825},{"style":302},[2826],{"type":30,"value":1032},{"type":20,"tag":279,"props":2828,"children":2829},{"style":480},[2830],{"type":30,"value":1696},{"type":20,"tag":279,"props":2832,"children":2833},{"style":302},[2834],{"type":30,"value":2772},{"type":20,"tag":279,"props":2836,"children":2838},{"class":281,"line":2837},33,[2839],{"type":20,"tag":279,"props":2840,"children":2841},{"style":302},[2842],{"type":30,"value":2843},"                spans;\n",{"type":20,"tag":279,"props":2845,"children":2847},{"class":281,"line":2846},34,[2848],{"type":20,"tag":279,"props":2849,"children":2850},{"emptyLinePlaceholder":286},[2851],{"type":30,"value":289},{"type":20,"tag":279,"props":2853,"children":2855},{"class":281,"line":2854},35,[2856],{"type":20,"tag":279,"props":2857,"children":2858},{"style":320},[2859],{"type":30,"value":2860},"            // Unwrap all options from their span tags\n",{"type":20,"tag":279,"props":2862,"children":2864},{"class":281,"line":2863},36,[2865,2869,2874,2879,2884,2888,2892,2896,2900,2905,2910,2914,2919,2924],{"type":20,"tag":279,"props":2866,"children":2867},{"style":296},[2868],{"type":30,"value":2701},{"type":20,"tag":279,"props":2870,"children":2871},{"style":302},[2872],{"type":30,"value":2873}," (needsWrap ",{"type":20,"tag":279,"props":2875,"children":2876},{"style":296},[2877],{"type":30,"value":2878},"&&",{"type":20,"tag":279,"props":2880,"children":2881},{"style":302},[2882],{"type":30,"value":2883}," (spans ",{"type":20,"tag":279,"props":2885,"children":2886},{"style":296},[2887],{"type":30,"value":337},{"type":20,"tag":279,"props":2889,"children":2890},{"style":302},[2891],{"type":30,"value":2789},{"type":20,"tag":279,"props":2893,"children":2894},{"style":444},[2895],{"type":30,"value":2794},{"type":20,"tag":279,"props":2897,"children":2898},{"style":302},[2899],{"type":30,"value":2177},{"type":20,"tag":279,"props":2901,"children":2902},{"style":458},[2903],{"type":30,"value":2904},"'span'",{"type":20,"tag":279,"props":2906,"children":2907},{"style":302},[2908],{"type":30,"value":2909},")) ",{"type":20,"tag":279,"props":2911,"children":2912},{"style":296},[2913],{"type":30,"value":2878},{"type":20,"tag":279,"props":2915,"children":2916},{"style":302},[2917],{"type":30,"value":2918}," spans.",{"type":20,"tag":279,"props":2920,"children":2921},{"style":480},[2922],{"type":30,"value":2923},"length",{"type":20,"tag":279,"props":2925,"children":2926},{"style":302},[2927],{"type":30,"value":2196},{"type":20,"tag":279,"props":2929,"children":2931},{"class":281,"line":2930},37,[2932,2937,2942,2947,2952],{"type":20,"tag":279,"props":2933,"children":2934},{"style":302},[2935],{"type":30,"value":2936},"                spans.",{"type":20,"tag":279,"props":2938,"children":2939},{"style":444},[2940],{"type":30,"value":2941},"children",{"type":20,"tag":279,"props":2943,"children":2944},{"style":302},[2945],{"type":30,"value":2946},"().",{"type":20,"tag":279,"props":2948,"children":2949},{"style":444},[2950],{"type":30,"value":2951},"unwrap",{"type":20,"tag":279,"props":2953,"children":2954},{"style":302},[2955],{"type":30,"value":2956},"();\n",{"type":20,"tag":279,"props":2958,"children":2960},{"class":281,"line":2959},38,[2961],{"type":20,"tag":279,"props":2962,"children":2963},{"style":302},[2964],{"type":30,"value":2965},"            }\n",{"type":20,"tag":279,"props":2967,"children":2969},{"class":281,"line":2968},39,[2970],{"type":20,"tag":279,"props":2971,"children":2972},{"emptyLinePlaceholder":286},[2973],{"type":30,"value":289},{"type":20,"tag":279,"props":2975,"children":2977},{"class":281,"line":2976},40,[2978,2983,2987,2992,2996,3001,3005,3010,3014,3018,3022,3026,3031,3035],{"type":20,"tag":279,"props":2979,"children":2980},{"style":302},[2981],{"type":30,"value":2982},"            opts ",{"type":20,"tag":279,"props":2984,"children":2985},{"style":296},[2986],{"type":30,"value":337},{"type":20,"tag":279,"props":2988,"children":2989},{"style":302},[2990],{"type":30,"value":2991}," (invert) ",{"type":20,"tag":279,"props":2993,"children":2994},{"style":296},[2995],{"type":30,"value":2642},{"type":20,"tag":279,"props":2997,"children":2998},{"style":302},[2999],{"type":30,"value":3000}," opts.",{"type":20,"tag":279,"props":3002,"children":3003},{"style":444},[3004],{"type":30,"value":1435},{"type":20,"tag":279,"props":3006,"children":3007},{"style":302},[3008],{"type":30,"value":3009},"(filter).",{"type":20,"tag":279,"props":3011,"children":3012},{"style":444},[3013],{"type":30,"value":2813},{"type":20,"tag":279,"props":3015,"children":3016},{"style":302},[3017],{"type":30,"value":2177},{"type":20,"tag":279,"props":3019,"children":3020},{"style":458},[3021],{"type":30,"value":2822},{"type":20,"tag":279,"props":3023,"children":3024},{"style":302},[3025],{"type":30,"value":1032},{"type":20,"tag":279,"props":3027,"children":3028},{"style":480},[3029],{"type":30,"value":3030},"true",{"type":20,"tag":279,"props":3032,"children":3033},{"style":302},[3034],{"type":30,"value":2254},{"type":20,"tag":279,"props":3036,"children":3037},{"style":296},[3038],{"type":30,"value":488},{"type":20,"tag":279,"props":3040,"children":3042},{"class":281,"line":3041},41,[3043,3048,3052,3056,3060,3064,3068,3072,3076],{"type":20,"tag":279,"props":3044,"children":3045},{"style":302},[3046],{"type":30,"value":3047},"                opts.",{"type":20,"tag":279,"props":3049,"children":3050},{"style":444},[3051],{"type":30,"value":2588},{"type":20,"tag":279,"props":3053,"children":3054},{"style":302},[3055],{"type":30,"value":3009},{"type":20,"tag":279,"props":3057,"children":3058},{"style":444},[3059],{"type":30,"value":2813},{"type":20,"tag":279,"props":3061,"children":3062},{"style":302},[3063],{"type":30,"value":2177},{"type":20,"tag":279,"props":3065,"children":3066},{"style":458},[3067],{"type":30,"value":2822},{"type":20,"tag":279,"props":3069,"children":3070},{"style":302},[3071],{"type":30,"value":1032},{"type":20,"tag":279,"props":3073,"children":3074},{"style":480},[3075],{"type":30,"value":3030},{"type":20,"tag":279,"props":3077,"children":3078},{"style":302},[3079],{"type":30,"value":2313},{"type":20,"tag":279,"props":3081,"children":3083},{"class":281,"line":3082},42,[3084],{"type":20,"tag":279,"props":3085,"children":3086},{"emptyLinePlaceholder":286},[3087],{"type":30,"value":289},{"type":20,"tag":279,"props":3089,"children":3091},{"class":281,"line":3090},43,[3092],{"type":20,"tag":279,"props":3093,"children":3094},{"style":320},[3095],{"type":30,"value":3096},"            // Wrap options in a single hidden span to hide them on browsers that don't support\n",{"type":20,"tag":279,"props":3098,"children":3100},{"class":281,"line":3099},44,[3101],{"type":20,"tag":279,"props":3102,"children":3103},{"style":320},[3104],{"type":30,"value":3105},"            // display:none for options\n",{"type":20,"tag":279,"props":3107,"children":3109},{"class":281,"line":3108},45,[3110,3114],{"type":20,"tag":279,"props":3111,"children":3112},{"style":296},[3113],{"type":30,"value":2701},{"type":20,"tag":279,"props":3115,"children":3116},{"style":302},[3117],{"type":30,"value":3118}," (needsWrap) {\n",{"type":20,"tag":279,"props":3120,"children":3122},{"class":281,"line":3121},46,[3123,3127,3132,3136,3141],{"type":20,"tag":279,"props":3124,"children":3125},{"style":302},[3126],{"type":30,"value":3047},{"type":20,"tag":279,"props":3128,"children":3129},{"style":444},[3130],{"type":30,"value":3131},"wrapAll",{"type":20,"tag":279,"props":3133,"children":3134},{"style":302},[3135],{"type":30,"value":2177},{"type":20,"tag":279,"props":3137,"children":3138},{"style":458},[3139],{"type":30,"value":3140},"'\u003Cspan class=\"hide\">\u003C/span>'",{"type":20,"tag":279,"props":3142,"children":3143},{"style":302},[3144],{"type":30,"value":2313},{"type":20,"tag":279,"props":3146,"children":3148},{"class":281,"line":3147},47,[3149,3153,3157,3161,3166,3170,3174],{"type":20,"tag":279,"props":3150,"children":3151},{"style":302},[3152],{"type":30,"value":2780},{"type":20,"tag":279,"props":3154,"children":3155},{"style":296},[3156],{"type":30,"value":337},{"type":20,"tag":279,"props":3158,"children":3159},{"style":302},[3160],{"type":30,"value":3000},{"type":20,"tag":279,"props":3162,"children":3163},{"style":444},[3164],{"type":30,"value":3165},"parent",{"type":20,"tag":279,"props":3167,"children":3168},{"style":302},[3169],{"type":30,"value":2177},{"type":20,"tag":279,"props":3171,"children":3172},{"style":458},[3173],{"type":30,"value":2904},{"type":20,"tag":279,"props":3175,"children":3176},{"style":302},[3177],{"type":30,"value":2313},{"type":20,"tag":279,"props":3179,"children":3181},{"class":281,"line":3180},48,[3182],{"type":20,"tag":279,"props":3183,"children":3184},{"style":302},[3185],{"type":30,"value":2965},{"type":20,"tag":279,"props":3187,"children":3189},{"class":281,"line":3188},49,[3190],{"type":20,"tag":279,"props":3191,"children":3192},{"emptyLinePlaceholder":286},[3193],{"type":30,"value":289},{"type":20,"tag":279,"props":3195,"children":3197},{"class":281,"line":3196},50,[3198,3203,3208,3212,3217],{"type":20,"tag":279,"props":3199,"children":3200},{"style":302},[3201],{"type":30,"value":3202},"            opts.",{"type":20,"tag":279,"props":3204,"children":3205},{"style":444},[3206],{"type":30,"value":3207},"detach",{"type":20,"tag":279,"props":3209,"children":3210},{"style":302},[3211],{"type":30,"value":2946},{"type":20,"tag":279,"props":3213,"children":3214},{"style":444},[3215],{"type":30,"value":3216},"appendTo",{"type":20,"tag":279,"props":3218,"children":3219},{"style":302},[3220],{"type":30,"value":3221},"($this);\n",{"type":20,"tag":279,"props":3223,"children":3225},{"class":281,"line":3224},51,[3226],{"type":20,"tag":279,"props":3227,"children":3228},{"style":302},[3229],{"type":30,"value":3230},"        });\n",{"type":20,"tag":279,"props":3232,"children":3234},{"class":281,"line":3233},52,[3235],{"type":20,"tag":279,"props":3236,"children":3237},{"emptyLinePlaceholder":286},[3238],{"type":30,"value":289},{"type":20,"tag":279,"props":3240,"children":3242},{"class":281,"line":3241},53,[3243,3248],{"type":20,"tag":279,"props":3244,"children":3245},{"style":296},[3246],{"type":30,"value":3247},"        return",{"type":20,"tag":279,"props":3249,"children":3250},{"style":302},[3251],{"type":30,"value":3252}," $el;\n",{"type":20,"tag":279,"props":3254,"children":3256},{"class":281,"line":3255},54,[3257],{"type":20,"tag":279,"props":3258,"children":3259},{"style":302},[3260],{"type":30,"value":3261},"    };\n",{"type":20,"tag":279,"props":3263,"children":3265},{"class":281,"line":3264},55,[3266],{"type":20,"tag":279,"props":3267,"children":3268},{"emptyLinePlaceholder":286},[3269],{"type":30,"value":289},{"type":20,"tag":279,"props":3271,"children":3273},{"class":281,"line":3272},56,[3274],{"type":20,"tag":279,"props":3275,"children":3276},{"style":320},[3277],{"type":30,"value":2321},{"type":20,"tag":279,"props":3279,"children":3281},{"class":281,"line":3280},57,[3282],{"type":20,"tag":279,"props":3283,"children":3284},{"style":320},[3285],{"type":30,"value":3286},"     * Allow for the $(element) form of invocation.\n",{"type":20,"tag":279,"props":3288,"children":3290},{"class":281,"line":3289},58,[3291,3295,3299,3303],{"type":20,"tag":279,"props":3292,"children":3293},{"style":320},[3294],{"type":30,"value":2432},{"type":20,"tag":279,"props":3296,"children":3297},{"style":296},[3298],{"type":30,"value":2470},{"type":20,"tag":279,"props":3300,"children":3301},{"style":444},[3302],{"type":30,"value":2496},{"type":20,"tag":279,"props":3304,"children":3305},{"style":302},[3306],{"type":30,"value":2501},{"type":20,"tag":279,"props":3308,"children":3310},{"class":281,"line":3309},59,[3311,3315,3319,3323],{"type":20,"tag":279,"props":3312,"children":3313},{"style":320},[3314],{"type":30,"value":2432},{"type":20,"tag":279,"props":3316,"children":3317},{"style":296},[3318],{"type":30,"value":2470},{"type":20,"tag":279,"props":3320,"children":3321},{"style":444},[3322],{"type":30,"value":2517},{"type":20,"tag":279,"props":3324,"children":3325},{"style":302},[3326],{"type":30,"value":2522},{"type":20,"tag":279,"props":3328,"children":3330},{"class":281,"line":3329},60,[3331,3335,3339],{"type":20,"tag":279,"props":3332,"children":3333},{"style":320},[3334],{"type":30,"value":2432},{"type":20,"tag":279,"props":3336,"children":3337},{"style":296},[3338],{"type":30,"value":2534},{"type":20,"tag":279,"props":3340,"children":3341},{"style":444},[3342],{"type":30,"value":2539},{"type":20,"tag":279,"props":3344,"children":3346},{"class":281,"line":3345},61,[3347],{"type":20,"tag":279,"props":3348,"children":3349},{"style":320},[3350],{"type":30,"value":2547},{"type":20,"tag":279,"props":3352,"children":3354},{"class":281,"line":3353},62,[3355,3360,3364,3368,3372,3376,3380,3384,3388],{"type":20,"tag":279,"props":3356,"children":3357},{"style":302},[3358],{"type":30,"value":3359},"    $.fn.",{"type":20,"tag":279,"props":3361,"children":3362},{"style":444},[3363],{"type":30,"value":2560},{"type":20,"tag":279,"props":3365,"children":3366},{"style":296},[3367],{"type":30,"value":2565},{"type":20,"tag":279,"props":3369,"children":3370},{"style":296},[3371],{"type":30,"value":2570},{"type":20,"tag":279,"props":3373,"children":3374},{"style":302},[3375],{"type":30,"value":2177},{"type":20,"tag":279,"props":3377,"children":3378},{"style":1035},[3379],{"type":30,"value":2588},{"type":20,"tag":279,"props":3381,"children":3382},{"style":302},[3383],{"type":30,"value":1032},{"type":20,"tag":279,"props":3385,"children":3386},{"style":1035},[3387],{"type":30,"value":2597},{"type":20,"tag":279,"props":3389,"children":3390},{"style":302},[3391],{"type":30,"value":2196},{"type":20,"tag":279,"props":3393,"children":3395},{"class":281,"line":3394},63,[3396,3400,3405,3409,3413,3417],{"type":20,"tag":279,"props":3397,"children":3398},{"style":296},[3399],{"type":30,"value":3247},{"type":20,"tag":279,"props":3401,"children":3402},{"style":302},[3403],{"type":30,"value":3404}," $.",{"type":20,"tag":279,"props":3406,"children":3407},{"style":444},[3408],{"type":30,"value":2560},{"type":20,"tag":279,"props":3410,"children":3411},{"style":302},[3412],{"type":30,"value":2177},{"type":20,"tag":279,"props":3414,"children":3415},{"style":480},[3416],{"type":30,"value":2711},{"type":20,"tag":279,"props":3418,"children":3419},{"style":302},[3420],{"type":30,"value":3421},", filter, invert);\n",{"type":20,"tag":279,"props":3423,"children":3425},{"class":281,"line":3424},64,[3426],{"type":20,"tag":279,"props":3427,"children":3428},{"style":302},[3429],{"type":30,"value":3261},{"type":20,"tag":279,"props":3431,"children":3433},{"class":281,"line":3432},65,[3434],{"type":20,"tag":279,"props":3435,"children":3436},{"style":302},[3437],{"type":30,"value":3438},"})(jQuery);\n",{"type":20,"tag":33,"props":3440,"children":3441},{},[3442],{"type":30,"value":3443},"Usage for this plugin looks like:",{"type":20,"tag":269,"props":3445,"children":3447},{"className":2163,"code":3446,"language":2165,"meta":8,"style":8},"$.elideOptions('#mydiv select', function(){ \n    /* return true to keep option in selected set */\n});\n",[3448],{"type":20,"tag":275,"props":3449,"children":3450},{"__ignoreMap":8},[3451,3485,3493],{"type":20,"tag":279,"props":3452,"children":3453},{"class":281,"line":282},[3454,3459,3463,3467,3472,3476,3480],{"type":20,"tag":279,"props":3455,"children":3456},{"style":302},[3457],{"type":30,"value":3458},"$.",{"type":20,"tag":279,"props":3460,"children":3461},{"style":444},[3462],{"type":30,"value":2560},{"type":20,"tag":279,"props":3464,"children":3465},{"style":302},[3466],{"type":30,"value":2177},{"type":20,"tag":279,"props":3468,"children":3469},{"style":458},[3470],{"type":30,"value":3471},"'#mydiv select'",{"type":20,"tag":279,"props":3473,"children":3474},{"style":302},[3475],{"type":30,"value":1032},{"type":20,"tag":279,"props":3477,"children":3478},{"style":296},[3479],{"type":30,"value":2182},{"type":20,"tag":279,"props":3481,"children":3482},{"style":302},[3483],{"type":30,"value":3484},"(){ \n",{"type":20,"tag":279,"props":3486,"children":3487},{"class":281,"line":292},[3488],{"type":20,"tag":279,"props":3489,"children":3490},{"style":320},[3491],{"type":30,"value":3492},"    /* return true to keep option in selected set */\n",{"type":20,"tag":279,"props":3494,"children":3495},{"class":281,"line":308},[3496],{"type":20,"tag":279,"props":3497,"children":3498},{"style":302},[3499],{"type":30,"value":1709},{"type":20,"tag":33,"props":3501,"children":3502},{},[3503],{"type":30,"value":3504},"OR",{"type":20,"tag":269,"props":3506,"children":3508},{"className":2163,"code":3507,"language":2165,"meta":8,"style":8},"$('#mydiv select').elideOptions(function(){ \n    /* return true to keep option in selected set */ \n});\n",[3509],{"type":20,"tag":275,"props":3510,"children":3511},{"__ignoreMap":8},[3512,3547,3560],{"type":20,"tag":279,"props":3513,"children":3514},{"class":281,"line":282},[3515,3519,3523,3527,3531,3535,3539,3543],{"type":20,"tag":279,"props":3516,"children":3517},{"style":444},[3518],{"type":30,"value":2191},{"type":20,"tag":279,"props":3520,"children":3521},{"style":302},[3522],{"type":30,"value":2177},{"type":20,"tag":279,"props":3524,"children":3525},{"style":458},[3526],{"type":30,"value":3471},{"type":20,"tag":279,"props":3528,"children":3529},{"style":302},[3530],{"type":30,"value":2808},{"type":20,"tag":279,"props":3532,"children":3533},{"style":444},[3534],{"type":30,"value":2560},{"type":20,"tag":279,"props":3536,"children":3537},{"style":302},[3538],{"type":30,"value":2177},{"type":20,"tag":279,"props":3540,"children":3541},{"style":296},[3542],{"type":30,"value":2182},{"type":20,"tag":279,"props":3544,"children":3545},{"style":302},[3546],{"type":30,"value":3484},{"type":20,"tag":279,"props":3548,"children":3549},{"class":281,"line":292},[3550,3555],{"type":20,"tag":279,"props":3551,"children":3552},{"style":320},[3553],{"type":30,"value":3554},"    /* return true to keep option in selected set */",{"type":20,"tag":279,"props":3556,"children":3557},{"style":302},[3558],{"type":30,"value":3559}," \n",{"type":20,"tag":279,"props":3561,"children":3562},{"class":281,"line":308},[3563],{"type":20,"tag":279,"props":3564,"children":3565},{"style":302},[3566],{"type":30,"value":1709},{"type":20,"tag":33,"props":3568,"children":3569},{},[3570,3572,3577,3579,3584,3586,3591],{"type":30,"value":3571},"Additionally, you can specify a third optional parameter, ",{"type":20,"tag":275,"props":3573,"children":3575},{"className":3574},[],[3576],{"type":30,"value":2597},{"type":30,"value":3578}," - if true, the logic of the filter function is inverted (that is, options that match the criteria [ie. you return true from the filter function] will be omitted from the options that will be acted upon - essentially, using ",{"type":20,"tag":275,"props":3580,"children":3582},{"className":3581},[],[3583],{"type":30,"value":1435},{"type":30,"value":3585}," instead of ",{"type":20,"tag":275,"props":3587,"children":3589},{"className":3588},[],[3590],{"type":30,"value":2588},{"type":30,"value":3592}," on the set).",{"type":20,"tag":33,"props":3594,"children":3595},{},[3596,3598,3605],{"type":30,"value":3597},"As per the comments at the top of the above gist, we work around our issue with Chrome by performing the disable as per usual, then ",{"type":20,"tag":109,"props":3599,"children":3602},{"href":3600,"rel":3601},"https://api.jquery.com/detach/",[113],[3603],{"type":30,"value":3604},"detaching",{"type":30,"value":3606}," the disabled options from the select, and appending them back onto the end of the select.",{"type":20,"tag":33,"props":3608,"children":3609},{},[3610],{"type":30,"value":3611},"The behaviour of this bug is such that any visible options that occur after hidden elements aren't counted towards the height of the list box - by ensuring all visible options will be above any hidden options, we avoid this behaviour. Now we can still filter on the elements within the select, not needing to create a shadow element or object to store state for this select, while still having the select dropdown expand to the dimensions of the visible options as expected.",{"type":20,"tag":33,"props":3613,"children":3614},{},[3615,3617,3624],{"type":30,"value":3616},"So, we know the how - but are you curious as to the why? The answer lurks somewhere in ",{"type":20,"tag":109,"props":3618,"children":3621},{"href":3619,"rel":3620},"https://codereview.chromium.org/189543012/diff/200001/Source/core/rendering/RenderListBox.cpp",[113],[3622],{"type":30,"value":3623},"this diff",{"type":30,"value":3625},". I've only had time to give it a cursory read-through - there's not a lot to go on, given the lack of documentation (come on guys, a doc-block per function wouldn't be that hard), but given the bug behaviour, it probably has something to do with either the calculation of the list box height only including those items encountered before the first hidden item, or with the lastIndex being calculated as the last visible element before the first hidden element.",{"type":20,"tag":33,"props":3627,"children":3628},{},[3629],{"type":30,"value":3630},"Need to get the job done today? My plugin can help with that. Want to be a hero? Go fix this for realsies.",{"type":20,"tag":1200,"props":3632,"children":3633},{},[3634],{"type":30,"value":1204},{"title":8,"searchDepth":308,"depth":308,"links":3636},[],"content:ckeefer:2014-4:hidden-options.md","ckeefer/2014-4/hidden-options.md","ckeefer/2014-4/hidden-options",{"user":3641,"name":3642},"ckeefer","Christopher Keefer",1780330267753]