\n \u003Cinput type=\"text\" name=\"superpower\" />\n \u003Cbutton type=\"submit\" is=\"ajax-submit\" data-target=\"hero-container\">\n add new item\n \u003C/button>\n\u003C/form>\n\n\u003Cdiv id=\"hero-container\">\u003C/div>\n","html",[147,240,241,275,303,325,357,363,373,383,390],{"__ignoreMap":141},[242,243,246,250,253,257,260,264,267,269,272],"span",{"class":244,"line":245},"line",1,[242,247,249],{"class":248},"sVt8B","\u003C",[242,251,149],{"class":252},"s9eBZ",[242,254,256],{"class":255},"sScJk"," action",[242,258,259],{"class":248},"=",[242,261,263],{"class":262},"sZZnC","\"/heroes\"",[242,265,266],{"class":255}," method",[242,268,259],{"class":248},[242,270,271],{"class":262},"\"post\"",[242,273,274],{"class":248},">\n",[242,276,278,281,284,287,289,292,295,297,300],{"class":244,"line":277},2,[242,279,280],{"class":248}," \u003C",[242,282,283],{"class":252},"input",[242,285,286],{"class":255}," type",[242,288,259],{"class":248},[242,290,291],{"class":262},"\"text\"",[242,293,294],{"class":255}," name",[242,296,259],{"class":248},[242,298,299],{"class":262},"\"hero\"",[242,301,302],{"class":248}," />\n",[242,304,306,308,310,312,314,316,318,320,323],{"class":244,"line":305},3,[242,307,280],{"class":248},[242,309,283],{"class":252},[242,311,286],{"class":255},[242,313,259],{"class":248},[242,315,291],{"class":262},[242,317,294],{"class":255},[242,319,259],{"class":248},[242,321,322],{"class":262},"\"superpower\"",[242,324,302],{"class":248},[242,326,328,330,332,334,336,339,342,344,347,350,352,355],{"class":244,"line":327},4,[242,329,280],{"class":248},[242,331,227],{"class":252},[242,333,286],{"class":255},[242,335,259],{"class":248},[242,337,338],{"class":262},"\"submit\"",[242,340,341],{"class":255}," is",[242,343,259],{"class":248},[242,345,346],{"class":262},"\"ajax-submit\"",[242,348,349],{"class":255}," data-target",[242,351,259],{"class":248},[242,353,354],{"class":262},"\"hero-container\"",[242,356,274],{"class":248},[242,358,360],{"class":244,"line":359},5,[242,361,362],{"class":248}," add new item\n",[242,364,366,369,371],{"class":244,"line":365},6,[242,367,368],{"class":248}," \u003C/",[242,370,227],{"class":252},[242,372,274],{"class":248},[242,374,376,379,381],{"class":244,"line":375},7,[242,377,378],{"class":248},"\u003C/",[242,380,149],{"class":252},[242,382,274],{"class":248},[242,384,386],{"class":244,"line":385},8,[242,387,389],{"emptyLinePlaceholder":388},true,"\n",[242,391,393,395,397,400,402,404,407,409],{"class":244,"line":392},9,[242,394,249],{"class":248},[242,396,223],{"class":252},[242,398,399],{"class":255}," id",[242,401,259],{"class":248},[242,403,354],{"class":262},[242,405,406],{"class":248},">\u003C/",[242,408,223],{"class":252},[242,410,274],{"class":248},[234,412,416],{"className":413,"code":414,"language":415,"meta":141,"style":141},"language-javascript shiki shiki-themes github-light github-dark","class AjaxSubmitButton extends HTMLButtonElement {\n connectedCallback() {\n this.addEventListener(\"click\", async (event) => {\n event.preventDefault();\n let html = await ajaxFormSubmit();\n document.getElementById(this.dataset.target).innerHTML = html;\n });\n }\n}\n\ncustomElements.define(\"ajax-submit\", AjaxSubmitButton, {\n extends: \"button\",\n});\n","javascript",[147,417,418,436,444,483,494,512,533,538,543,548,553,569,581],{"__ignoreMap":141},[242,419,420,424,427,430,433],{"class":244,"line":245},[242,421,423],{"class":422},"szBVR","class",[242,425,426],{"class":255}," AjaxSubmitButton",[242,428,429],{"class":422}," extends",[242,431,432],{"class":255}," HTMLButtonElement",[242,434,435],{"class":248}," {\n",[242,437,438,441],{"class":244,"line":277},[242,439,440],{"class":255}," connectedCallback",[242,442,443],{"class":248},"() {\n",[242,445,446,450,453,456,459,462,465,468,471,475,478,481],{"class":244,"line":305},[242,447,449],{"class":448},"sj4cs"," this",[242,451,452],{"class":248},".",[242,454,455],{"class":255},"addEventListener",[242,457,458],{"class":248},"(",[242,460,461],{"class":262},"\"click\"",[242,463,464],{"class":248},", ",[242,466,467],{"class":422},"async",[242,469,470],{"class":248}," (",[242,472,474],{"class":473},"s4XuR","event",[242,476,477],{"class":248},") ",[242,479,480],{"class":422},"=>",[242,482,435],{"class":248},[242,484,485,488,491],{"class":244,"line":327},[242,486,487],{"class":248}," event.",[242,489,490],{"class":255},"preventDefault",[242,492,493],{"class":248},"();\n",[242,495,496,499,502,504,507,510],{"class":244,"line":359},[242,497,498],{"class":422}," let",[242,500,501],{"class":248}," html ",[242,503,259],{"class":422},[242,505,506],{"class":422}," await",[242,508,509],{"class":255}," ajaxFormSubmit",[242,511,493],{"class":248},[242,513,514,517,520,522,525,528,530],{"class":244,"line":365},[242,515,516],{"class":248}," document.",[242,518,519],{"class":255},"getElementById",[242,521,458],{"class":248},[242,523,524],{"class":448},"this",[242,526,527],{"class":248},".dataset.target).innerHTML ",[242,529,259],{"class":422},[242,531,532],{"class":248}," html;\n",[242,534,535],{"class":244,"line":375},[242,536,537],{"class":248}," });\n",[242,539,540],{"class":244,"line":385},[242,541,542],{"class":248}," }\n",[242,544,545],{"class":244,"line":392},[242,546,547],{"class":248},"}\n",[242,549,551],{"class":244,"line":550},10,[242,552,389],{"emptyLinePlaceholder":388},[242,554,556,559,562,564,566],{"class":244,"line":555},11,[242,557,558],{"class":248},"customElements.",[242,560,561],{"class":255},"define",[242,563,458],{"class":248},[242,565,346],{"class":262},[242,567,568],{"class":248},", AjaxSubmitButton, {\n",[242,570,572,575,578],{"class":244,"line":571},12,[242,573,574],{"class":248}," extends: ",[242,576,577],{"class":262},"\"button\"",[242,579,580],{"class":248},",\n",[242,582,584],{"class":244,"line":583},13,[242,585,586],{"class":248},"});\n",[18,588,589,590,593],{},"Der Vorteil des Custom-Elements gegenüber jQuery (oder sonstiger eigener Implementierung) ist, dass der Browser sich\ndarum kümmert, unser JavaScript mit dem HTML zu verbinden und eine Instanz des ",[147,591,592],{},"AjaxSubmitButtons"," zu erstellen.",[18,595,596,597,600,601,606],{},"Kommen zur Laufzeit weitere ",[147,598,599],{},"submit"," Buttons mit diesem Attribut hinzu, werden sie vom Browser automatisch mit unserem\ngewünschten Verhalten erweitert. Ist das JavaScript aus irgendwelchen Gründen nicht verfügbar funktioniert weiterhin das\ngute alte HTML Formular mit komplettem Seitenreload. Wir verbessern unsere Anwendung mit jedem weiteren\nTechnologie-Layer, der zur Verfügung\nsteht, ",[24,602,605],{"href":603,"rel":604},"https://developer.mozilla.org/de/docs/Glossary/Progressive_Enhancement",[28],"Progressive Enhancement"," genannt. HTML\nbeschreibt den Inhalt, CSS macht es bunt, und zu guter Letzt verbessern wir die Benutzererfahrung mit JavaScript.",[18,608,609],{},"Das Backend wird mit diesem Ansatz auch nicht komplizierter oder gar komplexer. Im Controller müssen wir erkennen, dass\nes sich um einen Ajax Request handelt. In dem Fall wird ein HTML Fragment gerendert, andernfalls wird die komplette\nSeite gerendert:",[234,611,615],{"className":612,"code":613,"language":614,"meta":141,"style":141},"language-java shiki shiki-themes github-light github-dark","@PostMapping(value = \"/heroes\")\npublic String addSuperhero(\n @RequestParam String hero,\n @RequestParam String superpower,\n @RequestHeader(name = \"X-Requested-With\", defaultValue = \"\") String requestedWith,\n Model model\n ) {\n\n model.addAttribute(\"hero\", hero);\n model.addAttribute(\"superpower\", superpower);\n\n if (\"ajax\".equals(requestedWith)) {\n return \"fragments/hero-fragment :: hero-fragment\";\n }\n\n return \"full-page-including-the-hero-fragment\";\n}\n\n","java",[147,616,617,622,627,632,637,642,647,652,656,661,666,670,675,680,686,691,697],{"__ignoreMap":141},[242,618,619],{"class":244,"line":245},[242,620,621],{},"@PostMapping(value = \"/heroes\")\n",[242,623,624],{"class":244,"line":277},[242,625,626],{},"public String addSuperhero(\n",[242,628,629],{"class":244,"line":305},[242,630,631],{}," @RequestParam String hero,\n",[242,633,634],{"class":244,"line":327},[242,635,636],{}," @RequestParam String superpower,\n",[242,638,639],{"class":244,"line":359},[242,640,641],{}," @RequestHeader(name = \"X-Requested-With\", defaultValue = \"\") String requestedWith,\n",[242,643,644],{"class":244,"line":365},[242,645,646],{}," Model model\n",[242,648,649],{"class":244,"line":375},[242,650,651],{}," ) {\n",[242,653,654],{"class":244,"line":385},[242,655,389],{"emptyLinePlaceholder":388},[242,657,658],{"class":244,"line":392},[242,659,660],{}," model.addAttribute(\"hero\", hero);\n",[242,662,663],{"class":244,"line":550},[242,664,665],{}," model.addAttribute(\"superpower\", superpower);\n",[242,667,668],{"class":244,"line":555},[242,669,389],{"emptyLinePlaceholder":388},[242,671,672],{"class":244,"line":571},[242,673,674],{}," if (\"ajax\".equals(requestedWith)) {\n",[242,676,677],{"class":244,"line":583},[242,678,679],{}," return \"fragments/hero-fragment :: hero-fragment\";\n",[242,681,683],{"class":244,"line":682},14,[242,684,685],{}," }\n",[242,687,689],{"class":244,"line":688},15,[242,690,389],{"emptyLinePlaceholder":388},[242,692,694],{"class":244,"line":693},16,[242,695,696],{}," return \"full-page-including-the-hero-fragment\";\n",[242,698,700],{"class":244,"line":699},17,[242,701,547],{},[74,703,705],{"id":704},"auf-dem-weg-zur-single-page-application","Auf dem Weg zur Single Page Application",[18,707,708],{},"Mittlerweile haben wir die ajax-submit Komponente in unserer Anwendung an sehr vielen Stellen im Einsatz. Bisher hat\nsie sich als robust und einfach bewährt. Jeder im Team hat verstanden wie man es einsetzt und wie es funktioniert. Wir\nbrauchen keine Runtime a la ReactJS, wir müssen Hooks nicht verstehen, wir benötigen kein komplexes Build Setup. Wir\nmüssen nicht überlegen wie wir vue Komponenten integrieren und wir müssen uns keine Gedanken bzgl Client Side only\nFunktionalität machen.",[18,710,711],{},"Kurz gesagt: Wir bauen unser Frontend ohne modernes JavaScript Framework und sind (trotzdem) glücklich.",[18,713,714],{},"Zugegeben, die Progressive Enhancement Denkweise ist herausfordernd. Daran zu denken und zu überlegen, wie Anforderungen\nohne JavaScript gelöst werden können, hat unsere Industrie vielleicht verlernt? In der Vergangenheit haben mich\nKollegen, die überwiegend im Backend unterwegs sind gefragt, wie man denn heutzutage ein Frontend baut. Nehme man da\nAngular oder React? Über Anforderungen war man sich da aber noch nicht bewusst… Es war zumindest nicht die Einleitung\nzur Technologie Frage.",[716,717,719],"h3",{"id":718},"herausforderungen-die-kommen-könnten","Herausforderungen die kommen (könnten)",[82,721,722],{},[85,723,724],{},[211,725,726],{},"Progressive Enhancement Denkweise",[18,728,729],{},"Sobald unsere Anwendung einen gewissen Charakter moderner Single Page Applications erreicht hat, wird das natürlich der\nDefault werden (im Geiste). Hier gilt es weiterhin an die Basis zu denken und nicht sofort an JavaScript only Lösungen.",[82,731,732],{},[85,733,734],{},[211,735,736],{},"History Handling",[18,738,739,740,745],{},"Vor und Zurück nach bestimmten Aktionen ist bisher keine Anforderung. Sollte diese Anforderung kommen ist das aber auch\nkein Hexenwerk. Nach JavaScript Aktionen die URL mit\nder ",[24,741,744],{"href":742,"rel":743},"https://developer.mozilla.org/en-US/docs/Web/API/History",[28],"history API"," ändern ist im Bereich des Möglichen 😉",[82,747,748],{},[85,749,750],{},[211,751,752],{},"State",[18,754,755,756,761],{},"Nach bestimmten Aktionen den State an mehreren Stellen im Browser aktualisieren. Hier muss denke ich abewägt werden, ob\nBibiliotheken eine Daseinsberechtigung bekommen, oder\nob ",[24,757,760],{"href":758,"rel":759},"https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent",[28],"Custom Events"," ausreichen.",[763,764,765],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":141,"searchDepth":277,"depth":277,"links":767},[768,769,770,771],{"id":76,"depth":277,"text":77},{"id":123,"depth":277,"text":124},{"id":199,"depth":277,"text":190},{"id":704,"depth":277,"text":705,"children":772},[773],{"id":718,"depth":305,"text":719},[775,776],"developer-blog","synyx-blog","2020-06-23T11:20:03","Mit einigen Jahren JavaScript und Reactjs Erfahrung durfte ich Ende letzten Jahres (November 2019) Teil eines neuen\\nTeams und eines neuen Projektes werden. Das Projekt ist ein Traumprojekt jeden Entwicklers. Ein grüne Wiese Projekt mit\\n“freier” Technologiewahl. “Frei” in Form von man darf sich die Zeit für eine Risikoanalyse nehmen und moderne Tools und\\nFrameworks evaluieren.","md","https://synyx.de/blog/frameworkless-frontend-und-trotzdem-gluecklich/",{},"/blog/frameworkless-frontend-und-trotzdem-gluecklich",{"title":7,"description":20},"frameworkless-frontend-und-trotzdem-gluecklich","blog/frameworkless-frontend-und-trotzdem-gluecklich",[787,415,788],"development","progressive-enhancement","Mit einigen Jahren JavaScript und Reactjs Erfahrung durfte ich Ende letzten Jahres (November 2019) Teil eines neuen Teams und eines neuen Projektes werden. Das Projekt ist ein Traumprojekt jeden Entwicklers. Ein grüne Wiese Projekt mit 'freier' Technologiewahl. 'Frei' in Form von man darf sich die Zeit für eine Risikoanalyse nehmen und moderne Tools und Frameworks evaluieren.","JGt7bZJ_gokf64kVf7XrDHMaEJ2JdAYkb3HTHT7LZEw",{"id":792,"title":793,"author":794,"body":796,"category":854,"date":855,"description":803,"extension":779,"link":856,"meta":857,"navigation":388,"path":858,"seo":859,"slug":860,"stem":861,"tags":862,"teaser":867,"__hash__":868},"blog/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen.md","Experiment JavaScript – Ein synyx Entwickler erzählt von seinen Anfängen",[795],"lange",{"type":11,"value":797,"toc":849},[798,801,804,808,815,818,821,824,828,831,834,838,841],[14,799,793],{"id":800},"experiment-javascript-ein-synyx-entwickler-erzählt-von-seinen-anfängen",[18,802,803],{},"Alles ging vor knapp drei Jahren mit einer ganz harmlosen Frage los:",[74,805,807],{"id":806},"kannst-du-dir-vorstellen-in-einem-javascript-projekt-zu-arbeiten","„Kannst Du Dir vorstellen, in einem JavaScript-Projekt zu arbeiten?“",[18,809,810,811,814],{},"Das war der Abschluss meines Bewerbungsgesprächs bei synyx. Lässt man die üblichen Website-Spielereien außer Acht, hatte ich zu diesem Zeitpunkt noch keine wirklichen Erfahrungen mit JavaScript vorzuweisen. Damals hätte ich es vermutlich nicht einmal als ",[89,812,813],{},"richtige"," Programmiersprache bezeichnet. Doch mein Interesse war geweckt und ich ließ mich auf das Experiment JavaScript ein.",[18,816,817],{},"Dann ging der für mich anfangs beschwerliche Weg los. Ich komme aus der klassischen Java-Welt und JavaScript ist doch ein bisschen anders als Java. Typsicherheit und einen Compiler habe ich vor allem anfangs sehr vermisst, aber mich auch über das direkte Ausführen von kleinen Fragmenten gefreut. JavaScript lässt sich einfach interaktiver / schneller entwickeln. Das macht Spaß, wenn man sich darauf einlässt. Die Sprache gibt einem viele Freiheiten, dynamisches Überschreiben von Methoden zum Beispiel.",[18,819,820],{},"Ach ja, Methoden – ein gutes Thema. Neben der Single-Threaded-Natur ist die prototypische Vererbung doch recht gewöhnungsbedürftig. Aber man sollte ja sowieso eher delegieren als vererben 😉 Und durch das Ducktyping sind Vererbungen auch gar nicht so wichtig. Die Sprache hatte für mich definitiv viele Eigenheiten, die ich mir erst nach und nach erarbeiten musste. Toll war, dass ich damit nicht allein im Team war.",[18,822,823],{},"Oh ja, das Team. Es ist klasse, wie offen wir über Probleme sprechen und gemeinsam nach Lösungen suchen können. Die verschiedenen Charaktere helfen dabei, zu guten Ergebnissen zu gelangen.",[74,825,827],{"id":826},"eine-interessante-sprache-und-ein-tolles-team-aber-was-machen-wir-da-eigentlich","Eine interessante Sprache und ein tolles Team, aber was machen wir da eigentlich?",[18,829,830],{},"Natürlich einem großen Kunden gute Software abliefern. Wobei so natürlich ist es eigentlich gar nicht. Wir bearbeiten zur Zeit zwei Projekte, die ihre Ausführungsumgebung gewechselt haben und parallel weiterentwickelt werden sollten. Von einem Headless-Browser in eine Node-Umgebung zu wechseln, klingt nach einem natürlichen Schritt. Allerdings brachte das viele kleinere und größere Probleme mit sich, die nur durch die intensive Zusammenarbeit mit dem Kunden, der die Ausführungsumgebung bereitstellt, zu bewältigen waren.",[18,832,833],{},"Erschwerend kam hinzu, dass nur eine Lösung komplett bei synyx erstellt wurde. Bei der anderen unterstützen wir einen Dienstleister, der an dem Programm seit rund sechs Jahren arbeitet. Gemeinsam machen wir die Anwendung fit für die Zukunft. Musik-Streaming ins Auto, ein interessantes, wenn auch stellenweise stressiges Projekt. Vor sechs Jahren wurde JavaScript doch noch ganz anders geschrieben als heute.",[74,835,837],{"id":836},"bock-mitzumachen","Bock mitzumachen?",[18,839,840],{},"Wenn Dich JavaScript als allgegenwärtige Sprache interessiert, Du Deine Erfahrungen einbringen willst oder einfach mal schauen möchtest, wie es ist, mit einem großen Kunden zusammenzuarbeiten, der versucht Probleme prozessorientiert zu lösen, komm doch mal bei uns vorbei und wir schauen, ob es für Dich und uns passt.",[18,842,843,844,848],{},"Melde Dich einfach bei uns unter ",[24,845,847],{"href":846},"mailto:jobs@synyx.de","jobs@synyx.de"," und erzähl uns, wer Du bist und was Du so kannst. Zeugnisse sind uns nicht so wichtig. Statt Deine Grundschulnoten zu analysieren, wollen wir lieber mehr über Deine Interessen und Fähigkeiten erfahren. Wir freuen uns, von Dir zu hören!",{"title":141,"searchDepth":277,"depth":277,"links":850},[851,852,853],{"id":806,"depth":277,"text":807},{"id":826,"depth":277,"text":827},{"id":836,"depth":277,"text":837},[776],"2019-05-28T16:26:03","https://synyx.de/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen/",{},"/blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen",{"title":793,"description":803},"experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen","blog/experiment-javascript-ein-synyx-entwickler-erzaehlt-von-seinen-anfaengen",[863,864,865,415,866],"arbeitsalltag","developer","entwickler","jobs","Werdegang des JavaScript Developers Christian Lange bei synyx.","AqRd7fJ4-roSV9bB721fTkY674GmJh6n6gpoi-HeEUI",{"id":870,"title":871,"author":872,"body":873,"category":2357,"date":2358,"description":2359,"extension":779,"link":2360,"meta":2361,"navigation":388,"path":2362,"seo":2363,"slug":877,"stem":2365,"tags":2366,"teaser":2369,"__hash__":2370},"blog/blog/javascript-code-refactoring-automatisieren.md","JavaScript Code Refactoring automatisieren",[9],{"type":11,"value":874,"toc":2348},[875,878,886,906,912,916,925,932,935,951,955,958,963,971,976,979,984,987,990,998,1001,1005,1008,1025,1028,1032,1035,1046,1143,1146,1173,1176,1231,1234,1237,1268,1271,1274,1373,1415,1418,1432,1435,1460,1466,1512,1524,1627,1647,1659,1666,1718,1721,1735,1738,1752,1755,1758,1761,1781,1791,1925,1933,1939,2068,2071,2093,2099,2200,2203,2206,2218,2221,2235,2238,2242,2251,2254,2271,2315,2319,2322,2337,2342,2345],[14,876,871],{"id":877},"javascript-code-refactoring-automatisieren",[18,879,880,881,885],{},"Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\nArt von asynchronen Specs und einmal die verwendeten Expectations.\nUnter ",[24,882,883],{"href":883,"rel":884},"http://jasmine.github.io/2.0/upgrading.html",[28]," wurde super\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.",[18,887,888,889,36,892,895,896,899,900,905],{},"In diesem Artikel möchte ich zeigen, wie ich die Transformation der ",[147,890,891],{},"runs",[147,893,894],{},"waitsFor"," Blöcke zum neuen ",[147,897,898],{},"done","\nCallback Muster mittels ",[24,901,904],{"href":902,"rel":903},"https://github.com/facebook/jscodeshift",[28],"jscodeshift"," automatisiert habe.",[18,907,908],{},[139,909],{"alt":910,"src":911},"jasmine_async_vergleich","https://media.synyx.de/uploads//2016/08/jasmine_async_vergleich-1024x469.png",[74,913,915],{"id":914},"jscodeshift-recast-esprima-codemods","jscodeshift / recast / esprima / codemods",[18,917,918,919,924],{},"Jscodeshift ist ein Werkzeug dass von Facebook gebaut wurde und ",[24,920,923],{"href":921,"rel":922},"https://github.com/benjamn/recast",[28],"recast"," erweitert.\nDieses wiederum arbeitet mit dem Esprima Parser. Dieser baut einen abstrakten Syntaxbaum (engl. abstract source tree,\nAST) auf, der traversiert werden kann.",[18,926,927,928,931],{},"Mit jscodeshift ist es z. B. möglich, alle anonyme Funktionen herauszuholen und mit einem Namen ",[89,929,930],{},"ichBinNichtMehrAnonym","\nzu ersetzen. Ein weiteres, nettes Feature wie ich finde, ist die Beibehaltung der originalen Code Formatierung (so weit\nmöglich).",[18,933,934],{},"Man baut also ein Codeschnipsel welches anderen Code umschreibt. Dieses Codeschnipsel wird codemod genannt. Eine\nschnelle Google Suche bringt uns zu zwei interessanten Artikeln; die für das Folgende aber nicht unbedingt gelesen\nwerden müssen 🙂",[82,936,937,944],{},[85,938,939],{},[24,940,943],{"href":941,"rel":942},"https://vramana.github.io/blog/2015/12/21/codemod-tutorial/",[28],"How to write a codemod",[85,945,946],{},[24,947,950],{"href":948,"rel":949},"https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb",[28],"Effective JavaScript Codemods",[74,952,954],{"id":953},"automatisieren-von-code-refactorings","Automatisieren von Code Refactorings",[18,956,957],{},"Warum automatisieren mag sich der ein oder andere denken. Das kann mehrere Gründe haben. Zum einen hat man vielleicht\nkeinen Auszubildenden zur Verfügung der die Tests umschreiben kann, zum anderen… Nein… Auszubildende bitte mit\njscodeshift ersetzen. Also für die Arbeit des Refactorings… Aber zurück zu den Gründen der Automatisierung.",[18,959,960],{},[211,961,962],{},"Spaß",[18,964,965,966,970],{},"Ich musste kurz überlegen wann ich ein Refactoring in JavaScript Projekten gemacht habe, bei dem ich auch Spaß hatte.\nMir fiel keines ein. Die Arbeit ",[967,968,969],"del",{},"war"," ist stupide: Suchen und Ersetzen. Für das automatisierte Code Refactoring wird\nein Code Schnipsel geschrieben welches das Suchen und Ersetzen für mich übernimmt! Ich darf Code hacken!",[18,972,973],{},[211,974,975],{},"Zuverlässigkeit",[18,977,978],{},"Ein generelles Argument zum Automatisieren trifft denke ich auch bei Code Refactoring zu. Es wird immer zu jeder Zeit an\njeder Stelle das selbe gemacht. Es gibt keinen Finger der auf der Tastatur um eine Taste verrutscht. Es gibt keine\nUnachtsamkeit die zum Vergessen einer Stelle führt. Die Maschine erledigt zuverlässig was getan werden soll. Immer.\nJederzeit. Überall.",[18,980,981],{},[211,982,983],{},"Effektivität",[18,985,986],{},"Ist etwas automatisiert gilt es nur noch ein Knöpfchen zu drücken. Bezogen auf das Code Refactoring kann das Ergebnis in\nwenigen (Milli-)Sekunden bestaunt werden. Hier spreche ich aber noch nicht konkret von codemods und jscodeshift als\nWerkzeug. Eine Regex kann hier auch schon völlig ausreichen.",[18,988,989],{},"Eine Regex zum",[82,991,992,995],{},[85,993,994],{},"löschen von Abschnitten wenn Bedingung A zutrifft",[85,996,997],{},"verschieben von Code Blöcken",[18,999,1000],{},"kann entweder einmal geschrieben und nie wieder verstanden werden, oder ist gar unmöglich zu schreiben. Hier kommt\njscodeshift mit codemods zur Rettung.",[74,1002,1004],{"id":1003},"codemods-zum-upgrade-von-jasmine-1x-auf-2x","Codemods zum Upgrade von Jasmine 1.x auf 2.x",[18,1006,1007],{},"Eine codemod ist ein Codeschnipsel welches vorhandenen Quellcode transformiert. Im unserem Fall der Jasmine Migrierung\nvon Version 1.x zu 2.x müssen transformiert werden:",[82,1009,1010,1013,1016,1019,1022],{},[85,1011,1012],{},"spies",[85,1014,1015],{},"asynchrone Tests",[85,1017,1018],{},"expectations",[85,1020,1021],{},"custom matchers (falls vorhanden)",[85,1023,1024],{},"clock",[18,1026,1027],{},"Wir wenden uns folgend den asynchronen Tests zu.",[716,1029,1031],{"id":1030},"asynchrone-tests-transformieren","Asynchrone Tests transformieren",[18,1033,1034],{},"Das Projekt das es zu refactored galt hatte überwiegend sehr einfach geschriebene asynchrone Tests. Perfekt für den\nEinstieg in jscodeshift.",[18,1036,1037,1038,1041,1042,1045],{},"Eine Variable die initial ",[147,1039,1040],{},"false"," ist und nach $Aktion auf ",[147,1043,1044],{},"true"," gesetzt wird. Die Assertions werden dann erst\nausgeführt, wenn die Variable gesetzt wurde.",[234,1047,1051],{"className":1048,"code":1049,"language":1050,"meta":141,"style":141},"language-js shiki shiki-themes github-light github-dark","it(\"tests something async\", function () {\n var done;\n doSomethingAsync(function callback() {\n // assertions\n done = true;\n });\n waitsFor(function () {\n return done;\n });\n});\n","js",[147,1052,1053,1071,1079,1093,1099,1112,1117,1128,1135,1139],{"__ignoreMap":141},[242,1054,1055,1058,1060,1063,1065,1068],{"class":244,"line":245},[242,1056,1057],{"class":255},"it",[242,1059,458],{"class":248},[242,1061,1062],{"class":262},"\"tests something async\"",[242,1064,464],{"class":248},[242,1066,1067],{"class":422},"function",[242,1069,1070],{"class":248}," () {\n",[242,1072,1073,1076],{"class":244,"line":277},[242,1074,1075],{"class":422}," var",[242,1077,1078],{"class":248}," done;\n",[242,1080,1081,1084,1086,1088,1091],{"class":244,"line":305},[242,1082,1083],{"class":255}," doSomethingAsync",[242,1085,458],{"class":248},[242,1087,1067],{"class":422},[242,1089,1090],{"class":255}," callback",[242,1092,443],{"class":248},[242,1094,1095],{"class":244,"line":327},[242,1096,1098],{"class":1097},"sJ8bj"," // assertions\n",[242,1100,1101,1104,1106,1109],{"class":244,"line":359},[242,1102,1103],{"class":248}," done ",[242,1105,259],{"class":422},[242,1107,1108],{"class":448}," true",[242,1110,1111],{"class":248},";\n",[242,1113,1114],{"class":244,"line":365},[242,1115,1116],{"class":248}," });\n",[242,1118,1119,1122,1124,1126],{"class":244,"line":375},[242,1120,1121],{"class":255}," waitsFor",[242,1123,458],{"class":248},[242,1125,1067],{"class":422},[242,1127,1070],{"class":248},[242,1129,1130,1133],{"class":244,"line":385},[242,1131,1132],{"class":422}," return",[242,1134,1078],{"class":248},[242,1136,1137],{"class":244,"line":392},[242,1138,1116],{"class":248},[242,1140,1141],{"class":244,"line":550},[242,1142,586],{"class":248},[18,1144,1145],{},"Für jasmine 2.x müssen wir also",[82,1147,1148,1157,1167],{},[85,1149,1150,1151,1153,1154,1156],{},"einmal der Funktion die dem ",[147,1152,1057],{}," übergeben wird einen Parameter ",[147,1155,898],{}," hinzufügen",[85,1158,1159,1162,1163,1166],{},[147,1160,1161],{},"done = true;"," mit ",[147,1164,1165],{},"done();"," ersetzen",[85,1168,1169,1170,1172],{},"und den ",[147,1171,894],{}," Block löschen",[18,1174,1175],{},"Nach dem Refactoring soll das Ganze also wie folgt aussehen:",[234,1177,1179],{"className":1048,"code":1178,"language":1050,"meta":141,"style":141},"it(\"tests something async\", function (done) {\n doSomethingAsync(function callback() {\n // assertions\n done();\n });\n});\n",[147,1180,1181,1200,1212,1216,1223,1227],{"__ignoreMap":141},[242,1182,1183,1185,1187,1189,1191,1193,1195,1197],{"class":244,"line":245},[242,1184,1057],{"class":255},[242,1186,458],{"class":248},[242,1188,1062],{"class":262},[242,1190,464],{"class":248},[242,1192,1067],{"class":422},[242,1194,470],{"class":248},[242,1196,898],{"class":473},[242,1198,1199],{"class":248},") {\n",[242,1201,1202,1204,1206,1208,1210],{"class":244,"line":277},[242,1203,1083],{"class":255},[242,1205,458],{"class":248},[242,1207,1067],{"class":422},[242,1209,1090],{"class":255},[242,1211,443],{"class":248},[242,1213,1214],{"class":244,"line":305},[242,1215,1098],{"class":1097},[242,1217,1218,1221],{"class":244,"line":327},[242,1219,1220],{"class":255}," done",[242,1222,493],{"class":248},[242,1224,1225],{"class":244,"line":359},[242,1226,1116],{"class":248},[242,1228,1229],{"class":244,"line":365},[242,1230,586],{"class":248},[1232,1233,904],"h4",{"id":904},[18,1235,1236],{},"Bevor wir loslegen können, müssen noch wenige Dinge erledigt werden.",[234,1238,1242],{"className":1239,"code":1240,"language":1241,"meta":141,"style":141},"language-plaintext shiki shiki-themes github-light github-dark","\n$> npm install -g jscodeshift\n$> mkdir jasmineCodemods && cd jasmineCodemods\n$> git init && git commit -m \"initial commit\" --allow-empty\n$> touch jasmine-async.js\n\n","plaintext",[147,1243,1244,1248,1253,1258,1263],{"__ignoreMap":141},[242,1245,1246],{"class":244,"line":245},[242,1247,389],{"emptyLinePlaceholder":388},[242,1249,1250],{"class":244,"line":277},[242,1251,1252],{},"$> npm install -g jscodeshift\n",[242,1254,1255],{"class":244,"line":305},[242,1256,1257],{},"$> mkdir jasmineCodemods && cd jasmineCodemods\n",[242,1259,1260],{"class":244,"line":327},[242,1261,1262],{},"$> git init && git commit -m \"initial commit\" --allow-empty\n",[242,1264,1265],{"class":244,"line":359},[242,1266,1267],{},"$> touch jasmine-async.js\n",[18,1269,1270],{},"Der Einfachkeit halber installieren wir jscodeshift global um das binary auf der Konsole ausführen zu können. Und das\nGit Repo zum einfachen hacken, sichern und zurückrollen darf auch nicht fehlen!",[18,1272,1273],{},"Dann legen wir uns eine Datei für der/die/das erste codemod an und schreiben folgenden Inhalt:",[234,1275,1277],{"className":1048,"code":1276,"language":1050,"meta":141,"style":141},"// jasmine-async.js\nmodule.exports = function transformer(file, api) {\n const j = api.jscodeshift;\n const { statement } = j.template;\n const root = j(file.source);\n return root;\n};\n",[147,1278,1279,1284,1315,1328,1346,1360,1368],{"__ignoreMap":141},[242,1280,1281],{"class":244,"line":245},[242,1282,1283],{"class":1097},"// jasmine-async.js\n",[242,1285,1286,1289,1291,1294,1297,1300,1303,1305,1308,1310,1313],{"class":244,"line":277},[242,1287,1288],{"class":448},"module",[242,1290,452],{"class":248},[242,1292,1293],{"class":448},"exports",[242,1295,1296],{"class":422}," =",[242,1298,1299],{"class":422}," function",[242,1301,1302],{"class":255}," transformer",[242,1304,458],{"class":248},[242,1306,1307],{"class":473},"file",[242,1309,464],{"class":248},[242,1311,1312],{"class":473},"api",[242,1314,1199],{"class":248},[242,1316,1317,1320,1323,1325],{"class":244,"line":305},[242,1318,1319],{"class":422}," const",[242,1321,1322],{"class":448}," j",[242,1324,1296],{"class":422},[242,1326,1327],{"class":248}," api.jscodeshift;\n",[242,1329,1330,1332,1335,1338,1341,1343],{"class":244,"line":327},[242,1331,1319],{"class":422},[242,1333,1334],{"class":248}," { ",[242,1336,1337],{"class":448},"statement",[242,1339,1340],{"class":248}," } ",[242,1342,259],{"class":422},[242,1344,1345],{"class":248}," j.template;\n",[242,1347,1348,1350,1353,1355,1357],{"class":244,"line":359},[242,1349,1319],{"class":422},[242,1351,1352],{"class":448}," root",[242,1354,1296],{"class":422},[242,1356,1322],{"class":255},[242,1358,1359],{"class":248},"(file.source);\n",[242,1361,1362,1365],{"class":244,"line":365},[242,1363,1364],{"class":422}," return",[242,1366,1367],{"class":248}," root;\n",[242,1369,1370],{"class":244,"line":375},[242,1371,1372],{"class":248},"};\n",[18,1374,1375,1376,1379,1380,464,1383,464,1386,464,1389,1392,1393,1396,1397,1402,1403,1408,1409,1414],{},"Auf ",[147,1377,1378],{},"root"," können wir jetzt jscodeshift Methoden aufrufen wie ",[147,1381,1382],{},"find",[147,1384,1385],{},"filter",[147,1387,1388],{},"forEach",[147,1390,1391],{},"replaceWith"," und zuletzt\n",[147,1394,1395],{},"toSource",". Die Methoden machen genau das was der Name sagt, selbsterklärend. Genaueres muss man sich leider selbst\nim ",[24,1398,1401],{"href":1399,"rel":1400},"https://github.com/facebook/jscodeshift/blob/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/Collection.js",[28],"Source"," ",[24,1404,1407],{"href":1405,"rel":1406},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/collections",[28],"Code","\nauf ",[24,1410,1413],{"href":1411,"rel":1412},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b",[28],"Github"," zusammenkratzen.",[18,1416,1417],{},"Ausführen können wir das Skript später mit",[234,1419,1421],{"className":1239,"code":1420,"language":1241,"meta":141,"style":141},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n\n",[147,1422,1423,1427],{"__ignoreMap":141},[242,1424,1425],{"class":244,"line":245},[242,1426,389],{"emptyLinePlaceholder":388},[242,1428,1429],{"class":244,"line":277},[242,1430,1431],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n",[18,1433,1434],{},"Doch zuerst müssen Transformationen gecoded werden 😮",[18,1436,1437,1438,1440,1441,1443,1444,1447,1448,1450,1451,1453,1454,1459],{},"Wir wollen fürs erste alle ",[147,1439,1057],{}," Knoten finden und der übergebenen Funktion einen ",[147,1442,898],{}," Parameter spendieren. Zum Suchen\nvon Ausdrücken verwenden wir die jscodeshift Methode ",[147,1445,1446],{},"root.find",". Diese traversiert den AST und gibt uns eine Collection\nvon passenden Knoten zurück. Als Argument müssen wird dem ",[147,1449,1382],{}," Aufruf eine AST Beschreibung des ",[147,1452,1057],{}," Knotens mitgeben.\nBeim Finden der Beschreibung hilft uns der geniale ",[24,1455,1458],{"href":1456,"rel":1457},"https://astexplorer.net",[28],"astexplorer.net",". Wir kopieren den Code den\nwir transformieren wollen in den Editor und bekommen den AST ausgespuckt. Wir können sogar auf jeden beliebigen Knoten\nim Editor klicken und bekommen im AST den enstsprechenden Teil markiert!",[18,1461,1462],{},[139,1463],{"alt":1464,"src":1465},"astexplorer","https://media.synyx.de/uploads//2016/08/astexplorer-1024x631.png",[234,1467,1469],{"className":1048,"code":1468,"language":1050,"meta":141,"style":141},"// jasmine-async.js\nreturn root.find(j.CallExpression, {\n callee: {\n name: \"it\",\n },\n});\n",[147,1470,1471,1475,1488,1493,1503,1508],{"__ignoreMap":141},[242,1472,1473],{"class":244,"line":245},[242,1474,1283],{"class":1097},[242,1476,1477,1480,1483,1485],{"class":244,"line":277},[242,1478,1479],{"class":422},"return",[242,1481,1482],{"class":248}," root.",[242,1484,1382],{"class":255},[242,1486,1487],{"class":248},"(j.CallExpression, {\n",[242,1489,1490],{"class":244,"line":305},[242,1491,1492],{"class":248}," callee: {\n",[242,1494,1495,1498,1501],{"class":244,"line":327},[242,1496,1497],{"class":248}," name: ",[242,1499,1500],{"class":262},"\"it\"",[242,1502,580],{"class":248},[242,1504,1505],{"class":244,"line":359},[242,1506,1507],{"class":248}," },\n",[242,1509,1510],{"class":244,"line":365},[242,1511,586],{"class":248},[18,1513,1514,1515,1517,1518,1520,1521,1523],{},"Dann wollen wir für alle Knoten die gefunden werden etwas tun. Nämlich den ",[147,1516,898],{}," Parameter hinzufügen zur eigentlichen\nTestfunktion. Mit ",[147,1519,1388],{}," können wir über die von ",[147,1522,1382],{}," zurückgegebene Collection iterieren und dies tun.",[234,1525,1527],{"className":1048,"code":1526,"language":1050,"meta":141,"style":141},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // p.node.arguments[0] would be the spec description\n const specCallee = p.node.arguments[1];\n // add 'done' parameter\n specCallee.params.push(statment`done`);\n })\n\n",[147,1528,1529,1533,1537,1544,1559,1574,1579,1598,1603,1622],{"__ignoreMap":141},[242,1530,1531],{"class":244,"line":245},[242,1532,389],{"emptyLinePlaceholder":388},[242,1534,1535],{"class":244,"line":277},[242,1536,1283],{"class":1097},[242,1538,1539,1541],{"class":244,"line":305},[242,1540,1479],{"class":422},[242,1542,1543],{"class":248}," root\n",[242,1545,1546,1549,1551,1553,1556],{"class":244,"line":327},[242,1547,1548],{"class":248}," .",[242,1550,1382],{"class":255},[242,1552,458],{"class":248},[242,1554,1555],{"class":422},"...",[242,1557,1558],{"class":248},")\n",[242,1560,1561,1563,1565,1567,1569,1572],{"class":244,"line":359},[242,1562,1548],{"class":248},[242,1564,1388],{"class":255},[242,1566,458],{"class":248},[242,1568,18],{"class":473},[242,1570,1571],{"class":422}," =>",[242,1573,435],{"class":248},[242,1575,1576],{"class":244,"line":365},[242,1577,1578],{"class":1097}," // p.node.arguments[0] would be the spec description\n",[242,1580,1581,1584,1587,1589,1592,1595],{"class":244,"line":375},[242,1582,1583],{"class":422}," const",[242,1585,1586],{"class":448}," specCallee",[242,1588,1296],{"class":422},[242,1590,1591],{"class":248}," p.node.arguments[",[242,1593,1594],{"class":448},"1",[242,1596,1597],{"class":248},"];\n",[242,1599,1600],{"class":244,"line":385},[242,1601,1602],{"class":1097}," // add 'done' parameter\n",[242,1604,1605,1608,1611,1613,1616,1619],{"class":244,"line":392},[242,1606,1607],{"class":248}," specCallee.params.",[242,1609,1610],{"class":255},"push",[242,1612,458],{"class":248},[242,1614,1615],{"class":255},"statment",[242,1617,1618],{"class":262},"`done`",[242,1620,1621],{"class":248},");\n",[242,1623,1624],{"class":244,"line":550},[242,1625,1626],{"class":248}," })\n",[18,1628,1629,1630,1632,1633,1636,1637,1640,1641,1643,1644,1646],{},"Die Variable ",[147,1631,18],{}," ist der Pfad des gefundenen Knotens. Man könnte die Variable auch ",[147,1634,1635],{},"path"," benennen, würde sich aber\nbeißen mit dem node Modul ",[147,1638,1639],{},"const path = require('path');",". Das importieren dieses Moduls ist keine Seltenheit in\ncodemods denke ich. Und als Konvention nehmen wir einfach ",[147,1642,18],{}," statt ",[147,1645,1635],{},", immer!",[18,1648,1649,1650,1652,1653,1655,1656,1658],{},"Der ASTExplorer zeigt wie wir an die Funktion kommen der wir den ",[147,1651,898],{}," Parameter hinzufügen möchten. Wir holen uns das\nzweite Element der CallExpression Argumente und fügen dessen Parameter Liste einfach das ",[147,1654,898],{}," hinzu. Leider (?) können\nwir aber keinen String übergeben. Wir erinnern uns an den AST. Wir brauchen eine Beschreibung des Knotens. Man könnte\njetzt entweder ein komplexes Objekt erstellen, oder man nimmt sich einfach die nützliche ",[147,1657,1337],{}," Funktion zu Hilfe.\nAuf die Funktion machte mich ein Kollege aufmerksam. Sie ist leider nicht in der Doku zu finden sondern nur in codemods\nauf Github… Sagte ich schon, dass die Doku etwas spärlich ist?",[18,1660,1661,1662,1665],{},"Zum Abschluss müssen wir die Änderungen mit ",[147,1663,1664],{},"toSource()"," an jscodeshift zurückgeben um die Datei neu zu schreiben.",[234,1667,1669],{"className":1048,"code":1668,"language":1050,"meta":141,"style":141},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(...)\n .toSource()\n\n",[147,1670,1671,1675,1679,1685,1697,1709],{"__ignoreMap":141},[242,1672,1673],{"class":244,"line":245},[242,1674,389],{"emptyLinePlaceholder":388},[242,1676,1677],{"class":244,"line":277},[242,1678,1283],{"class":1097},[242,1680,1681,1683],{"class":244,"line":305},[242,1682,1479],{"class":422},[242,1684,1543],{"class":248},[242,1686,1687,1689,1691,1693,1695],{"class":244,"line":327},[242,1688,1548],{"class":248},[242,1690,1382],{"class":255},[242,1692,458],{"class":248},[242,1694,1555],{"class":422},[242,1696,1558],{"class":248},[242,1698,1699,1701,1703,1705,1707],{"class":244,"line":359},[242,1700,1548],{"class":248},[242,1702,1388],{"class":255},[242,1704,458],{"class":248},[242,1706,1555],{"class":422},[242,1708,1558],{"class":248},[242,1710,1711,1713,1715],{"class":244,"line":365},[242,1712,1548],{"class":248},[242,1714,1395],{"class":255},[242,1716,1717],{"class":248},"()\n",[18,1719,1720],{},"Zum schnellen Testen kann die Transformation auf der Konsole mit",[234,1722,1724],{"className":1239,"code":1723,"language":1241,"meta":141,"style":141},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n\n",[147,1725,1726,1730],{"__ignoreMap":141},[242,1727,1728],{"class":244,"line":245},[242,1729,389],{"emptyLinePlaceholder":388},[242,1731,1732],{"class":244,"line":277},[242,1733,1734],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n",[18,1736,1737],{},"ausgeführt werden. Nach dem ersten Staunen aber mit",[234,1739,1741],{"className":1239,"code":1740,"language":1241,"meta":141,"style":141},"\n$> git checkout HEAD -- pfad/zur/source/datei.js\n\n",[147,1742,1743,1747],{"__ignoreMap":141},[242,1744,1745],{"class":244,"line":245},[242,1746,389],{"emptyLinePlaceholder":388},[242,1748,1749],{"class":244,"line":277},[242,1750,1751],{},"$> git checkout HEAD -- pfad/zur/source/datei.js\n",[18,1753,1754],{},"zurück gesetzt werden.",[18,1756,1757],{},"Git o/",[18,1759,1760],{},"Der erste Punkt ist erledigt.",[82,1762,1763,1771,1777],{},[85,1764,1765],{},[967,1766,1150,1767,1153,1769,1156],{},[147,1768,1057],{},[147,1770,898],{},[85,1772,1773,1162,1775,1166],{},[147,1774,1161],{},[147,1776,1165],{},[85,1778,1169,1779,1172],{},[147,1780,894],{},[18,1782,1783,1784,1786,1787,1790],{},"Ersetzen wir als nächstes ",[147,1785,1161],{}," mit dem ",[147,1788,1789],{},"done()"," Aufruf. Dazu klicken wir im ASTExplorer auf den entsprechenden\nAusdruck und schauen rechts im AST nach der Pfad Beschreibung die wir brauchen.",[234,1792,1794],{"className":1048,"code":1793,"language":1050,"meta":141,"style":141},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // replace 'done = true' with done() invocation\n j(p).find(j.ExpressionStatement, {\n expression: {\n type: j.AssignmentExpression.name,\n left: {\n name: 'done'\n }\n }\n }).replaceWith(p => statement`done();`);\n })\n .toSource()\n\n",[147,1795,1796,1800,1804,1810,1822,1836,1841,1846,1859,1864,1869,1874,1882,1887,1892,1913,1917],{"__ignoreMap":141},[242,1797,1798],{"class":244,"line":245},[242,1799,389],{"emptyLinePlaceholder":388},[242,1801,1802],{"class":244,"line":277},[242,1803,1283],{"class":1097},[242,1805,1806,1808],{"class":244,"line":305},[242,1807,1479],{"class":422},[242,1809,1543],{"class":248},[242,1811,1812,1814,1816,1818,1820],{"class":244,"line":327},[242,1813,1548],{"class":248},[242,1815,1382],{"class":255},[242,1817,458],{"class":248},[242,1819,1555],{"class":422},[242,1821,1558],{"class":248},[242,1823,1824,1826,1828,1830,1832,1834],{"class":244,"line":359},[242,1825,1548],{"class":248},[242,1827,1388],{"class":255},[242,1829,458],{"class":248},[242,1831,18],{"class":473},[242,1833,1571],{"class":422},[242,1835,435],{"class":248},[242,1837,1838],{"class":244,"line":365},[242,1839,1840],{"class":1097}," // ...\n",[242,1842,1843],{"class":244,"line":375},[242,1844,1845],{"class":1097}," // replace 'done = true' with done() invocation\n",[242,1847,1848,1851,1854,1856],{"class":244,"line":385},[242,1849,1850],{"class":255}," j",[242,1852,1853],{"class":248},"(p).",[242,1855,1382],{"class":255},[242,1857,1858],{"class":248},"(j.ExpressionStatement, {\n",[242,1860,1861],{"class":244,"line":392},[242,1862,1863],{"class":248}," expression: {\n",[242,1865,1866],{"class":244,"line":550},[242,1867,1868],{"class":248}," type: j.AssignmentExpression.name,\n",[242,1870,1871],{"class":244,"line":555},[242,1872,1873],{"class":248}," left: {\n",[242,1875,1876,1879],{"class":244,"line":571},[242,1877,1878],{"class":248}," name: ",[242,1880,1881],{"class":262},"'done'\n",[242,1883,1884],{"class":244,"line":583},[242,1885,1886],{"class":248}," }\n",[242,1888,1889],{"class":244,"line":682},[242,1890,1891],{"class":248}," }\n",[242,1893,1894,1897,1899,1901,1903,1905,1908,1911],{"class":244,"line":688},[242,1895,1896],{"class":248}," }).",[242,1898,1391],{"class":255},[242,1900,458],{"class":248},[242,1902,18],{"class":473},[242,1904,1571],{"class":422},[242,1906,1907],{"class":255}," statement",[242,1909,1910],{"class":262},"`done();`",[242,1912,1621],{"class":248},[242,1914,1915],{"class":244,"line":693},[242,1916,1626],{"class":248},[242,1918,1919,1921,1923],{"class":244,"line":699},[242,1920,1548],{"class":248},[242,1922,1395],{"class":255},[242,1924,1717],{"class":248},[18,1926,1927,1928,1930,1931,452],{},"Da wir wissen, dass ",[147,1929,1161],{}," nur einmal vorkommt, können wir der Collection direkt sagen bitte ersetzen mit dem\nStatement ",[147,1932,1165],{},[18,1934,1935,1936,1938],{},"Die ",[147,1937,898],{}," Variable ist jetzt natürlich obsolet und kann komplett entfernt werden. Wieder schauen wir im ASTExplorer\nnach der Beschreibung die wir brauchen um auf folgendes zu kommen:",[234,1940,1942],{"className":1048,"code":1941,"language":1050,"meta":141,"style":141},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // get rid of 'var done = false'\n j(p).find(j.VariableDeclaration, {\n declarations: [\n {\n type: j.VariableDeclarator.name,\n id: {\n name: 'done'\n }\n }\n ]\n }).remove()\n })\n .toSource()\n\n",[147,1943,1944,1948,1952,1958,1970,1984,1988,1993,2004,2009,2014,2019,2024,2031,2036,2040,2045,2054,2059],{"__ignoreMap":141},[242,1945,1946],{"class":244,"line":245},[242,1947,389],{"emptyLinePlaceholder":388},[242,1949,1950],{"class":244,"line":277},[242,1951,1283],{"class":1097},[242,1953,1954,1956],{"class":244,"line":305},[242,1955,1479],{"class":422},[242,1957,1543],{"class":248},[242,1959,1960,1962,1964,1966,1968],{"class":244,"line":327},[242,1961,1548],{"class":248},[242,1963,1382],{"class":255},[242,1965,458],{"class":248},[242,1967,1555],{"class":422},[242,1969,1558],{"class":248},[242,1971,1972,1974,1976,1978,1980,1982],{"class":244,"line":359},[242,1973,1548],{"class":248},[242,1975,1388],{"class":255},[242,1977,458],{"class":248},[242,1979,18],{"class":473},[242,1981,1571],{"class":422},[242,1983,435],{"class":248},[242,1985,1986],{"class":244,"line":365},[242,1987,1840],{"class":1097},[242,1989,1990],{"class":244,"line":375},[242,1991,1992],{"class":1097}," // get rid of 'var done = false'\n",[242,1994,1995,1997,1999,2001],{"class":244,"line":385},[242,1996,1850],{"class":255},[242,1998,1853],{"class":248},[242,2000,1382],{"class":255},[242,2002,2003],{"class":248},"(j.VariableDeclaration, {\n",[242,2005,2006],{"class":244,"line":392},[242,2007,2008],{"class":248}," declarations: [\n",[242,2010,2011],{"class":244,"line":550},[242,2012,2013],{"class":248}," {\n",[242,2015,2016],{"class":244,"line":555},[242,2017,2018],{"class":248}," type: j.VariableDeclarator.name,\n",[242,2020,2021],{"class":244,"line":571},[242,2022,2023],{"class":248}," id: {\n",[242,2025,2026,2029],{"class":244,"line":583},[242,2027,2028],{"class":248}," name: ",[242,2030,1881],{"class":262},[242,2032,2033],{"class":244,"line":682},[242,2034,2035],{"class":248}," }\n",[242,2037,2038],{"class":244,"line":688},[242,2039,1886],{"class":248},[242,2041,2042],{"class":244,"line":693},[242,2043,2044],{"class":248}," ]\n",[242,2046,2047,2049,2052],{"class":244,"line":699},[242,2048,1896],{"class":248},[242,2050,2051],{"class":255},"remove",[242,2053,1717],{"class":248},[242,2055,2057],{"class":244,"line":2056},18,[242,2058,1626],{"class":248},[242,2060,2062,2064,2066],{"class":244,"line":2061},19,[242,2063,1548],{"class":248},[242,2065,1395],{"class":255},[242,2067,1717],{"class":248},[18,2069,2070],{},"Zweiter Punkt auch erledigt.",[82,2072,2073,2081,2089],{},[85,2074,2075],{},[967,2076,1150,2077,1153,2079,1156],{},[147,2078,1057],{},[147,2080,898],{},[85,2082,2083],{},[967,2084,2085,1162,2087,1166],{},[147,2086,1161],{},[147,2088,1165],{},[85,2090,1169,2091,1172],{},[147,2092,894],{},[18,2094,2095,2096,2098],{},"Fehlt nur noch das Entfernen des ",[147,2097,894],{}," Blocks. Richtig geraten! Der ASTExplorer zeigt uns was wir suchen müssen.",[234,2100,2102],{"className":1048,"code":2101,"language":1050,"meta":141,"style":141},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(p => {\n // ...\n // get rid of obsolete waitsFor block\n j(p).find(j.CallExpression, {\n callee: {\n name: 'waitsFor'\n }\n }).remove()\n })\n .toSource()\n\n",[147,2103,2104,2108,2112,2118,2130,2144,2148,2153,2163,2168,2176,2180,2188,2192],{"__ignoreMap":141},[242,2105,2106],{"class":244,"line":245},[242,2107,389],{"emptyLinePlaceholder":388},[242,2109,2110],{"class":244,"line":277},[242,2111,1283],{"class":1097},[242,2113,2114,2116],{"class":244,"line":305},[242,2115,1479],{"class":422},[242,2117,1543],{"class":248},[242,2119,2120,2122,2124,2126,2128],{"class":244,"line":327},[242,2121,1548],{"class":248},[242,2123,1382],{"class":255},[242,2125,458],{"class":248},[242,2127,1555],{"class":422},[242,2129,1558],{"class":248},[242,2131,2132,2134,2136,2138,2140,2142],{"class":244,"line":359},[242,2133,1548],{"class":248},[242,2135,1388],{"class":255},[242,2137,458],{"class":248},[242,2139,18],{"class":473},[242,2141,1571],{"class":422},[242,2143,435],{"class":248},[242,2145,2146],{"class":244,"line":365},[242,2147,1840],{"class":1097},[242,2149,2150],{"class":244,"line":375},[242,2151,2152],{"class":1097}," // get rid of obsolete waitsFor block\n",[242,2154,2155,2157,2159,2161],{"class":244,"line":385},[242,2156,1850],{"class":255},[242,2158,1853],{"class":248},[242,2160,1382],{"class":255},[242,2162,1487],{"class":248},[242,2164,2165],{"class":244,"line":392},[242,2166,2167],{"class":248}," callee: {\n",[242,2169,2170,2173],{"class":244,"line":550},[242,2171,2172],{"class":248}," name: ",[242,2174,2175],{"class":262},"'waitsFor'\n",[242,2177,2178],{"class":244,"line":555},[242,2179,1891],{"class":248},[242,2181,2182,2184,2186],{"class":244,"line":571},[242,2183,1896],{"class":248},[242,2185,2051],{"class":255},[242,2187,1717],{"class":248},[242,2189,2190],{"class":244,"line":583},[242,2191,1626],{"class":248},[242,2193,2194,2196,2198],{"class":244,"line":682},[242,2195,1548],{"class":248},[242,2197,1395],{"class":255},[242,2199,1717],{"class":248},[18,2201,2202],{},"Fertig!",[18,2204,2205],{},"Schnell noch testen obs auch wirklich tut:",[234,2207,2208],{"className":1239,"code":1723,"language":1241,"meta":141,"style":141},[147,2209,2210,2214],{"__ignoreMap":141},[242,2211,2212],{"class":244,"line":245},[242,2213,389],{"emptyLinePlaceholder":388},[242,2215,2216],{"class":244,"line":277},[242,2217,1734],{},[18,2219,2220],{},"Und ab damit ins Repo",[234,2222,2224],{"className":1239,"code":2223,"language":1241,"meta":141,"style":141},"\n$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n\n",[147,2225,2226,2230],{"__ignoreMap":141},[242,2227,2228],{"class":244,"line":245},[242,2229,389],{"emptyLinePlaceholder":388},[242,2231,2232],{"class":244,"line":277},[242,2233,2234],{},"$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n",[18,2236,2237],{},"Mit dieser codemod können wir ab sofort mit einem Befehl zig JavaScript Dateien transformieren lassen o/ Wobei ich\ntrotzdem ein Code Review empfehlen würde. Und nicht blind auf den Master pushen.",[74,2239,2241],{"id":2240},"ausblick","Ausblick",[18,2243,2244,2245,2247,2248,2250],{},"Zugegeben. Ich habe hier ein wirklich einfaches Beispiel gewählt. Aber trotzdem hat das viele Tests unseres Projektes\nabgedeckt. Ich habe noch ein paar Bedingungen hinzugefügt zur codemod wie z. B. bitte abbrechen, wenn Anzahl der\n",[147,2246,894],{}," Blöcke != 1. Das ignoriert dann alles specs die synchron sind und asynchrone Tests die ein anderes Muster\naufweisen. Bei uns waren das z. B. Integrations Tests die mehrere ",[147,2249,894],{}," Blöcke hatten weil mehrere Klicks simuliert\nwurden.",[18,2252,2253],{},"Es fehlen auch noch die restlichen jasmine Transformationen für Matchers und Spies. Aber ich denke es sollte recht klar\ngeworden sein wie auch dafür codemods geschrieben werden können.",[18,2255,2256,2257,2262,2263,2266,2267,2270],{},"Werden codemods komplexer kann man über Unit Tests nachdenken. Hier gibt es Hilfe von jscodeshift. Als Beispiel\nhilft ",[24,2258,2261],{"href":2259,"rel":2260},"https://github.com/cpojer/js-codemod/blob/82af3089f22fa0687159f64177b73908b82d074f/transforms/__tests__/arrow-function-test.js",[28],"diese Stelle hier",".\nKurzer Ablauf: Als TestRunner wird jest benötigt. Der Quellcode der transformiert und der Quellcode der herausspringen\nsoll, werden jeweils als Datei im Verzeichnis ‘",[211,2264,2265],{},"testfixtures","’ abgelegt. Der Test liegt im Verzeichnis\n‘",[211,2268,2269],{},"tests","’ und definiert mittels den TestUtils einfach nur den Test. Codemods testgetrieben entwickeln steht nichts\nmehr im Weg.",[234,2272,2274],{"className":1048,"code":2273,"language":1050,"meta":141,"style":141},"// jasmine-async.spec.js\nconst defineTest = require(\"jscodeshift/dist/testUtils\").defineTest;\ndefineTest(__dirname, \"jasmine-async\");\n",[147,2275,2276,2281,2302],{"__ignoreMap":141},[242,2277,2278],{"class":244,"line":245},[242,2279,2280],{"class":1097},"// jasmine-async.spec.js\n",[242,2282,2283,2286,2289,2291,2294,2296,2299],{"class":244,"line":277},[242,2284,2285],{"class":422},"const",[242,2287,2288],{"class":448}," defineTest",[242,2290,1296],{"class":422},[242,2292,2293],{"class":255}," require",[242,2295,458],{"class":248},[242,2297,2298],{"class":262},"\"jscodeshift/dist/testUtils\"",[242,2300,2301],{"class":248},").defineTest;\n",[242,2303,2304,2307,2310,2313],{"class":244,"line":305},[242,2305,2306],{"class":255},"defineTest",[242,2308,2309],{"class":248},"(__dirname, ",[242,2311,2312],{"class":262},"\"jasmine-async\"",[242,2314,1621],{"class":248},[74,2316,2318],{"id":2317},"fazit","Fazit",[18,2320,2321],{},"Trotz spärlicher Doku findet man sich nach gewisser Zeit zurecht. Vor allem der ASTExplorer ist eine große Hilfe dabei!\nFür der/die/das erste codemod habe ich vielleicht 3x länger gebraucht, als im Projekt alles per Hand zu ersetzen. Aber\nkennt man das Vorgehen mit jscodeshift gleicht sich das schnell wieder aus. Und der/die/das codemod kann wiederverwendet\nwerden! Wenn auch mit kleinen Anpassungen für andere Gegebenheiten.",[18,2323,2324,2325,2330,2331,2336],{},"Oft verwendet habe ich bisher die ",[24,2326,2329],{"href":2327,"rel":2328},"https://github.com/cpojer/js-codemod",[28],"js-codemods","\nvon ",[24,2332,2335],{"href":2333,"rel":2334},"https://twitter.com/cpojer",[28],"@cpojer"," zum transformieren zu neuen es2015 Sprachfeatures.",[18,2338,2339],{},[211,2340,2341],{},"Meine Empfehlung:",[18,2343,2344],{},"Einmal reinknien und machen! Vielleicht sogar für kleinere Projekte.",[763,2346,2347],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":141,"searchDepth":277,"depth":277,"links":2349},[2350,2351,2352,2355,2356],{"id":914,"depth":277,"text":915},{"id":953,"depth":277,"text":954},{"id":1003,"depth":277,"text":1004,"children":2353},[2354],{"id":1030,"depth":305,"text":1031},{"id":2240,"depth":277,"text":2241},{"id":2317,"depth":277,"text":2318},[775],"2016-08-25T09:56:03","Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\\nArt von asynchronen Specs und einmal die verwendeten Expectations.\\nUnter http://jasmine.github.io/2.0/upgrading.html wurde super\\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.","https://synyx.de/blog/javascript-code-refactoring-automatisieren/",{},"/blog/javascript-code-refactoring-automatisieren",{"title":871,"description":2364},"Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion\nBibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei unseren Tests zu refactoren. Einmal die\nArt von asynchronen Specs und einmal die verwendeten Expectations.\nUnter http://jasmine.github.io/2.0/upgrading.html wurde super\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.","blog/javascript-code-refactoring-automatisieren",[2367,415,2368],"automatisierung","refactoring","Vor kurzem hatte ich die Muße ein älteres JavaScript Projekt zu refactoren. Unter anderem sollte die Assertion Bibliothek Jasmine von 1.x auf 2.x aktualisiert werden. Zwei Dinge gab es bei…","npdOLNgV0j3l4jQhircA5afYDyTYYRrnbICTKF9u9Mw",{"id":2372,"title":2373,"author":2374,"body":2375,"category":2784,"date":2785,"description":2786,"extension":779,"link":2787,"meta":2788,"navigation":388,"path":2789,"seo":2790,"slug":2792,"stem":2793,"tags":2794,"teaser":2799,"__hash__":2800},"blog/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting.md","springboot & reactjs #2 | progressive enhancement based on list sorting",[9],{"type":11,"value":2376,"toc":2776},[2377,2380,2389,2392,2395,2399,2445,2447,2450,2453,2458,2465,2468,2471,2478,2500,2511,2514,2521,2528,2531,2538,2557,2563,2566,2569,2572,2586,2589,2612,2627,2630,2640,2646,2653,2656,2659,2662,2676,2695,2698,2707,2723,2727,2751,2757,2761,2768,2773],[14,2378,2373],{"id":2379},"springboot-reactjs-2-progressive-enhancement-based-on-list-sorting",[18,2381,2382,2383,2388],{},"This is the second article of a springboot & reactjs article series about server side rendering and progressive\nenhancement. In the ",[24,2384,2387],{"href":2385,"rel":2386},"https://synyx.de/2016/03/universal-webapp-development-with-spring-boot-react/",[28],"first article"," we\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\nCurrently we just see a static list of awesome products…",[18,2390,2391],{},"Today we will implement the sorting feature that should work with a plain html form submit as well as with a Ajax\nRequest and client side rendering. So the app is progressively enhanced with JavaScript o/",[2393,2394],"hr",{},[74,2396,2398],{"id":2397},"springboot-reactjs-article-series","springboot & reactjs article series",[2400,2401,2402,2410,2439,2442],"ol",{},[85,2403,2404,2409],{},[24,2405,2408],{"href":2406,"rel":2407},"https://synyx.de/2016/03/springboot-reactjs-server-side-rendering",[28],"server side rendering"," ✅",[85,2411,2412,2413],{},"progressive enhancement based on list sorting 🆕\n",[82,2414,2415,2421,2427,2433],{},[85,2416,2417],{},[24,2418,2420],{"href":2419},"#html-form-and-server-side-rendering","HTML form and server side rendering",[85,2422,2423],{},[24,2424,2426],{"href":2425},"#enhance-the-client","Enhance the client",[85,2428,2429],{},[24,2430,2432],{"href":2431},"#make-the-back-button-work-again","Make the back button work again",[85,2434,2435],{},[24,2436,2438],{"href":2437},"#what-do-we-have-learned-so-far","What do we have learned so far",[85,2440,2441],{},"improving developer experience",[85,2443,2444],{},"lessons learned",[2393,2446],{},[18,2448,2449],{},"While the JavaScript solution with client side rendering results in faster rendering and therefore a better user\nexperience, it also has it’s costs. We have to manage the browser url by ourselves. Furthermore we have to implement the\nbrowsers back button feature /o",[18,2451,2452],{},"But let’s take one step after another…",[18,2454,2455],{},[211,2456,2457],{},"tl;dr",[18,2459,2460],{},[24,2461,2464],{"href":2462,"rel":2463},"https://github.com/synyx/springboot-reactjs-demo",[28],"project source code is available on github",[74,2466,2420],{"id":2467},"html-form-and-server-side-rendering",[18,2469,2470],{},"Before we can even start thinking about the back button we have to implement the sorting feature. So we create a plain\nhtml form first that fires a good old get request.",[18,2472,2473,2474,2477],{},"The ProductFilterItem is a simple input field of type radio(button). For the moment the sorting should only consider one\nattribute. Therefore a radiobutton group is the way to go. So every input is defined with ",[147,2475,2476],{},"name=\"sort\""," and a label to\nincrease the clickable area.",[18,2479,2480,2481,2484,2485,2488,2489,2492,2493,2495,2496,2499],{},"In the previous blog the ",[211,2482,2483],{},"ProductList"," was the only component to render and therefore the entry in ",[211,2486,2487],{},"main.js",". But\nthe new ",[211,2490,2491],{},"ProductFilter"," is not part of the list. The ",[211,2494,2483],{}," reacts to the filter parameter set by the user.\nSo we have to create an ",[211,2497,2498],{},"App"," container that combines our awesome ProductList and ProductFilter components.",[18,2501,2502,2503,2506,2507,2510],{},"Since we have a container now to combine the ProductList and the ProductFilter components we also have to adjust the\n",[147,2504,2505],{},"global.renderServer"," function. First we add a second parameter ",[147,2508,2509],{},"sortBy"," to be able to render the selected radio button.\nThen we must render the new App container instead of the plain ProductList.",[18,2512,2513],{},"That’s it for the frontend part!",[18,2515,2516,2517,2520],{},"Next we need to extend the backend controller to process the ",[147,2518,2519],{},"sort"," request parameter defined in the ProductFilter form\nand use it to sort the product list.",[18,2522,2523,2524,2527],{},"Additionally the ",[147,2525,2526],{},"React#renderProducts"," method must be extended, too, of course.",[18,2529,2530],{},"And that’s it with the backend part as well!",[18,2532,2533,2534,2537],{},"Now build the frontend, start the spring boot app, open your browser on ",[147,2535,2536],{},"http://localhost:8080"," and start sorting the\nawesome product list 🙂",[234,2539,2541],{"className":1239,"code":2540,"language":1241,"meta":141,"style":141},"\n$ npm run build\n$ ./gradlew bootRun\n\n",[147,2542,2543,2547,2552],{"__ignoreMap":141},[242,2544,2545],{"class":244,"line":245},[242,2546,389],{"emptyLinePlaceholder":388},[242,2548,2549],{"class":244,"line":277},[242,2550,2551],{},"$ npm run build\n",[242,2553,2554],{"class":244,"line":305},[242,2555,2556],{},"$ ./gradlew bootRun\n",[18,2558,2559],{},[139,2560],{"alt":2561,"src":2562},"awesome_productlist_sorting","https://media.synyx.de/uploads//2016/04/awesome_productlist_sorting.gif",[74,2564,2426],{"id":2565},"enhance-the-client",[18,2567,2568],{},"So far our awesome product list is fully functional. Let’s recap what we can do now.",[18,2570,2571],{},"We are able to:",[82,2573,2574,2577,2580,2583],{},[85,2575,2576],{},"see the awesome product info",[85,2578,2579],{},"sort the awesome products by name or price",[85,2581,2582],{},"use the browser’s back and forward button (static site!)",[85,2584,2585],{},"bookmark every single view",[18,2587,2588],{},"As the next step we want to increase the user experience with AJAX requests and client side rendering. This results in a\nmuch quicker feedback for the user as requesting the whole html document.",[18,2590,2591,2592,2596,2597,2601,2602,580,2605,464,2608,2611],{},"To fetch data dynamically from the server we have to prevent the native form submit and take over the control by\nourselves. React provides [lifecycle methods](",[24,2593,2594],{"href":2594,"rel":2595},"http://git@gitlab-test.synyx.coffee",[28],":\nseber/SynyxBibliothek.git ",[24,2598,2599],{"href":2599,"rel":2600},"https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods",[28],") like ",[147,2603,2604],{},"onClick",[147,2606,2607],{},"onChange",[147,2609,2610],{},"onSubmit",", etc. So we simply have to register a handler for the form submit. The handler does nothing but\nto prevent the native behaviour and to inform the consumer of the ProductFilter about the submit.",[18,2613,2614,2615,2617,2618,2620,2621,2626],{},"You may ask why we are subscribing our submit handler via ",[147,2616,2610],{}," on the form and not the ",[147,2619,2604],{}," hook on the\nsubmit button. Well, actually we could listen to the button click. But then we had to keep track of all form data by\nourselves… And the html form element already provides all this data as\na ",[24,2622,2625],{"href":2623,"rel":2624},"https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements",[28],"HTMLFormControlsCollection","!",[18,2628,2629],{},"Since we use Reacts API for DOM event handling, we have to render our awesome product list on the client, too. We only\nhave server side rendering at this moment, remember? 😉",[18,2631,2632,2633,2635,2636,2639],{},"So additionally to the ",[147,2634,2505],{}," function used by Nashorn we need a second function ",[147,2637,2638],{},"window.renderClient","\nthat we have to call on the client side (browser) as we will see later in this tutorial.",[18,2641,2642,2643,2645],{},"Next we have to add the initial rendering to the index.html template. Of course, we must call ",[147,2644,2638],{}," with\nthe same data as on the server. However, React prints a nice error message on the browser console if the data differs (\nmeans the client side rendering would result in another DOM structure as the already existing one).",[18,2647,2648,2649,2652],{},"Back to the Java backend we have to inject the initial product list and the sortBy value into the server side model of\nthe ",[147,2650,2651],{},"ProductController.java"," class. Additionally we add a second endpoint to provide the sorted product list as json.",[18,2654,2655],{},"That’s it!",[18,2657,2658],{},"A form submit now fetches the minimal data from the server and the client takes care about the rendering. Awesome,\nright? If only this queasy feeling wouldn’t be there… Right… The browser url is not changing anymore /o And if it\ncouldn’t be worse… Without url changing we also lost the power of the glorious back button.",[74,2660,2432],{"id":2661},"make-the-back-button-work-again",[18,2663,2664,2665,2670,2671,452],{},"Okay, at first we should face the browser url. With HTML5 we’ve gained\nthe ",[24,2666,2669],{"href":2667,"rel":2668},"https://developer.mozilla.org/en-US/docs/Web/API/History_API",[28],"window.history"," api which is supported by all modern\nbrowsers. Changing the url is as simple as pushing the new state into the history\nwith ",[24,2672,2675],{"href":2673,"rel":2674},"https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries",[28],"window.history.pushState",[18,2677,2678,2679,2682,2683,2686,2687,2690,2691,2694],{},"Next we want to listen to the browsers back and forward buttons. This can be implemented with subscribing to the\n",[147,2680,2681],{},"popstate"," event. The subscription is done within ",[147,2684,2685],{},"componentDidMount"," since we only want to subscribe in the browser\nenvironment. ",[147,2688,2689],{},"Constructor"," and it’s counterpart ",[147,2692,2693],{},"componentWillMount"," are both called on server side creating the static\nhtml markup.",[18,2696,2697],{},"Finally we made it 🙂",[18,2699,2700,2701,2706],{},"Go on, build the frontend, start the spring boot app, open ",[89,2702,2703],{},[24,2704,2536],{"href":2536,"rel":2705},[28]," and admire our awesome progressively\nenhanced product list. Functional without JavaScript and even better with enabled JavaScript.",[234,2708,2709],{"className":1239,"code":2540,"language":1241,"meta":141,"style":141},[147,2710,2711,2715,2719],{"__ignoreMap":141},[242,2712,2713],{"class":244,"line":245},[242,2714,389],{"emptyLinePlaceholder":388},[242,2716,2717],{"class":244,"line":277},[242,2718,2551],{},[242,2720,2721],{"class":244,"line":305},[242,2722,2556],{},[74,2724,2726],{"id":2725},"what-do-we-have-learned-so-far","What do we have learned so far?",[82,2728,2729,2739,2748],{},[85,2730,2731,2732,2735,2736],{},"use plain HTML ",[147,2733,2734],{},"\u003Cform>"," element and enhance it with JavaScript and ",[147,2737,2738],{},"event.preventDefault",[85,2740,2741,2742,2744,2745,2747],{},"use ",[147,2743,2669],{}," and ",[147,2746,2681],{}," event to handle the browser back/forward button on the client",[85,2749,2750],{},"manually rebuilding and reloading the ReactJS app still sucks (autoreload would be cool, right)",[18,2752,2753],{},[139,2754],{"alt":2755,"src":2756},"progressive_js","https://media.synyx.de/uploads//2016/04/progressive_js.gif",[74,2758,2760],{"id":2759},"the-next-steps-will-be","The next steps will be",[82,2762,2763,2766],{},[85,2764,2765],{},"using webpack to enhance the developer experience",[85,2767,2444],{},[18,2769,2770],{},[211,2771,2772],{},"Stay tuned and keep learning!",[763,2774,2775],{},"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":141,"searchDepth":277,"depth":277,"links":2777},[2778,2779,2780,2781,2782,2783],{"id":2397,"depth":277,"text":2398},{"id":2467,"depth":277,"text":2420},{"id":2565,"depth":277,"text":2426},{"id":2661,"depth":277,"text":2432},{"id":2725,"depth":277,"text":2726},{"id":2759,"depth":277,"text":2760},[775],"2016-04-08T16:00:09","This is the second article of a springboot & reactjs article series about server side rendering and progressive\\nenhancement. In the first article we\\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\\nCurrently we just see a static list of awesome products…","https://synyx.de/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting/",{},"/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting",{"title":2373,"description":2791},"This is the second article of a springboot & reactjs article series about server side rendering and progressive\nenhancement. In the first article we\nhave learned how to render a ReactJS app on the server with nashorn. However, actually it is not really an “app” yet.\nCurrently we just see a static list of awesome products…","springboot-reactjs-progressive-enhancement-based-on-list-sorting","blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting",[614,415,2795,2796,2797,2798],"react","reactjs","spring","springboot","This is the second article of a springboot & reactjs article series about server side rendering and progressive enhancement. In the first article we have learned how to render a…","-bm-m-HOVsX8sO3gHZgQPhuKHIL62ikfjs8JOfJb9w8",{"id":2802,"title":2803,"author":2804,"body":2805,"category":3388,"date":3389,"description":3390,"extension":779,"link":3391,"meta":3392,"navigation":388,"path":3393,"seo":3394,"slug":3395,"stem":3396,"tags":3397,"teaser":3398,"__hash__":3399},"blog/blog/springboot-reactjs-server-side-rendering.md","springboot & reactjs #1 | server side rendering",[9],{"type":11,"value":2806,"toc":3381},[2807,2810,2813,2815,2817,2877,2879,2882,2885,2890,2893,2896,2899,2902,2905,2909,2914,2917,2920,2923,2931,2934,2937,2945,2951,2978,2981,2990,2996,2999,3002,3007,3014,3019,3034,3041,3052,3066,3070,3084,3088,3095,3110,3113,3116,3121,3136,3141,3148,3151,3175,3180,3190,3194,3215,3218,3220,3223,3236,3241,3244,3247,3250,3285,3296,3302,3307,3313,3316,3329,3335,3337,3359,3365,3367,3375,3379],[14,2808,2803],{"id":2809},"springboot-reactjs-1-server-side-rendering",[18,2811,2812],{},"This is the first article of a series about server side rendering and progressive enhancement. We will implement a\nproduct list that can be sorted by two parameters. Furthermore the app will be progressively enhanced, means the html\ndocument is rendered on the server and javascript will just enhance the app on the client if possible.",[2393,2814],{},[74,2816,2398],{"id":2397},[2400,2818,2819,2865,2873,2875],{},[85,2820,2821,2822],{},"server side rendering ✅\n",[82,2823,2824,2830,2836,2842,2848,2854,2860],{},[85,2825,2826],{},[24,2827,2829],{"href":2828},"#why-java","Why Java for the backend?",[85,2831,2832],{},[24,2833,2835],{"href":2834},"#why-reactjs","Why ReactJS for the client?",[85,2837,2838],{},[24,2839,2841],{"href":2840},"#springboot","Spring Boot Initializr",[85,2843,2844],{},[24,2845,2847],{"href":2846},"#backend","Backend",[85,2849,2850],{},[24,2851,2853],{"href":2852},"#frontend","Frontend",[85,2855,2856],{},[24,2857,2859],{"href":2858},"#running-the-app","Running the app",[85,2861,2862],{},[24,2863,2438],{"href":2864},"#what-we-have-learned-so-far",[85,2866,2867,2872],{},[24,2868,2871],{"href":2869,"rel":2870},"https://synyx.de/2016/04/springboot-reactjs-progressive-enhancement-based-on-list-sorting/",[28],"progressive enhancement based on list sorting","\n🆕",[85,2874,2441],{},[85,2876,2444],{},[2393,2878],{},[18,2880,2881],{},"Today we are going to create an awesome product list while using progressive enhancement to provide the best user\nexperience possible on each device. Progressive enhancement is a method in web development that sets focus on content\nfirst. The content will then be enhanced with dynamic features (JavaScript) and Layout (css). To provide content even\nwith disabled JavaScript the page has to be rendered on the server. For reasons (explained below) we use Java & spring\non the backend and ReactJS on the client side.",[18,2883,2884],{},"But why do we even want to make the effort of developing a universal web application? And even a progressive one? Of\ncourse this is overhead that must be handled and we have to implement and maintain more APIs as we will see later.\nHowever, the user experience is worth this effort in my opinion.",[18,2886,2887],{},[211,2888,2889],{},"Benefits of universal applications",[18,2891,2892],{},"thanks to server side rendered markup",[18,2894,2895],{},"– the app is fully functional from the start",[18,2897,2898],{},"– the app is usable instantly",[18,2900,2901],{},"JavaScript just enhances the features",[18,2903,2904],{},"– ajax calls without full page reloading are super fast",[18,2906,2907],{},[211,2908,2457],{},[18,2910,2911],{},[24,2912,2464],{"href":2462,"rel":2913},[28],[74,2915,2829],{"id":2916},"why-java-for-the-backend",[18,2918,2919],{},"To start with our little project we first have to think about the technologies we want to use. At synyx we are mainly\nusing Java for the backend. And over the last year we have built up a large knowledge around the spring ecosystem as\nwell.",[18,2921,2922],{},"So to answer the question",[82,2924,2925,2928],{},[85,2926,2927],{},"team knowledge (Spring, …)",[85,2929,2930],{},"battle tested solutions (Spring Security, Spring MVC, …)",[74,2932,2835],{"id":2933},"why-reactjs-for-the-client",[18,2935,2936],{},"Well, personally I am a fan of react and it’s ecosystem. That’s it! 😎",[18,2938,2939,2940,452],{},"Okay… actually there are good reasons to use react. React provides a simple API to develop UI components and it works\nwell with reactive architectures like Flux and its\nsuccessor ",[24,2941,2944],{"href":2942,"rel":2943},"https://web.archive.org/web/20160411023634/https://egghead.io/series/getting-started-with-redux",[28],"redux",[18,2946,2947,2948],{},"Furthermore it supports server side rendering quite well. ",[89,2949,2950],{},"(afaik Angular 2 and Ember could also be used, or cyclejs,\nor …)",[2952,2953,2954,2959],"blockquote",{},[18,2955,2956],{},[211,2957,2958],{},"Disclaimer",[18,2960,2961,2962,2967,2968,2972,2973,2977],{},"I will not explain spring, react or $technology. Nor I will explain Java or JavaScript, es2015 syntax in particular.\nThere already are excellent articles out there on the web\nlike ",[24,2963,2966],{"href":2964,"rel":2965},"http://javascriptplayground.com/blog/2016/02/the-react-webpack-tooling-problem",[28],"how to start with react"," (\nwithout\nusing webpack). Additionally I recommend to have a look at the official documentation\nfor ",[24,2969,2795],{"href":2970,"rel":2971},"https://facebook.github.io/react/docs/thinking-in-react.html",[28]," as well as ",[24,2974,2797],{"href":2975,"rel":2976},"https://spring.io/docs",[28],"\nof\ncourse.",[14,2979,2841],{"id":2980},"spring-boot-initializr",[18,2982,2983,2984,2989],{},"At first we’re going to generate a bootstrap project with the awesome ",[24,2985,2988],{"href":2986,"rel":2987},"https://start.spring.io",[28],"spring starter"," web\ninterface. We use gradle as build system, thymeleaf as template engine to render our views on the server and good old\nspring-web. Handlebars would also be an option as template engine but it is not supported by spring initializr.",[18,2991,2992],{},[139,2993],{"alt":2994,"src":2995},"springboot-initializr","https://media.synyx.de/uploads//2016/02/springboot-initializer.png",[14,2997,2847],{"id":2998},"backend",[18,3000,3001],{},"Starting with the backend we have to create the following files.",[18,3003,3004],{},[211,3005,3006],{},"index.html",[18,3008,3009,3010,3013],{},"The html template is as simple as it could be. We just need a div that acts as container for our ReactJS app.\n",[147,3011,3012],{},"th:utext=\"${content}\""," is thymeleaf specific and injects the content attribute of the view model as unescaped string.",[18,3015,3016],{},[211,3017,3018],{},"React.java",[18,3020,3021,3022,3025,3026,3029,3030,3033],{},"In React.java we are going to instantiate the Nashorn engine to be able to interpret JavaScript code which we will add\nlater. Nashorn is just a runtime environment for JavaScript on the JVM. It neither provides a ",[147,3023,3024],{},"window"," object nor a\n",[147,3027,3028],{},"console"," for logging. But since the latter one is required by ReactJS we have to load a ",[147,3031,3032],{},"nashorn-polyfill.js"," file\nbefore anything else.",[18,3035,3036,3037,3040],{},"We are loading our JavaScript sources into Nashorn with ",[147,3038,3039],{},"nashornScriptEngine.eval (\"load ('...')\")",". This is the same as\nincluding a script tag in a html document.",[18,3042,3043,3044,3047,3048,3051],{},"However, we could also call ",[147,3045,3046],{},"nashorn.eval (new InputStreamReader (...))"," to load the JavaScript files instead of using\nthe Nashorn specific ",[89,3049,3050],{},"load"," function. But we would lose the ability to debug the JavaScript code while running in\nNashorn (at least with IntelliJ). Which could be… useful 😉",[18,3053,3054,3055,3057,3058,3061,3062,3065],{},"Furthermore we have to implement a method ",[89,3056,2526],{}," which will invoke a global ",[147,3059,3060],{},"renderServer"," function\ndefined in ",[89,3063,3064],{},"app.bundle.js"," to create the rendered html string.",[18,3067,3068],{},[211,3069,3032],{},[18,3071,3072,3073,3076,3077,3079,3080,3083],{},"The polyfill for nashorn has to define a ",[211,3074,3075],{},"global"," variable (for reasons I will explain later) and the already\nmentioned ",[211,3078,3028],{},". ",[89,3081,3082],{},"print"," is a Nashorn function that logs on stdout.",[18,3085,3086],{},[211,3087,2651],{},[18,3089,3090,3091,3094],{},"The ProductController is responsible for getting the products and for setting the rendered html string as the ",[147,3092,3093],{},"content","\nattribute of the view model.",[18,3096,3097,3098,3103,3104,3109],{},"Additionally we need\na ",[24,3099,3102],{"href":3100,"rel":3101},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/Product.java",[28],"Product.java","\nPOJO and\na ",[24,3105,3108],{"href":3106,"rel":3107},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/ProductRepository.java",[28],"ProductRepository.java",".\nI think this is very straight forward and code snippets are obsolete here.",[14,3111,2853],{"id":3112},"frontend",[18,3114,3115],{},"With the backend part ready we can start with the frontend.",[18,3117,3118,3119,452],{},"At first we have to do a small setup to enable es2015 compilation and module bundling with webpack. Webpack is actually\nnot required but eases our developer lives immensely. Babel-cli could also be used with small adjustments in\n",[89,3120,3018],{},[234,3122,3124],{"className":1239,"code":3123,"language":1241,"meta":141,"style":141},"$ npm init\n$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n\n",[147,3125,3126,3131],{"__ignoreMap":141},[242,3127,3128],{"class":244,"line":245},[242,3129,3130],{},"$ npm init\n",[242,3132,3133],{"class":244,"line":277},[242,3134,3135],{},"$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n",[18,3137,3138],{},[211,3139,3140],{},"webpack.config.js",[18,3142,3143,3144,3147],{},"Next we configure webpack to generate a bundle of our JavaScript files including the ReactJS library and our app\nbusiness logic. Please note ",[211,3145,3146],{},"output.filename"," which is the file loaded by React.java.",[18,3149,3150],{},"Webpack can then simply be used to create the bundle by a npm task.",[234,3152,3154],{"className":1239,"code":3153,"language":1241,"meta":141,"style":141},"// package.json\n\"scripts\": {\n \"build\": \"webpack\"\n}\n\n",[147,3155,3156,3161,3166,3171],{"__ignoreMap":141},[242,3157,3158],{"class":244,"line":245},[242,3159,3160],{},"// package.json\n",[242,3162,3163],{"class":244,"line":277},[242,3164,3165],{},"\"scripts\": {\n",[242,3167,3168],{"class":244,"line":305},[242,3169,3170],{}," \"build\": \"webpack\"\n",[242,3172,3173],{"class":244,"line":327},[242,3174,547],{},[18,3176,3177],{},[211,3178,3179],{},"ProducList.js",[18,3181,3182,3183,3185,3186,3189],{},"To start with the UI components we are going to create the ProductList as the main component. It will be, drum roll,\nresponsible for displaying a list of products which have a name and a price (remember ",[89,3184,3102],{},"?). Therefore we\nimplement a function that takes our products and returns the representational markup. To avoid\n",[147,3187,3188],{},"\"Cannot read property 'map' of undefined\""," type errors we simply assign an empty array to the products by default.",[18,3191,3192],{},[211,3193,2487],{},[18,3195,3196,3197,3199,3200,3202,3203,3205,3206,3208,3209,3211,3212,3214],{},"Next we need the entry point of our ReactJS app to define the ",[211,3198,3060],{}," function invoked by Nashorn. Remember the\n",[211,3201,3075],{}," variable set in ",[89,3204,3032],{},"? We use this variable now to “export” our ",[89,3207,3060],{}," function. If\nyou are familiar with the NodeJS environment, you already know that the ",[89,3210,3075],{}," object is the equivalent to the\n",[89,3213,3024],{}," object available in the browser. And Nashorn is our equivalent of NodeJS 😉",[14,3216,2859],{"id":3217},"running-the-app",[18,3219,2655],{},[18,3221,3222],{},"Now we can run our first universal server side rendered springboot application to admire our graceful product list. Go\non, run",[234,3224,3226],{"className":1239,"code":3225,"language":1241,"meta":141,"style":141},"$ npm run build\n$ ./gradlew bootRun\n",[147,3227,3228,3232],{"__ignoreMap":141},[242,3229,3230],{"class":244,"line":245},[242,3231,2551],{},[242,3233,3234],{"class":244,"line":277},[242,3235,2556],{},[18,3237,3238,3239,452],{},"open your Browser and load ",[147,3240,2536],{},[18,3242,3243],{},"Just…",[18,3245,3246],{},"to see…",[18,3248,3249],{},"a wonderful stacktrace…",[234,3251,3253],{"className":1239,"code":3252,"language":1241,"meta":141,"style":141},"jdk.nashorn.internal.runtime.ECMAException: TypeError:\n[de.synyx...Product@553287f8, Product@65ae29e6] has no such function \"map\"\n at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]\n at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]\n\n",[147,3254,3255,3260,3265,3270,3275,3280],{"__ignoreMap":141},[242,3256,3257],{"class":244,"line":245},[242,3258,3259],{},"jdk.nashorn.internal.runtime.ECMAException: TypeError:\n",[242,3261,3262],{"class":244,"line":277},[242,3263,3264],{},"[de.synyx...Product@553287f8, Product@65ae29e6] has no such function \"map\"\n",[242,3266,3267],{"class":244,"line":305},[242,3268,3269],{}," at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]\n",[242,3271,3272],{"class":244,"line":327},[242,3273,3274],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]\n",[242,3276,3277],{"class":244,"line":359},[242,3278,3279],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]\n",[242,3281,3282],{"class":244,"line":365},[242,3283,3284],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]\n",[18,3286,3287,3288,3291,3292,3295],{},"The reason is that Nashorn interprets Java objects as, surprise, Java objects. As you remember, our ReactJS component\n",[147,3289,3290],{},"\u003CProductList />"," expects a list of products (actually a JavaScript array). But currently the type of products is a\n",[147,3293,3294],{},"java.util.List"," which doesn’t have the map method. Note the datatype of products in the image below.",[18,3297,3298],{},[139,3299],{"alt":3300,"src":3301},"nashorn-debugging","https://media.synyx.de/uploads//2016/03/nashorn-debugging.png",[2952,3303,3304],{},[18,3305,3306],{},"“Given a Java array or Collection, this function returns a JavaScript array with a shallow copy of its contents”",[18,3308,3309,3310,3312],{},"So our renderServer function defined in ",[147,3311,2487],{}," must be extended to:",[18,3314,3315],{},"Now we’re ready to go 🙂",[18,3317,3318,3319,3322,3323,3328],{},"Rebuild the frontend with ",[147,3320,3321],{},"npm run build",", restart the Spring Boot application, reload ",[89,3324,3325],{},[24,3326,2536],{"href":2536,"rel":3327},[28]," and\nadmire our awesome product list.",[18,3330,3331],{},[139,3332],{"alt":3333,"src":3334},"awesome-product-list-001","https://media.synyx.de/uploads//2016/03/awesome-product-list-001.png",[74,3336,2726],{"id":2725},[82,3338,3339,3342,3348,3356],{},[85,3340,3341],{},"using Nashorn is no rocket science",[85,3343,3344,3345,3347],{},"load js files via ",[147,3346,3039],{}," to enable debugging (at least in IntelliJ)",[85,3349,3350,3352,3353],{},[89,3351,3294],{}," must be converted to JavaScript array with ",[147,3354,3355],{},"Java.from",[85,3357,3358],{},"manually rebuilding and reloading the ReactJS app sucks (autoreload would be cool, right)",[18,3360,3361],{},[139,3362],{"alt":3363,"src":3364},"js-webpack-nashorn","https://media.synyx.de/uploads//2016/03/js-webpack-nashorn.png",[74,3366,2760],{"id":2759},[82,3368,3369,3372],{},[85,3370,3371],{},"using webpack to enhance developer experience",[85,3373,3374],{},"implementing the sorting feature",[18,3376,3377],{},[211,3378,2772],{},[763,3380,2775],{},{"title":141,"searchDepth":277,"depth":277,"links":3382},[3383,3384,3385,3386,3387],{"id":2397,"depth":277,"text":2398},{"id":2916,"depth":277,"text":2829},{"id":2933,"depth":277,"text":2835},{"id":2725,"depth":277,"text":2726},{"id":2759,"depth":277,"text":2760},[775],"2016-03-11T11:29:12","This is the first article of a series about server side rendering and progressive enhancement. We will implement a\\nproduct list that can be sorted by two parameters. Furthermore the app will be progressively enhanced, means the html\\ndocument is rendered on the server and javascript will just enhance the app on the client if possible.","https://synyx.de/blog/springboot-reactjs-server-side-rendering/",{},"/blog/springboot-reactjs-server-side-rendering",{"title":2803,"description":2812},"springboot-reactjs-server-side-rendering","blog/springboot-reactjs-server-side-rendering",[614,415,2795,2796,2797,2798],"This is the first article of a series about server side rendering and progressive enhancement. We will implement a product list that can be sorted by two parameters. Furthermore the…","NgbG5R1prPK9EUbjPI1FYf4uauo9uWfYO5zNB_kSUvk",{"id":3401,"title":3402,"author":3403,"body":3405,"category":3588,"date":3589,"description":3590,"extension":779,"link":3591,"meta":3592,"navigation":388,"path":3593,"seo":3594,"slug":3409,"stem":3596,"tags":3597,"teaser":3605,"__hash__":3606},"blog/blog/devoxx-poland-2015-summary.md","Devoxx Poland 2015 Summary",[3404],"szulc",{"type":11,"value":3406,"toc":3586},[3407,3410,3431,3437,3440,3445,3448,3467,3473,3478,3481,3484,3487,3493,3498,3501,3504,3510,3515,3518,3521,3526,3529,3532,3537,3540,3543,3546,3551,3554,3557,3560,3563,3566,3571,3574,3583],[14,3408,3402],{"id":3409},"devoxx-poland-2015-summary",[18,3411,3412,3413,3418,3419,3424,3425,3430],{},"So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\noutstanding beautiful city. It is for the first time, when ",[24,3414,3417],{"href":3415,"rel":3416},"http://devoxx.pl",[28],"Devoxx Poland"," (previously known\nas ",[24,3420,3423],{"href":3421,"rel":3422},"http://2014.33degree.org",[28],"33rd Degree","), one of the most recognizable European Java Conference took place in Krakow,\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\nWednesday last week in the ",[24,3426,3429],{"href":3427,"rel":3428},"http://www.icekrakow.pl/",[28],"ICE Conference Center",", which is located directly by the Vistula\nriver, with beautiful view over the Wawel Royal Castle.",[18,3432,3433],{},[139,3434],{"alt":3435,"src":3436},"\"Inside the ICE Conference Center \"","https://media.synyx.de/uploads//2015/06/IMG_20150624_110556.jpg",[18,3438,3439],{},"Now it’s time to write some short summary. Below is my personal list of conclusions and analysis about the current state\nof Java industry and where it is going to, based on that what I’ve heard and seen during the conference.",[18,3441,3442],{},[211,3443,3444],{},"1. The rise of Microservices",[18,3446,3447],{},"You might say – “yep, of course”. It is obvious for everyone who is following the development of Java & Web Ecosystem\nthat microservices are the hottest and fancies “new” “technology” of the year 2015. I counted that around 10 talks (\nfrom around 100 full-time presentations) were completely dedicated to them. Another couple or so discussed tools\nemerged to make them easier to monitor, deploy and deal with them. And nearly every other talk mentioned them.",[18,3449,3450,3451,3454,3455,3460,3461,3466],{},"I think we are in the peak phase. I remember two years ago at",[211,3452,3453],{},"GeeCON 2013","as I saw the\nfirst ",[24,3456,3459],{"href":3457,"rel":3458},"http://2013.geecon.org/speakers/sam-newman.html",[28],"talk"," about it. Now it explodes, and you could hear at Devoxx\neverything about it:from theory and principles, through architecture, best practices, tools supporting it, monitoring,\ndevops, to live coding demos. Most of this talks were very enthusiastic about it, although in the next few months I\nexpect some more skeptical or at least balanced talks. There was\nactually ",[24,3462,3465],{"href":3463,"rel":3464},"https://web.archive.org/web/20150503201315/http://cfp.devoxx.pl:80/2015/talk/MZA-9564/Modularity_in_post_microservice_world",[28],"one","\nlike this.",[18,3468,3469],{},[139,3470],{"alt":3471,"src":3472},"\"Microservices Live-Coding Demo\"","https://media.synyx.de/uploads//2015/06/IMG_20150623_154822.jpg",[18,3474,3475],{},[211,3476,3477],{},"2. Reactive and Resilient by default",[18,3479,3480],{},"The second most important topic at Devoxx was the resiliency and reactive programming, together with durability,\nasynchronous programming, circuit breaking and back pressure, which are all actually strongly connected with\nmicroservices.",[18,3482,3483],{},"Conclusion from the talks I have seen is quite obvious: if something can crash, it eventually will, most probably in the\nworst moment. That is why resiliency and recovery mechanisms are so important and should be not only a feature, but a\nmust, especially in the era of microservices.",[18,3485,3486],{},"The application (or actually the whole container or server) should be able to crash at any time and the supervisor\nshould take care of it. The other parts of the system should be able to continue their operation normally almost as if\nnothing had happened, circuit-breaking the failed system and using fall-back mechanisms, providing some simplified\ndata from other sources. And as soon the failed part of the system has been recovered, it should automatically back to\nnormal.",[18,3488,3489],{},[139,3490],{"alt":3491,"src":3492},"\"Main Room\"","https://media.synyx.de/uploads//2015/06/PANO_20150622_155838.jpg",[18,3494,3495],{},[211,3496,3497],{},"3. Functional Programming breaks (slowly) though",[18,3499,3500],{},"Third most popular and still very hot topic is functional programming (or rather, a soft of FP). Thank to lambda\nexpressions, streams API, CompletableFutur, (semi)closures and tools like RxJava programmers slowly adopt more and more\nfunctional concepts and start to think of computation rather as a pipeline processing of immutable data instead of\nthinking of it as a mutating of objects’ state.",[18,3502,3503],{},"Furthermore, more and more developers found it increasingly important to write the code in such a way that there is no\nmutable state, including code at the method-level. I guess this is also a side-effect of using the IoC Containers,\nwhere many objects are simply the global singletons, and introducing the state in it would case a serious performance\nproblems and bugs.",[18,3505,3506],{},[139,3507],{"alt":3508,"src":3509},"\"View over Wawel Castle from ICE Center\"","https://media.synyx.de/uploads//2015/06/PANO_20150624_174232.jpg",[18,3511,3512],{},[211,3513,3514],{},"4. Java 8 throttles the rise of the new languages",[18,3516,3517],{},"Two, three years ago, if you attend a Java conference, one of the most important thing which would be discussed was “Is\nthe Scala/Groovy/xyz the next Java?” or “What are the Java alternatives”. You won’t here it anymore. In some cases you\nwould hear that Scala/Groovy/xyz is better for implementing some kind of stuff like mathematical computations or data\npipeline processing or so. Or that some languages are better at concurrency and parallelism. Or that some languages are\nbetter for writing tests.",[18,3519,3520],{},"But there is no more doubt that Java will be soon replaced by other languages whatsoever, because with Java 8 and many (\nespecially “reactive”) mature libraries Java is simply good enough for most cases. At least for now.",[18,3522,3523],{},[211,3524,3525],{},"5. Java-Community focus on backend",[18,3527,3528],{},"Again, two, three years ago there very many talks about Mobile, Android, Web, Nodejs frameworks, new Javascript\nclient-side frameworks, and so called “Full Stack Developer”. In the past years on the java-Conference you can attend\nto many diverse talks – from Android Programming, through NodeJS and Javascript-Backend solutions and tool, to all the\nJavascript-Frontend stuff like AngularJS, EmberJS etc. It is not the case any more.",[18,3530,3531],{},"They all moved out to their own separate conferences, because no one doubts anymore that anyone can be a good Java (\nBackend) Developer, a good JS-Frontend Developer and Mobile Developer at once. There are simple to many problems to\nsolve on the “Backend”-Side, so one has to focus on Java-Code and (probably not so long and more) on “Data”.",[18,3533,3534],{},[211,3535,3536],{},"6. Big Data and NoSQL are here",[18,3538,3539],{},"Big Data and NoSQL were of course present, but it is not the same “Big Data” and the same NoSQL which it was for 3-4\nyears. Today we are not talking about what it is, what is the theory, etc. Today it is obvious for everyone, that there\nis nothing like “Big Data” and “NoSQL” databases.",[18,3541,3542],{},"There are only databases which better scale and handle some particular amount and type of data in particular\ncircumstances. That’s it, and the only thing we should do is learn how to recognize which data better fit to be stored\nin database X or Y, and how to use it properly. And the conference showed exactly this, that there are no SQL and NoSQL\ndatabases, but only that handle better data of the given characteristic.",[18,3544,3545],{},"There are simply databases which doing one thing better than another, for example better handle transactions and\nrelations between data, or scale very well but support no relations, or maybe are design to store and query text\ndocuments or graphs of objects.",[18,3547,3548],{},[211,3549,3550],{},"7. Spring is all what you need",[18,3552,3553],{},"It was probably for the first time I didn’t see any talk about alternative approach to Spring-Based Technologies (Java\nEE excluding).",[18,3555,3556],{},"This year there was nothing about any new or old frameworks. No Play Framework, dropwizard. No other smaller fancy\ntechnologies like RatPack or Spark Framework. Nothing. Zero. It has been always at least a couple talks about different\nframeworks and alternative approaches. Not this time. There wasn’t even Grails.",[18,3558,3559],{},"The same applies for the data-persistence solutions like ORMs for instance.",[18,3561,3562],{},"No new features in Hibernate, JPA. Nothing about jOOQ or myBatis/iBatis. Now it’s all about Spring Data. There is Spring\nData JPA, Spring Data MongoDB. Spring Data Neo4J, Cassandra, Redis, Elasticsearch, any more. There are also Spring\nsolutions for Big Data – Spring XD and for the microservices aka cloud stuff – Spring Cloud.",[18,3564,3565],{},"Spring is the only thing which you seems to need. Thus I’m waiting for the “Spring Developer” job titles instead of\n“Java Developer”, just like “SharePoint Developer” or “Liferay Developer” already.",[18,3567,3568],{},[211,3569,3570],{},"Conclusion",[18,3572,3573],{},"Despite of many new buzzwords and “new” technologies, there was not technological revolution, not even close. Maybe\nmicroservices will revolutionize the way we are designing systems but they will do it not because they are a\nrevolutionary technology, but rather by combining many other technologies together instead of introducing something what\nis really new.",[18,3575,3576,3577,3582],{},"I think it might be true that the Java Industry and the IT world in general is in\nthe",[24,3578,3581],{"href":3579,"rel":3580},"https://vimeo.com/130981099#t=2m56s",[28],"inflection point",". New technologies aren’t a game-changer and every new\ntechnology needs more and more time to spread across the industry, so it feels like a little bit stagnant.",[18,3584,3585],{},"I have such a feeling that we have now a little bit time to catch our breath, right after the Cloud, Mobile, Big Data,\nAsynchronous, Functional and DevOps era and to prepare ourselves for the Next Big Thing, which is probably waiting for\nus around the corner and will pop up in the least expected moment.",{"title":141,"searchDepth":277,"depth":277,"links":3587},[],[775],"2015-07-02T10:34:54","So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\\noutstanding beautiful city. It is for the first time, when Devoxx Poland (previously known\\nas 33rd Degree), one of the most recognizable European Java Conference took place in Krakow,\\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\\nWednesday last week in the ICE Conference Center, which is located directly by the Vistula\\nriver, with beautiful view over the Wawel Royal Castle.","https://synyx.de/blog/devoxx-poland-2015-summary/",{},"/blog/devoxx-poland-2015-summary",{"title":3402,"description":3595},"So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one\noutstanding beautiful city. It is for the first time, when Devoxx Poland (previously known\nas 33rd Degree), one of the most recognizable European Java Conference took place in Krakow,\nthe city of the polish kings, and one of the most important places in whole polish history. It took place from Monday to\nWednesday last week in the ICE Conference Center, which is located directly by the Vistula\nriver, with beautiful view over the Wawel Royal Castle.","blog/devoxx-poland-2015-summary",[3598,3599,3600,614,415,3601,3602,3603,3604,2797],"devoxx","devoxx-conference","devoxx-poland","microservices","reactive","resilent","rxjava","So that’s it. Three days, 2.000 Developers from 20 countries, over 140 speakers from around the world, and one outstanding beautiful city. It is for the first time, when Devoxx…","QWK3ox8PnfXTyuA0bTKcDvbny5tTEOTpJwT99Bdaw6g",{"id":3608,"title":3609,"author":3610,"body":3613,"category":3865,"date":3867,"description":3868,"extension":779,"link":3869,"meta":3870,"navigation":388,"path":3871,"seo":3872,"slug":3617,"stem":3874,"tags":3875,"teaser":3878,"__hash__":3879},"blog/blog/javascript-linting-tool-evaluation.md","Javascript Linting Tool Evaluation",[3611,3612],"mueller","schneider",{"type":11,"value":3614,"toc":3856},[3615,3618,3633,3658,3667,3670,3674,3742,3745,3748,3761,3776,3779,3796,3799,3802,3813,3817,3820,3823,3826,3837,3841,3844,3850,3853],[14,3616,3609],{"id":3617},"javascript-linting-tool-evaluation",[18,3619,3620,3621,3626,3627,3632],{},"In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\nlegendary ‘",[24,3622,3625],{"href":3623,"rel":3624},"http://www.javaposse.com",[28],"The Java Posse","‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\ndevelopment teams across ",[24,3628,3631],{"href":3629,"rel":3630},"http://www.synyx.de",[28],"synyx",", using it never felt 100% comfortable. A quick Google search left\nus with three alternatives:",[82,3634,3635,3643,3650],{},[85,3636,3637,3642],{},[24,3638,3641],{"href":3639,"rel":3640},"http://jslint.com",[28],"JSLint"," by Doug Crockford himself",[85,3644,3645],{},[24,3646,3649],{"href":3647,"rel":3648},"https://developers.google.com/closure/utilities/",[28],"Closure Linter by Google",[85,3651,3652,3657],{},[24,3653,3656],{"href":3654,"rel":3655},"http://eslint.org",[28],"ESLint",", the new kid on the block",[18,3659,3660,3661,3666],{},"…as well as ",[24,3662,3665],{"href":3663,"rel":3664},"http://jshint.com/",[28],"JSHint"," itself, of course.",[18,3668,3669],{},"We drew up a quick spreadsheet for evaluating the tools and came up with the following.",[74,3671,3673],{"id":3672},"criteria","Criteria",[82,3675,3676,3682,3688,3694,3700,3706,3712,3718,3724,3730,3736],{},[85,3677,3678,3681],{},[211,3679,3680],{},"Performance"," How long does it take to run over our example project, a single page webapp with a couple of thousands\nof JavaScript LOC?",[85,3683,3684,3687],{},[211,3685,3686],{},"Licensing"," Does the license meet our requirements (and those of our customers, of course)?",[85,3689,3690,3693],{},[211,3691,3692],{},"Project health/adoption"," How healthy is the project? Is it on Github, and is it well maintained?",[85,3695,3696,3699],{},[211,3697,3698],{},"Completeness of configurations"," Does the tool cover all our use-cases for a linting tool?",[85,3701,3702,3705],{},[211,3703,3704],{},"Productivity (rule set creation / project setup)"," When creating a new project, is it difficult to create a matching\nruleset? Does the tool come with a reasonable default rule set, or do you need to set up all the checks yourself?",[85,3707,3708,3711],{},[211,3709,3710],{},"Productivity (active software development)"," During active development, does the tool assist the developer in\nwriting quality code, or does it bully you to the point where you’d rather abolish using a linting tool at all?",[85,3713,3714,3717],{},[211,3715,3716],{},"Quality of Documentation/Tutorials/Self Help"," How good is the project documentation? When the tool breaks the build\nwith a certain error message, how difficult is it to find reliable information on the error in question (why does it\noccur, why is it a bad practice, how to fix it)?",[85,3719,3720,3723],{},[211,3721,3722],{},"Ability to integrate with existing projects"," Is it possible to integrate the linting tool in an existing projects\nwithout making changes to the project to comply to the rules?",[85,3725,3726,3729],{},[211,3727,3728],{},"Integration with build tool"," Is it possible to integrate the linting tool into your build chain to receive direct\nfeedback?",[85,3731,3732,3735],{},[211,3733,3734],{},"ES6 support"," How well does the project support future versions of the language?",[85,3737,3738,3741],{},[211,3739,3740],{},"Pluggable"," Is it possible to extend the given rule set with custom checks?",[74,3743,3665],{"id":3744},"jshint",[18,3746,3747],{},"The first tool we looked at was the already-familiar JSHint. We already knew what was bothering us about it:",[82,3749,3750,3758],{},[85,3751,3752,3753,3757],{},"Its hard to find the documentation for a certain error message. While the error messages itself are mostly\nself-explanatory, it can be somewhat difficult to find out how to deactivate or customize a certain rule. For\nexample, ",[24,3754,3755],{"href":3755,"rel":3756},"http://jshint.com/docs/options/",[28]," JSHint has both ‘enforcing’ and ‘relaxing’ rules. While setting a ‘enforcing’\nrule to true turns it on, setting a ‘relaxing’ rule to true deactivates it.",[85,3759,3760],{},"More often than not, using JSHint can be frustrating. For example, we had ‘maxdepth’",[18,3762,3763,3764,3767,3768,3771,3772,3775],{},"set to 3, meaning a maximum of three nested blocks of code was allowed. In case one of those blocks was a ‘",[89,3765,3766],{},"for … in","‘\nstatement, JSHint would (correctly) complain that its body should be wrapped in an ‘",[89,3769,3770],{},"if(obj.hasOwnProperty(key))","\n‘-check to filter out unwanted properties. However, doing so meant introducing another nested block, and if that pushed\nthe total depth beyond ‘",[89,3773,3774],{},"maxdepth","‘, JSHint would fail the build. The solution was usually to introduce private helper\nfunctions, which can make otherwise trivial code difficult to read (since you have to skip blocks of code).",[18,3777,3778],{},"Of course, that is not really the fault of JSHint (seeing that it only did what it was told to do), but it was a rather\nbig annoyance that caused us to re-evaluate our JavaScript linting practices in the first place.",[82,3780,3781],{},[85,3782,3783,3784,3789,3790,3795],{},"Being a fork of JSLint, JSHint has the same license containing the\ninfamous ",[24,3785,3788],{"href":3786,"rel":3787,"title":3788},"http://en.wikipedia.org/wiki/JSLint#License",[28],"JSLint License"," ‘Good, not evil’ statement.\nWhile we understand its humorous intent (and being\na ",[24,3791,3794],{"href":3792,"rel":3793,"title":3794},"https://synyx.de/unternehmen/verantwortung_csr/",[28],"fairly social responsible company",",\nwe wholeheartedly support it), we were worried that some corporate lawyer might not approve of our use of a tool bound\nto such a license.",[74,3797,3641],{"id":3798},"jslint",[18,3800,3801],{},"After JSHint we decided to evaluate the old guy in the gang, JSLint. It was the first linting tool for JavaScript, and\nit feels like that. From our opinion JSLint has two major problems, besides the license (see JSHint):",[82,3803,3804,3807,3810],{},[85,3805,3806],{},"The website of JSLint is very old-school and does not contain any explanations of the ruleset or any information to\nget a link between the error messages provided by JSLint and the problem in the code. So you have to search through\nthe internet to find any third-party explanation that will help you to fix the problem.",[85,3808,3809],{},"Some of the rules of JSLint are, at least, strangely named. There is a rule to forbid (or allow, if deactivated)\n‘stupidity’. The project’s web page does not provide any explanation (again) – resorting to Google, we found out that\n‘stupidity’ referred to the usage of synchronous functions in Node.js’s file system module.",[85,3811,3812],{},"The rule set is very strict and, when you look through the GitHub issues and pull requests, it is very hard to\nparticipate in the JSLint project. That’s why the community is very small and there are a lot more people active in\nJSHint and other projects and bring in their ideas there. Maybe that is why JSLint does not provide a rule similar to\n‘latedef’ from JSHint or ‘no-use-before-define’ from ESLint. Without this rule it is very hard to structure your\ncode with private named-functions at the end without assigning the function to a variable at the start (and that\nwould not be what we want).",[74,3814,3816],{"id":3815},"closure-linter","Closure Linter",[18,3818,3819],{},"The Closure Linter is part of Google’s Closure tool set. It was designed for internal use and provides very little\noptions for customization. Since following the rules enforced by it seems to be mandatory within Google, that is\ncertainly an acceptable practice. However, since it is (for example) not possible to change the default maximum line\nlength of 80 characters, we quickly decided not to look into the tool any more.",[74,3821,3656],{"id":3822},"eslint",[18,3824,3825],{},"When looking at ESLint, we were quick to decide that we might be looking at a potential winner:",[82,3827,3828,3831,3834],{},[85,3829,3830],{},"While the project is (by far) the youngest (or as others might put it: the least mature) of the four tools we looked\nat, it is also the best maintained. 100+ contributors on Github and a roughly monthly release schedule speak for\nthemselves.",[85,3832,3833],{},"Applying it to our example web app, we were surprised to find out that it was sufficient to write about ten lines of\nconfiguration to perform the same amount of checks that required around a hundred lines in JSHint. Of course, that\nmight only mean that we have the same idea of quality JavaScript code as the tool’s authors, but nevertheless it meant\nthat the tool would be quite easy to adopt into our development process.",[85,3835,3836],{},"The output is quite handy: It prints both a one-line human readable error message as well as an error code for a\nquick lookup in the documentation.",[74,3838,3840],{"id":3839},"evaluation","Evaluation",[18,3842,3843],{},"The criteria from above has been weighted from five to 15, from not important to important, and the tools got a 0, 0.5\nor 1 if it does not, almost or absolute fulfill the criterion.",[18,3845,3846],{},[139,3847],{"alt":3848,"src":3849},"\"jsLinting\"","https://media.synyx.de/uploads//2015/02/jsLinting2.png",[74,3851,3570],{"id":3852},"conclusion",[18,3854,3855],{},"As you can see in the image above, ESLint proved to be the winner of our evaluation. We decided that its major flaw, the\npotential immaturity, was acceptable to us since by its nature, it would only be used during (internal) development. The\nease of use, both because of the robust and reasonable default rule set and the high-quality documentation, outweighed\nany concern by far. We are looking forward to adopt ESLint into our development tool chain over the coming weeks and\nmonths!",{"title":141,"searchDepth":277,"depth":277,"links":3857},[3858,3859,3860,3861,3862,3863,3864],{"id":3672,"depth":277,"text":3673},{"id":3744,"depth":277,"text":3665},{"id":3798,"depth":277,"text":3641},{"id":3815,"depth":277,"text":3816},{"id":3822,"depth":277,"text":3656},{"id":3839,"depth":277,"text":3840},{"id":3852,"depth":277,"text":3570},[775,3866],"open-source-blog","2015-02-03T09:45:38","In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\\nlegendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\\ndevelopment teams across synyx, using it never felt 100% comfortable. A quick Google search left\\nus with three alternatives:","https://synyx.de/blog/javascript-linting-tool-evaluation/",{},"/blog/javascript-linting-tool-evaluation",{"title":3609,"description":3873},"In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the\nlegendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate\nalternatives to our current JavaScript linting standart, JSHint. Although well established by now among different\ndevelopment teams across synyx, using it never felt 100% comfortable. A quick Google search left\nus with three alternatives:","blog/javascript-linting-tool-evaluation",[787,3822,3839,415,1050,3744,3798,3876,3877],"linting","software-development","In our internal JavaScript ‘User Group’ (called JS-Posse in honour of the legendary ‘The Java Posse‘ by Dick Wall, Chet Haase et al.), we recently decided to evaluate alternatives to…","QcXdToJb-JTf6znqD4o0GbdwGfu2XcNA6y-tOvH75ck",{"id":3881,"title":3882,"author":3883,"body":3885,"category":4207,"date":4208,"description":4209,"extension":779,"link":4210,"meta":4211,"navigation":388,"path":4212,"seo":4213,"slug":3889,"stem":4214,"tags":4215,"teaser":4222,"__hash__":4223},"blog/blog/the-qt-framework-solid-fun-in-many-languages.md","The Qt framework: solid fun in many languages",[3884],"posch",{"type":11,"value":3886,"toc":4205},[3887,3890,3893,3913,3919,3922,3925,3929,3932,3940,3946,3949,3957,3960,3964,3967,3975,3981,3984,3992,3995,3998,4090,4098,4102,4110,4113,4122,4131,4140,4149,4158,4167,4176,4185,4194,4203],[14,3888,3882],{"id":3889},"the-qt-framework-solid-fun-in-many-languages",[18,3891,3892],{},"Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities\nit’s one of the most-used frameworks for application development. For those who don’t know what Qt is or what it does:\nit’s a comprehensive LGPL-licensed framework providing cross-platform support for GUI, network, multimedia, database,\nsensors, graphics (OpenGL) and many other features. In this article I would like to give a quick overview of these.",[18,3894,3895,3896,1402,3899,1402,3903,3907,3908,3912],{},"While written in C++, Qt has many language bindings",[24,3897,1594],{"href":3898},"#sdfootnote1sym",[24,3900,3902],{"href":3901},"#sdfootnote2sym","2",[24,3904,3906],{"href":3905},"#sdfootnote3sym","3",",\nincluding for Python, Perl, Ada, Ruby, Java, BASIC, Go, C#, PHP, Lua and Haskell. Any application written in any of\nthese languages and using the Qt framework can be deployed unmodified on any of the supported\nplatforms",[24,3909,3911],{"href":3910},"#sdfootnote4sym","4"," – including all major desktop and mobile platforms – which makes it a popular framework\nfor many big organizations and companies. Some well-known applications written using Qt include Autodesk Maya, Altera\nQuartus, KDE, Google Earth, Skype, Spotify (Linux), Virtualbox and VLC.",[18,3914,3915],{},[139,3916],{"alt":3917,"src":3918},"\"qt_imagecomposer_qt-creator\"","https://media.synyx.de/uploads//2014/09/qt_imagecomposer_qt-creator.jpg",[18,3920,3921],{},"Screenshot 1: Image Composition sample application running on top of Qt Creator IDE.",[18,3923,3924],{},"In addition to the straight Qt framework there is also the Qt Modeling Language (QML) component which can be used to\nrapidly create user interface-centric applications in a JavaScript-based, declarative language. It’s commonly used for\nmobile and embedded applications. A basic QML application can be enhanced using JavaScript code and feature anything\nfrom UI controls to a complete web browser widget (using the WebKit-based module).",[14,3926,3928],{"id":3927},"getting-started","Getting started",[18,3930,3931],{},"When I started using Qt in 2010 Qt 4.7 was the standard. Since then Qt has grown into its current form at version 5.3,\nwith a strong focus on JavaScript and mobile development (using the Qt Quick module, which defines QML), while the\noriginal C++ API also got a makeover. This didn’t change any fundamentals, however, mostly improving library\norganization and features such as accessibility in GUIs.",[18,3933,3934,3935,3939],{},"To quickly build a GUI application, one can use the provided Qt Creator IDE, which includes all of the tools to make any\ntype of application, including non-Qt-based ones. If one wanted to for example create a browser using the Webkit\nbrowser engine, a single class implementation would suffice, as in Qt’s Fancy Browser example",[24,3936,3938],{"href":3937},"#sdfootnote5sym","5",", which\ngoes one step further and even loads a JQuery instance into the JavaScript runtime to perform HTML manipulation.",[18,3941,3942],{},[139,3943],{"alt":3944,"src":3945},"\"qt_fancybrowser\"","https://media.synyx.de/uploads//2014/09/qt_fancybrowser.jpg",[18,3947,3948],{},"Screenshot 2: Fancy Browser example application.",[18,3950,3951,3952,3956],{},"For a hobby project I took this basic concept and made a more full-featured browser",[24,3953,3955],{"href":3954},"#sdfootnote6sym","6",", writing a\ncustom cookie handler among other extensions to the basic Qt classes. With the foundation Qt provides it’s very easy to\nrapidly get started on a project, or to quickly prototype a concept without wasting hours on implementation details.",[18,3958,3959],{},"Whether one uses C++, Python, Ada or another language for which a complete wrapper exists, the basic principle doesn’t\nchange in implementing a Qt-based application. One always uses the same API and same concepts, just molded to fit the\nimplementing language.",[14,3961,3963],{"id":3962},"enter-qml","Enter QML",[18,3965,3966],{},"Even to long-time users of C++/Qt QML can seem quite confusing at first, mostly because of the confusion over what\nQML is and isn’t. In essence QML (Qt Modeling Language) is the name of the modeling language: a descriptive language\nusing which one can define user interface elements and their behavior. QML is part of Qt Quick, the UI creation kit\nwhich itself is part of the Qt framework. Finally, the runtime for QML is called Qt Declarative.",[18,3968,3969,3970,3974],{},"Places where QML is used include (outside of mobile/embedded) KDE and the Unity UI (as of version 8",[24,3971,3973],{"href":3972},"#sdfootnote7sym","7",")\nwhich is used by Ubuntu. The main motivations behind the use of a QML-based UI seem to revolve around the language and\nplatform agnostic nature of it. All one needs is the QML runtime whereby one can add JavaScript and C++ code for\nfurther functionality. Unity 8 uses QML to ease the cross-platform deployment across desktop and mobile devices (\nrunning Ubuntu Touch).",[18,3976,3977],{},[139,3978],{"alt":3979,"src":3980},"\"Qt PhotoViewer sample\"","https://media.synyx.de/uploads//2014/09/qt_photo_viewer.jpg",[18,3982,3983],{},"Screenshot 3: Photo Viewer example. QML with minimal JavaScript.",[18,3985,3986,3987,3991],{},"The Photo Viewer QML example application",[24,3988,3990],{"href":3989},"#sdfootnote8sym","8"," on the Qt site is a good example of how much one can do\nwith just QML: this application allows one to define all views of the application with transitions, widgets and the\nXML-based model which retrieves image URLs from the Flickr public API. The JavaScript file is just used for some minor\nutility functions.",[18,3993,3994],{},"In theory one could extend the JavaScript side to include more or additional logic, and use a C++ extension for\nexample image processing or similar functionality. Where one puts the logic and which features are included would be\ndetermined by the available resources and intended languages. One can also use QML with just C++, or pure QML with no\nadditional languages. Many QML applications can be readily deployed on a mobile device as well.",[18,3996,3997],{},"QML isn’t just about static content either. Using Qt’s multimedia features one can for example quickly set up a video\nplayer:",[234,3999,4001],{"className":1239,"code":4000,"language":1241,"meta":141,"style":141},"import QtQuick 2.0\nimport QtMultimedia 5.0\nVideo {\n id: video\n width : 800\n height : 600\n source: \"video.avi\"\n \u003Ca class=\"broken_link\" href=\"http://qt-project.org/doc/qt-5/qml-qtquick-mousearea.html\">MouseArea\u003C/a> {\n anchors.fill: parent\n onClicked: {\n video.play()\n }\n }\n focus: true\n Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()\n Keys.onLeftPressed: video.seek(video.position - 5000)\n Keys.onRightPressed: video.seek(video.position + 5000)\n}\n",[147,4002,4003,4008,4013,4018,4023,4028,4033,4038,4043,4048,4053,4058,4062,4066,4071,4076,4081,4086],{"__ignoreMap":141},[242,4004,4005],{"class":244,"line":245},[242,4006,4007],{},"import QtQuick 2.0\n",[242,4009,4010],{"class":244,"line":277},[242,4011,4012],{},"import QtMultimedia 5.0\n",[242,4014,4015],{"class":244,"line":305},[242,4016,4017],{},"Video {\n",[242,4019,4020],{"class":244,"line":327},[242,4021,4022],{}," id: video\n",[242,4024,4025],{"class":244,"line":359},[242,4026,4027],{}," width : 800\n",[242,4029,4030],{"class":244,"line":365},[242,4031,4032],{}," height : 600\n",[242,4034,4035],{"class":244,"line":375},[242,4036,4037],{}," source: \"video.avi\"\n",[242,4039,4040],{"class":244,"line":385},[242,4041,4042],{}," \u003Ca class=\"broken_link\" href=\"http://qt-project.org/doc/qt-5/qml-qtquick-mousearea.html\">MouseArea\u003C/a> {\n",[242,4044,4045],{"class":244,"line":392},[242,4046,4047],{}," anchors.fill: parent\n",[242,4049,4050],{"class":244,"line":550},[242,4051,4052],{}," onClicked: {\n",[242,4054,4055],{"class":244,"line":555},[242,4056,4057],{}," video.play()\n",[242,4059,4060],{"class":244,"line":571},[242,4061,1886],{},[242,4063,4064],{"class":244,"line":583},[242,4065,685],{},[242,4067,4068],{"class":244,"line":682},[242,4069,4070],{}," focus: true\n",[242,4072,4073],{"class":244,"line":688},[242,4074,4075],{}," Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()\n",[242,4077,4078],{"class":244,"line":693},[242,4079,4080],{}," Keys.onLeftPressed: video.seek(video.position - 5000)\n",[242,4082,4083],{"class":244,"line":699},[242,4084,4085],{}," Keys.onRightPressed: video.seek(video.position + 5000)\n",[242,4087,4088],{"class":244,"line":2056},[242,4089,547],{},[18,4091,4092,4093,4097],{},"This sample, taken from the Qt Video QML type documentation",[24,4094,4096],{"href":4095},"#sdfootnote9sym","9"," shows just how easy it is to set up a\nresponsive user interface with QML and to add elements which not only respond to user inputs, but can use video and\naudio as well.",[14,4099,4101],{"id":4100},"wrapping-up","Wrapping up",[18,4103,4104,4105,4109],{},"This article has barely scratched the surface of what Qt is capable of. The multi-threading, networking, multimedia,\ngraphics acceleration, storage-related and many other features are at least as interesting. Using the many sample\napplications on the Qt site",[24,4106,4108],{"href":4107},"#sdfootnote10sym","10"," it’s easy to get an idea of the possibilities, however. Simply\ndownload the current version of the libraries together with Qt Creator and browse through the examples in the Welcome\ntab of the IDE, or check them out online.",[18,4111,4112],{},"Finally, if anyone reading has experience with any of the language wrappers for Qt, please leave a comment. I’d be very\ninterested in hearing how well they work.",[18,4114,4115,4118],{},[24,4116,1594],{"href":4117},"#sdfootnote1anc",[24,4119,4120],{"href":4120,"rel":4121},"http://qt-project.org/wiki/Category:LanguageBindings",[28],[18,4123,4124,4127],{},[24,4125,3902],{"href":4126},"#sdfootnote2anc",[24,4128,4129],{"href":4129,"rel":4130},"http://en.wikipedia.org/wiki/List_of_language_bindings_for_Qt_4",[28],[18,4132,4133,4136],{},[24,4134,3906],{"href":4135},"#sdfootnote3anc",[24,4137,4138],{"href":4138,"rel":4139},"http://en.wikipedia.org/wiki/List_of_language_bindings_for_Qt_5",[28],[18,4141,4142,4145],{},[24,4143,3911],{"href":4144},"#sdfootnote4anc",[24,4146,4147],{"href":4147,"rel":4148},"http://qt-project.org/doc/qt-5/supported-platforms.html",[28],[18,4150,4151,4154],{},[24,4152,3938],{"href":4153},"#sdfootnote5anc",[24,4155,4156],{"href":4156,"rel":4157},"http://qt-project.org/doc/qt-5/qtwebkitexamples-webkitwidgets-fancybrowser-example.html",[28],[18,4159,4160,4163],{},[24,4161,3955],{"href":4162},"#sdfootnote6anc",[24,4164,4165],{"href":4165,"rel":4166},"http://mayaposch.com/wildfox.php",[28],[18,4168,4169,4172],{},[24,4170,3973],{"href":4171},"#sdfootnote7anc",[24,4173,4174],{"href":4174,"rel":4175},"https://unity.ubuntu.com/getinvolved/development/unity8/",[28],[18,4177,4178,4181],{},[24,4179,3990],{"href":4180},"#sdfootnote8anc",[24,4182,4183],{"href":4183,"rel":4184},"http://qt-project.org/doc/qt-5/qtquick-demos-photoviewer-example.html",[28],[18,4186,4187,4190],{},[24,4188,4096],{"href":4189},"#sdfootnote9anc",[24,4191,4192],{"href":4192,"rel":4193},"http://qt-project.org/doc/qt-5/qml-qtmultimedia-video.html",[28],[18,4195,4196,4199],{},[24,4197,4108],{"href":4198},"#sdfootnote10anc",[24,4200,4201],{"href":4201,"rel":4202},"http://qt-project.org/",[28],[763,4204,2775],{},{"title":141,"searchDepth":277,"depth":277,"links":4206},[],[775],"2014-09-18T17:15:03","Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities\\nit’s one of the most-used frameworks for application development. For those who don’t know what Qt is or what it does:\\nit’s a comprehensive LGPL-licensed framework providing cross-platform support for GUI, network, multimedia, database,\\nsensors, graphics (OpenGL) and many other features. In this article I would like to give a quick overview of these.","https://synyx.de/blog/the-qt-framework-solid-fun-in-many-languages/",{},"/blog/the-qt-framework-solid-fun-in-many-languages",{"title":3882,"description":3892},"blog/the-qt-framework-solid-fun-in-many-languages",[4216,4217,415,4218,4219,4220,4221],"c","cross-platform","mobile","qml","qt","qt-quick","Particularly to people using C++ and Python the Qt framework is probably quite well-known, as in these communities it’s one of the most-used frameworks for application development. For those who…","pEQ_OlddqNbA7CWP-ltLaMJp7EGQfBl6i6zBkOADnbs",{"id":4225,"title":4226,"author":4227,"body":4229,"category":5344,"date":5345,"description":141,"extension":779,"link":5346,"meta":5347,"navigation":388,"path":5348,"seo":5349,"slug":4233,"stem":5350,"tags":5351,"teaser":5358,"__hash__":5359},"blog/blog/code-gluse.md","Code gluse",[4228],"clausen",{"type":11,"value":4230,"toc":5338},[4231,4234,4237,4247,4250,4253,4256,4262,4272,4279,4283,4286,4295,4367,4373,4376,4404,4411,4463,4466,4660,4663,4666,4670,4673,4690,4696,4699,4709,4712,4715,4722,4729,4759,4762,4765,4798,4801,4804,4858,4865,4871,4874,4877,4974,4976,4980,4989,4994,5008,5011,5022,5029,5031,5054,5060,5088,5200,5203,5206,5209,5216,5219,5222,5225,5228,5335],[14,4232,4226],{"id":4233},"code-gluse",[716,4235,4226],{"id":4236},"code-gluse-1",[18,4238,4239,4240,4243,4244,4246],{},"Today’s post targets an API, which has been released on Dec. 11, 2006; the ",[147,4241,4242],{},"javax.scripting"," package ",[242,4245,1594],{}," and a lot of\ngood articles that have been written around it.",[18,4248,4249],{},"The intention for this post is not about ‘how to use the scripting packaging’, but about gluse. So what do I mean with\nthe phrase gluse? Gluse is a coinage",[18,4251,4252],{},"for glue and (re)usage. As many of the Java developer know about the plenty of good libraries from maven central /\ngithub and the integration process, a few of them",[18,4254,4255],{},"might ask how to integrate libraries from other languages as well. As many of the every day problems have already bean\naddressed, there is a good chance that someone else has done the job for you and is willing to share.",[18,4257,4258,4259,4261],{},"Sometimes it’s written in pure Java, sometimes in a different language. Let’s see how to integrate the latter\nlibraries. (StackOverflow lists a dozen ",[242,4260,3902],{}," btw.)",[18,4263,4264,4265,4268,4269,4271],{},"The next parts will give you some information in form of three examples. The first and second example will address\nJavascript, as Javascript is getting more and more into the focus of developers and Oracle will ship their new engine\n‘Nashorn’ with the next Java 8 release, while the third example will target a more complex example using JRuby. ",[211,4266,4267],{},"All\nexamples"," can be downloaded from ",[242,4270,3906],{},". So it’s up to you if you want to read the sources in parallel, afterwards, or by\nplaying with the code in your IDE instantly.",[18,4273,4274,4275,4278],{},"All code examples have been written to be compatible with Java 1.6. See the note in example two, when it comes to the\n",[147,4276,4277],{},"bind"," function.",[716,4280,4282],{"id":4281},"proxy","Proxy",[18,4284,4285],{},"Lets discuss the first example: We want to replace parts of a string using a regular expression, but hook into the\nprocess of manipulating the matching elements, before the replacement eventually takes place – by adding some new\ncontent or returning something completely different.",[18,4287,4288,4289,4292,4293,452],{},"In Java you would probably end up using the ",[147,4290,4291],{},"java.util.regex.Pattern",", creating a matcher and iterating over the matched\ngroups and so on. There’s nothing wrong about it, but Javascript already defines that kind of behaviour ",[242,4294,3911],{},[234,4296,4298],{"className":413,"code":4297,"language":415,"meta":141,"style":141},"\"first 000 second\".replace(/[a-zA-Z]+/g, function (match) {\n return \"[\" + match.toUpperCase() + \"]\";\n});\n",[147,4299,4300,4337,4363],{"__ignoreMap":141},[242,4301,4302,4305,4307,4310,4312,4315,4318,4321,4323,4326,4328,4330,4332,4335],{"class":244,"line":245},[242,4303,4304],{"class":262},"\"first 000 second\"",[242,4306,452],{"class":248},[242,4308,4309],{"class":255},"replace",[242,4311,458],{"class":248},[242,4313,4314],{"class":262},"/",[242,4316,4317],{"class":448},"[a-zA-Z]",[242,4319,4320],{"class":422},"+",[242,4322,4314],{"class":262},[242,4324,4325],{"class":422},"g",[242,4327,464],{"class":248},[242,4329,1067],{"class":422},[242,4331,470],{"class":248},[242,4333,4334],{"class":473},"match",[242,4336,1199],{"class":248},[242,4338,4339,4341,4344,4347,4350,4353,4356,4358,4361],{"class":244,"line":277},[242,4340,1364],{"class":422},[242,4342,4343],{"class":262}," \"[\"",[242,4345,4346],{"class":422}," +",[242,4348,4349],{"class":248}," match.",[242,4351,4352],{"class":255},"toUpperCase",[242,4354,4355],{"class":248},"() ",[242,4357,4320],{"class":422},[242,4359,4360],{"class":262}," \"]\"",[242,4362,1111],{"class":248},[242,4364,4365],{"class":244,"line":305},[242,4366,586],{"class":248},[18,4368,4369,4370,4372],{},"As both, Rhino and Nashorn, support the javax.script.Invocable type, we will create an interface to address the\nproblem – you’ll find the whole documentation in the mentioned project ",[242,4371,3906],{},", but for the sake of completeness:",[18,4374,4375],{},"Apply the ‘pattern’ on the ‘sequence’ and call the ‘callback’ on each matched element. Either on ‘all’ matching\nelements, or on ‘any’ (first makes sense here).",[234,4377,4379],{"className":612,"code":4378,"language":614,"meta":141,"style":141},"\n public interface Replacement {\n public abstract CharSequence any (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n public abstract CharSequence all (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n }\n\n",[147,4380,4381,4385,4390,4395,4400],{"__ignoreMap":141},[242,4382,4383],{"class":244,"line":245},[242,4384,389],{"emptyLinePlaceholder":388},[242,4386,4387],{"class":244,"line":277},[242,4388,4389],{}," public interface Replacement {\n",[242,4391,4392],{"class":244,"line":305},[242,4393,4394],{}," public abstract CharSequence any (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n",[242,4396,4397],{"class":244,"line":327},[242,4398,4399],{}," public abstract CharSequence all (Pattern pattern, CharSequence sequence, Function\u003CCharSequence, CharSequence> callback);\n",[242,4401,4402],{"class":244,"line":359},[242,4403,685],{},[18,4405,4406,4407,4410],{},"The final Java code would look like the following (Java 8 users will flavour the new lambda syntax:\n",[147,4408,4409],{},"(match) -> { return \"[\" + match + \"]\"; }","):",[234,4412,4414],{"className":612,"code":4413,"language":614,"meta":141,"style":141},"\n Replacement replacement;\n replacement = replacement ();\n CharSequence enclosed = replacement.all (Pattern.compile (\"\\\\d+\"), \"could you please enclose 1234, 789, 345 with brackets?\", new Function\u003CCharSequence, CharSequence> () {\n @Override\n public CharSequence apply (CharSequence sequence) {\n return \"[\" + sequence + \"]\";\n }\n });\n /* replacement () returns a proxy of the type Replacement, using the shipped js scripting engine. the evaluated script returns an instance, which can be encapsulated using the Invocable#getInterface signature */\n\n",[147,4415,4416,4420,4425,4430,4435,4440,4445,4450,4454,4458],{"__ignoreMap":141},[242,4417,4418],{"class":244,"line":245},[242,4419,389],{"emptyLinePlaceholder":388},[242,4421,4422],{"class":244,"line":277},[242,4423,4424],{}," Replacement replacement;\n",[242,4426,4427],{"class":244,"line":305},[242,4428,4429],{}," replacement = replacement ();\n",[242,4431,4432],{"class":244,"line":327},[242,4433,4434],{}," CharSequence enclosed = replacement.all (Pattern.compile (\"\\\\d+\"), \"could you please enclose 1234, 789, 345 with brackets?\", new Function\u003CCharSequence, CharSequence> () {\n",[242,4436,4437],{"class":244,"line":359},[242,4438,4439],{}," @Override\n",[242,4441,4442],{"class":244,"line":365},[242,4443,4444],{}," public CharSequence apply (CharSequence sequence) {\n",[242,4446,4447],{"class":244,"line":375},[242,4448,4449],{}," return \"[\" + sequence + \"]\";\n",[242,4451,4452],{"class":244,"line":385},[242,4453,1886],{},[242,4455,4456],{"class":244,"line":392},[242,4457,537],{},[242,4459,4460],{"class":244,"line":550},[242,4461,4462],{}," /* replacement () returns a proxy of the type Replacement, using the shipped js scripting engine. the evaluated script returns an instance, which can be encapsulated using the Invocable#getInterface signature */\n",[18,4464,4465],{},"The Javascript implementation would look like:",[234,4467,4469],{"className":413,"code":4468,"language":415,"meta":141,"style":141},"\n(function () {\n function replace (regex, content, callback) {\n ...\n }\n var Replacement = function () {};\n Replacement.prototype.any = function (regex, content, callback) {\n return replace (new RegExp (regex), content, callback);\n };\n Replacement.prototype.all = function (regex, content, callback) {\n return replace (new RegExp (regex, 'g'), content, callback);\n };\n return new Replacement ();\n}) ();\n\n",[147,4470,4471,4475,4483,4507,4512,4516,4531,4564,4582,4587,4618,4639,4643,4655],{"__ignoreMap":141},[242,4472,4473],{"class":244,"line":245},[242,4474,389],{"emptyLinePlaceholder":388},[242,4476,4477,4479,4481],{"class":244,"line":277},[242,4478,458],{"class":248},[242,4480,1067],{"class":422},[242,4482,1070],{"class":248},[242,4484,4485,4488,4491,4493,4496,4498,4500,4502,4505],{"class":244,"line":305},[242,4486,4487],{"class":422}," function",[242,4489,4490],{"class":255}," replace",[242,4492,470],{"class":248},[242,4494,4495],{"class":473},"regex",[242,4497,464],{"class":248},[242,4499,3093],{"class":473},[242,4501,464],{"class":248},[242,4503,4504],{"class":473},"callback",[242,4506,1199],{"class":248},[242,4508,4509],{"class":244,"line":327},[242,4510,4511],{"class":422}," ...\n",[242,4513,4514],{"class":244,"line":359},[242,4515,685],{"class":248},[242,4517,4518,4521,4524,4526,4528],{"class":244,"line":365},[242,4519,4520],{"class":422}," var",[242,4522,4523],{"class":255}," Replacement",[242,4525,1296],{"class":422},[242,4527,1299],{"class":422},[242,4529,4530],{"class":248}," () {};\n",[242,4532,4533,4536,4538,4541,4543,4546,4548,4550,4552,4554,4556,4558,4560,4562],{"class":244,"line":375},[242,4534,4535],{"class":448}," Replacement",[242,4537,452],{"class":248},[242,4539,4540],{"class":448},"prototype",[242,4542,452],{"class":248},[242,4544,4545],{"class":255},"any",[242,4547,1296],{"class":422},[242,4549,1299],{"class":422},[242,4551,470],{"class":248},[242,4553,4495],{"class":473},[242,4555,464],{"class":248},[242,4557,3093],{"class":473},[242,4559,464],{"class":248},[242,4561,4504],{"class":473},[242,4563,1199],{"class":248},[242,4565,4566,4569,4571,4573,4576,4579],{"class":244,"line":385},[242,4567,4568],{"class":422}," return",[242,4570,4490],{"class":255},[242,4572,470],{"class":248},[242,4574,4575],{"class":422},"new",[242,4577,4578],{"class":255}," RegExp",[242,4580,4581],{"class":248}," (regex), content, callback);\n",[242,4583,4584],{"class":244,"line":392},[242,4585,4586],{"class":248}," };\n",[242,4588,4589,4591,4593,4595,4597,4600,4602,4604,4606,4608,4610,4612,4614,4616],{"class":244,"line":550},[242,4590,4535],{"class":448},[242,4592,452],{"class":248},[242,4594,4540],{"class":448},[242,4596,452],{"class":248},[242,4598,4599],{"class":255},"all",[242,4601,1296],{"class":422},[242,4603,1299],{"class":422},[242,4605,470],{"class":248},[242,4607,4495],{"class":473},[242,4609,464],{"class":248},[242,4611,3093],{"class":473},[242,4613,464],{"class":248},[242,4615,4504],{"class":473},[242,4617,1199],{"class":248},[242,4619,4620,4622,4624,4626,4628,4630,4633,4636],{"class":244,"line":555},[242,4621,4568],{"class":422},[242,4623,4490],{"class":255},[242,4625,470],{"class":248},[242,4627,4575],{"class":422},[242,4629,4578],{"class":255},[242,4631,4632],{"class":248}," (regex, ",[242,4634,4635],{"class":262},"'g'",[242,4637,4638],{"class":248},"), content, callback);\n",[242,4640,4641],{"class":244,"line":571},[242,4642,4586],{"class":248},[242,4644,4645,4647,4650,4652],{"class":244,"line":583},[242,4646,1132],{"class":422},[242,4648,4649],{"class":422}," new",[242,4651,4523],{"class":255},[242,4653,4654],{"class":248}," ();\n",[242,4656,4657],{"class":244,"line":682},[242,4658,4659],{"class":248},"}) ();\n",[18,4661,4662],{},"The Java code for this example would probably be less – measured in LOC – but the basic steps needed for an integration\ncan be shown pretty good and two worlds might benefit from your certainly approved works 🙂",[18,4664,4665],{},"One nice feature about this kind of mechanism is, that you can quickly prototype your functionality, while still able to\nchange parts of the implementation using pure Java afterwards.",[716,4667,4669],{"id":4668},"modularity","Modularity",[18,4671,4672],{},"Let’s come to the second example. You may have written a bunch of Javascript files in a modular way or just don’t want\nto put everything in a single file. While the first example showed how to proxy your implementation, the second example\nwill show you a basic approach for dynamically loading further resource and/or code files. The following signature\nshould be provided and accessible from all scripts.",[234,4674,4676],{"className":413,"code":4675,"language":415,"meta":141,"style":141},"require(\"org.geonames.reverse\");\n",[147,4677,4678],{"__ignoreMap":141},[242,4679,4680,4683,4685,4688],{"class":244,"line":245},[242,4681,4682],{"class":255},"require",[242,4684,458],{"class":248},[242,4686,4687],{"class":262},"\"org.geonames.reverse\"",[242,4689,1621],{"class":248},[18,4691,4692,4693,4695],{},"The similarity to requirejs ",[242,4694,3938],{}," is intentional and you may want to extend the signature to be fully compliant, but this\nwill be left for your curiosity 🙂",[18,4697,4698],{},"Loading resources from ‘unknown’, or from ‘at runtime unknown’ sources is by nature critical, as the code is executed in\nthe same JVM which hosts your application as well. Therefore, you should only load resources you really trust.",[18,4700,4701,4702,4704,4705,4708],{},"You could achieve this by verifying the signature of the reviewed files using a PKI ",[242,4703,3955],{}," infrastructure – ",[147,4706,4707],{},"javax.crypto","\nis your friend here and fortunately you can implement this in Java and/or use a security library to accomplish this\ntask.",[18,4710,4711],{},"Simply spoken: Always check the integrity if you provide a way for modifications.",[18,4713,4714],{},"If you are already familiar with the scripting engine API, you might have noticed that require is a function and not an\n‘object’.",[18,4716,4717,4718,4721],{},"Even when functions in Javascript are objects, there is no semantic way to say that ",[89,4719,4720],{},"“this object is a function and can\nbe invoked”"," if you share it between the environment.",[18,4723,4724,4725,4728],{},"There might be support for some engines, but not for the others and ",[147,4726,4727],{},"javax.script"," API is designed for general purpose –\ndepending on engine internal interfaces is not desired.",[234,4730,4732],{"className":413,"code":4731,"language":415,"meta":141,"style":141},"obj.require(\n \"org.geonames.reverse\",\n); /* nah, okay but requires additional knowledge obj. */\n",[147,4733,4734,4744,4751],{"__ignoreMap":141},[242,4735,4736,4739,4741],{"class":244,"line":245},[242,4737,4738],{"class":248},"obj.",[242,4740,4682],{"class":255},[242,4742,4743],{"class":248},"(\n",[242,4745,4746,4749],{"class":244,"line":277},[242,4747,4748],{"class":262}," \"org.geonames.reverse\"",[242,4750,580],{"class":248},[242,4752,4753,4756],{"class":244,"line":305},[242,4754,4755],{"class":248},"); ",[242,4757,4758],{"class":1097},"/* nah, okay but requires additional knowledge obj. */\n",[18,4760,4761],{},"Fortunately there is a solution. You can attach a script context to the evaluation process and reuse the context later\non, but you shouldn’t use the internal context as it could leak if your engine leaks.",[18,4763,4764],{},"Pseudo Algorithm:",[2400,4766,4767,4786,4789,4792,4795],{},[85,4768,4769,4770,464,4773,464,4776,464,4779,4782,4783],{},"create a java function object which can load your resources from the ",[89,4771,4772],{},"classpath",[89,4774,4775],{},"internet",[89,4777,4778],{},"local filesystem",[89,4780,4781],{},"…","\nwith a method signature you know. (a function/SAM object) like: ",[147,4784,4785],{},"void apply (String)",[85,4787,4788],{},"create a script context and attach the object from 1. to it with a variable called ‘whatever’ (really whatever name\nyou like)",[85,4790,4791],{},"evaluate an inline require function before you evaluate your business code, which puts your require function into the\nscope of the context from 2.",[85,4793,4794],{},"evaluate your business codes which relies on the require function with the same scope from 2.",[85,4796,4797],{},"have fun",[18,4799,4800],{},"var require = function (library) { whatever.apply (library); }",[18,4802,4803],{},"The above code would be sufficient, but has some drawbacks as it only works if the ‘whatever’ object is in the correct\nexecution scope and if it provides the correct signature – someone could overwrite the binding or does something you\nsimply don’t want him/her to do. We need some slight improvements to correct this.",[234,4805,4807],{"className":413,"code":4806,"language":415,"meta":141,"style":141},"var require = function (library) {\n this.apply(library);\n}.bind(whatever);\ndelete whatever;\n",[147,4808,4809,4827,4840,4850],{"__ignoreMap":141},[242,4810,4811,4814,4816,4818,4820,4822,4825],{"class":244,"line":245},[242,4812,4813],{"class":422},"var",[242,4815,2293],{"class":255},[242,4817,1296],{"class":422},[242,4819,1299],{"class":422},[242,4821,470],{"class":248},[242,4823,4824],{"class":473},"library",[242,4826,1199],{"class":248},[242,4828,4829,4832,4834,4837],{"class":244,"line":277},[242,4830,4831],{"class":448}," this",[242,4833,452],{"class":248},[242,4835,4836],{"class":255},"apply",[242,4838,4839],{"class":248},"(library);\n",[242,4841,4842,4845,4847],{"class":244,"line":305},[242,4843,4844],{"class":248},"}.",[242,4846,4277],{"class":255},[242,4848,4849],{"class":248},"(whatever);\n",[242,4851,4852,4855],{"class":244,"line":327},[242,4853,4854],{"class":422},"delete",[242,4856,4857],{"class":248}," whatever;\n",[2952,4859,4860],{},[18,4861,4862,4863],{},"“The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a\ngiven sequence of arguments preceding any provided when the new function is called.” ",[242,4864,3973],{},[18,4866,4867,4868,4870],{},"If the bind function is not available by your Rhino environment, you may want to look at the implementation from\nPrototype ",[242,4869,3990],{}," et al. and add it manually with the same procedure.",[18,4872,4873],{},"We delete the ‘whatever’ object from the script context afterwards.",[18,4875,4876],{},"You need the following Java code as glue:",[234,4878,4880],{"className":612,"code":4879,"language":614,"meta":141,"style":141},"\n// internal context for variables\nfinal Bindings bindings = scripting.newBindings ();\n bindings.put (Importer, new Function\u003CString, Void> () {\n // load a library 'argument' from the 'lib' directory\n @Override\n public Void apply (String argument) {\n // trusting returns either a valid stream object or throws an 'untrusted code' exception\n String resource;\n resource = String.format (\"/lib/%s.js\", argument);\n scripting.evaluate (trusting (resource), context);\n return null;\n }\n});\ncontext.setBindings (bindings, ScriptContext.ENGINE_SCOPE);\n// add require function to the scope before the application script is loaded\nscripting.evaluate (requirejs (Importer), context);\n// execute the script ultimately\nscripting.evaluate (applicationjs (), context);\n\n",[147,4881,4882,4886,4891,4896,4901,4906,4911,4916,4921,4926,4931,4936,4941,4945,4949,4954,4959,4964,4969],{"__ignoreMap":141},[242,4883,4884],{"class":244,"line":245},[242,4885,389],{"emptyLinePlaceholder":388},[242,4887,4888],{"class":244,"line":277},[242,4889,4890],{},"// internal context for variables\n",[242,4892,4893],{"class":244,"line":305},[242,4894,4895],{},"final Bindings bindings = scripting.newBindings ();\n",[242,4897,4898],{"class":244,"line":327},[242,4899,4900],{}," bindings.put (Importer, new Function\u003CString, Void> () {\n",[242,4902,4903],{"class":244,"line":359},[242,4904,4905],{}," // load a library 'argument' from the 'lib' directory\n",[242,4907,4908],{"class":244,"line":365},[242,4909,4910],{}," @Override\n",[242,4912,4913],{"class":244,"line":375},[242,4914,4915],{}," public Void apply (String argument) {\n",[242,4917,4918],{"class":244,"line":385},[242,4919,4920],{}," // trusting returns either a valid stream object or throws an 'untrusted code' exception\n",[242,4922,4923],{"class":244,"line":392},[242,4924,4925],{}," String resource;\n",[242,4927,4928],{"class":244,"line":550},[242,4929,4930],{}," resource = String.format (\"/lib/%s.js\", argument);\n",[242,4932,4933],{"class":244,"line":555},[242,4934,4935],{}," scripting.evaluate (trusting (resource), context);\n",[242,4937,4938],{"class":244,"line":571},[242,4939,4940],{}," return null;\n",[242,4942,4943],{"class":244,"line":583},[242,4944,685],{},[242,4946,4947],{"class":244,"line":682},[242,4948,586],{},[242,4950,4951],{"class":244,"line":688},[242,4952,4953],{},"context.setBindings (bindings, ScriptContext.ENGINE_SCOPE);\n",[242,4955,4956],{"class":244,"line":693},[242,4957,4958],{},"// add require function to the scope before the application script is loaded\n",[242,4960,4961],{"class":244,"line":699},[242,4962,4963],{},"scripting.evaluate (requirejs (Importer), context);\n",[242,4965,4966],{"class":244,"line":2056},[242,4967,4968],{},"// execute the script ultimately\n",[242,4970,4971],{"class":244,"line":2061},[242,4972,4973],{},"scripting.evaluate (applicationjs (), context);\n",[18,4975,2655],{},[716,4977,4979],{"id":4978},"reporting","Reporting",[18,4981,4982,4983,4985,4986,4988],{},"In the last example I want to explain how to integrate a script engine, which is not shipped by default – JRuby ",[242,4984,4096],{},".\nThe idea behind is to embed code of ruby gems into your application, especially PDFKit ",[242,4987,4108],{}," for this example. PDFKit\ndescribes itself with",[2952,4990,4991],{},[18,4992,4993],{},"“Create PDFs using plain old HTML+CSS. Uses wkhtmltopdf on the back-end which renders HTML using Webkit.”",[18,4995,4996,4997,5000,5001,5004,5005,452],{},"Mostly you don’t want to handle HTML content directly, as your data is often stored in form of a ‘model’. Our solution\nshould therefore target the transformation from: ",[147,4998,4999],{},"Model -> HTML -> PDF",", which can be achieved using e.g. the nice Jade\n",[242,5002,5003],{},"11"," language for the rendering process, especially Jade4j ",[242,5006,5007],{},"12",[18,5009,5010],{},"Instead of writing the integration code for wkhtmltopdf, we will base on the work of PDFKit and write some JRuby glue.",[18,5012,5013,5014,5017,5018,5021],{},"If you need some information about ‘gem bundling’ I would recommend the articles/examples from Sieger ",[242,5015,5016],{},"13"," and Harada\n",[242,5019,5020],{},"14"," as a starting point.",[18,5023,5024,5025,5028],{},"You will find a local file-based repository in the project, as I wanted Maven ",[242,5026,5027],{},"15"," to handle all the dependencies, but\nany other repository might work fine. It simply depends on your infrastructure and what suits you best.",[18,5030,4764],{},[2400,5032,5033,5036,5039,5045,5048,5051],{},[85,5034,5035],{},"put jruby-complete on your classpath, as the library ships the jsr223 implementation",[85,5037,5038],{},"put the converted pdfkit bundle on your classpath",[85,5040,5041,5042],{},"put any other needed library on your classpath ",[242,5043,5044],{},"jade4j, guava, …",[85,5046,5047],{},"write some jruby code to instantiate a configured pdfkit object",[85,5049,5050],{},"proxy the returned jruby object from 4. with a java interface",[85,5052,5053],{},"convert a jade (or differently) generated html stream to pdf using the proxy from 5.",[18,5055,5056,5057,5059],{},"I’ll show you the glue for the proxy only. Please download the project under ",[242,5058,3906],{}," if you want to see the remaining\nparts.",[234,5061,5063],{"className":612,"code":5062,"language":614,"meta":141,"style":141},"\npublic interface Pdfy {\n public boolean convert (InputStream streamin, OutputStream streamout);\n public boolean convert (InputStream streamin, OutputStream streamout, Map\u003CString, String> options);\n}\n\n",[147,5064,5065,5069,5074,5079,5084],{"__ignoreMap":141},[242,5066,5067],{"class":244,"line":245},[242,5068,389],{"emptyLinePlaceholder":388},[242,5070,5071],{"class":244,"line":277},[242,5072,5073],{},"public interface Pdfy {\n",[242,5075,5076],{"class":244,"line":305},[242,5077,5078],{}," public boolean convert (InputStream streamin, OutputStream streamout);\n",[242,5080,5081],{"class":244,"line":327},[242,5082,5083],{}," public boolean convert (InputStream streamin, OutputStream streamout, Map\u003CString, String> options);\n",[242,5085,5086],{"class":244,"line":359},[242,5087,547],{},[234,5089,5093],{"className":5090,"code":5091,"language":5092,"meta":141,"style":141},"language-ruby shiki shiki-themes github-light github-dark","\nclass Pdfy\n def initialize(stylesheet)\n @stylesheet = stylesheet\n end\n def convert(streamin, streamout, options = {})\n begin\n html = streamin.to_io.read\n kit = PDFKit.new(html, options)\n if @stylesheet\n kit.stylesheets \u003C\u003C @stylesheet\n end\n out = streamout.to_io\n out.binmode \u003C\u003C kit.to_pdf\n out.flush\n rescue\n return false\n end\n true\n end\nend\n\n","ruby",[147,5094,5095,5099,5104,5109,5114,5119,5124,5129,5134,5139,5144,5149,5154,5159,5164,5169,5174,5179,5184,5189,5194],{"__ignoreMap":141},[242,5096,5097],{"class":244,"line":245},[242,5098,389],{"emptyLinePlaceholder":388},[242,5100,5101],{"class":244,"line":277},[242,5102,5103],{},"class Pdfy\n",[242,5105,5106],{"class":244,"line":305},[242,5107,5108],{}," def initialize(stylesheet)\n",[242,5110,5111],{"class":244,"line":327},[242,5112,5113],{}," @stylesheet = stylesheet\n",[242,5115,5116],{"class":244,"line":359},[242,5117,5118],{}," end\n",[242,5120,5121],{"class":244,"line":365},[242,5122,5123],{}," def convert(streamin, streamout, options = {})\n",[242,5125,5126],{"class":244,"line":375},[242,5127,5128],{}," begin\n",[242,5130,5131],{"class":244,"line":385},[242,5132,5133],{}," html = streamin.to_io.read\n",[242,5135,5136],{"class":244,"line":392},[242,5137,5138],{}," kit = PDFKit.new(html, options)\n",[242,5140,5141],{"class":244,"line":550},[242,5142,5143],{}," if @stylesheet\n",[242,5145,5146],{"class":244,"line":555},[242,5147,5148],{}," kit.stylesheets \u003C\u003C @stylesheet\n",[242,5150,5151],{"class":244,"line":571},[242,5152,5153],{}," end\n",[242,5155,5156],{"class":244,"line":583},[242,5157,5158],{}," out = streamout.to_io\n",[242,5160,5161],{"class":244,"line":682},[242,5162,5163],{}," out.binmode \u003C\u003C kit.to_pdf\n",[242,5165,5166],{"class":244,"line":688},[242,5167,5168],{}," out.flush\n",[242,5170,5171],{"class":244,"line":693},[242,5172,5173],{}," rescue\n",[242,5175,5176],{"class":244,"line":699},[242,5177,5178],{}," return false\n",[242,5180,5181],{"class":244,"line":2056},[242,5182,5183],{}," end\n",[242,5185,5186],{"class":244,"line":2061},[242,5187,5188],{}," true\n",[242,5190,5192],{"class":244,"line":5191},20,[242,5193,5118],{},[242,5195,5197],{"class":244,"line":5196},21,[242,5198,5199],{},"end\n",[18,5201,5202],{},"Maven will produce an assembly as zip file, which can be extracted elsewhere with a shell script for windows and *nix\nbased systems.",[18,5204,5205],{},"You need to provide the full qualified path for wkhtmltopdf as first argument and the full qualified path of the output\nfile with file extension as second argument.",[18,5207,5208],{},"I did not implement any special CLI handling for this prototype.",[18,5210,5211,5212,5215],{},"You need to install wkhtmltopdf ",[242,5213,5214],{},"16"," as a consequence. I installed wkhtmltopdf 0.11.0 rc2 on windows 7 x64 and\nwkhtmltopdf 0.9.9 on ubuntu 13.10 x64 (virtualization).",[18,5217,5218],{},"Even if writing some boilerplate is not that interesting, writing less boilerplate is! So instead of writing your own\nwheel, you might want to spend your energy on making another wheel feel more rounded.",[18,5220,5221],{},"Whether a script engine can be used in your production environment depends on your configuration, of course, but writing\nsome glue to reuse another solutions might be worth thinking about it.",[18,5223,5224],{},"The effort could be less in comparison to a full rewrite. Stick to a separation of interfaces and implementations and\nlet the environment decide.",[18,5226,5227],{},"The devs. from Rhino/Nashorn/JRuby did quite a good job! As well as the devs. from the mentioned libraries. You should\ncompile the project with Java 1.6(!), 1.7 and 1.8 and look at the results.",[18,5229,5230,5233,5237,5240,5244,5247,5251,5254,5258,5261,5265,5268,5272,5275,5279,5282,5286,5289,5293,5295,5299,5301,5305,5307,5311,5313,5317,5319,5323,5325,5329,5331],{},[242,5231,5232],{}," 1",[24,5234,5235],{"href":5235,"rel":5236},"http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform",[28],[242,5238,5239],{}," 2",[24,5241,5242],{"href":5242,"rel":5243},"http://stackoverflow.com/questions/11838369/where-can-i-find-a-list-of-available-jsr-223-scripting-languages",[28],[242,5245,5246],{}," 3",[24,5248,5249],{"href":5249,"rel":5250},"https://media.synyx.de/uploads//2014/01/synyx.sample.zip",[28],[242,5252,5253],{}," 4",[24,5255,5256],{"href":5256,"rel":5257},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace",[28],[242,5259,5260],{}," 5",[24,5262,5263],{"href":5263,"rel":5264},"http://requirejs.org/",[28],[242,5266,5267],{}," 6",[24,5269,5270],{"href":5270,"rel":5271},"http://de.wikipedia.org/wiki/Public-Key-Infrastruktur",[28],[242,5273,5274],{}," 7",[24,5276,5277],{"href":5277,"rel":5278},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind",[28],[242,5280,5281],{}," 8",[24,5283,5284],{"href":5284,"rel":5285},"http://prototypejs.org/doc/latest/language/Function/prototype/bind/",[28],[242,5287,5288],{}," 9",[24,5290,5291],{"href":5291,"rel":5292},"http://www.jruby.org/",[28],[242,5294,4108],{},[24,5296,5297],{"href":5297,"rel":5298},"https://github.com/pdfkit/pdfkit",[28],[242,5300,5003],{},[24,5302,5303],{"href":5303,"rel":5304},"http://jade-lang.com/",[28],[242,5306,5007],{},[24,5308,5309],{"href":5309,"rel":5310},"https://github.com/neuland/jade4j",[28],[242,5312,5016],{},[24,5314,5315],{"href":5315,"rel":5316},"http://blog.nicksieger.com/articles/2009/01/10/jruby-1-1-6-gems-in-a-jar/",[28],[242,5318,5020],{},[24,5320,5321],{"href":5321,"rel":5322},"http://yokolet.blogspot.de/2010/10/gems-in-jar-with-redbridge.html",[28],[242,5324,5027],{},[24,5326,5327],{"href":5327,"rel":5328},"http://maven.apache.org/",[28],[242,5330,5214],{},[24,5332,5333],{"href":5333,"rel":5334},"https://code.google.com/p/wkhtmltopdf/",[28],[763,5336,5337],{},"html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":141,"searchDepth":277,"depth":277,"links":5339},[5340,5341,5342,5343],{"id":4236,"depth":305,"text":4226},{"id":4281,"depth":305,"text":4282},{"id":4668,"depth":305,"text":4669},{"id":4978,"depth":305,"text":4979},[775],"2014-01-22T10:47:14","https://synyx.de/blog/code-gluse/",{},"/blog/code-gluse",{"title":4226,"description":141},"blog/code-gluse",[614,415,5352,5353,5354,5355,5356,5357],"jruby","jsr-223","mozilla-rhino","oracle-nashorn","pdfkit","wkhtmltopdf","Code gluse Today’s post targets an API, which has been released on Dec. 11, 2006; the javax.scripting package [1] and a lot of good articles that have been written around…","M4C3LMe5gjOAJ7fTRdyMF5pjkdkf_FbgJp5K0AK4qnA",{"id":5361,"title":5362,"author":5363,"body":5365,"category":6356,"date":6357,"description":6358,"extension":779,"link":6359,"meta":6360,"navigation":388,"path":6361,"seo":6362,"slug":6364,"stem":6365,"tags":6366,"teaser":6370,"__hash__":6371},"blog/blog/asynchronous-concurrency-with-vert-x-part-2.md","Asynchronous concurrency with vert.x – Part 2",[5364],"allmendinger",{"type":11,"value":5366,"toc":6354},[5367,5370,5385,5399,5402,5425,5428,5452,5455,5518,5521,5530,5533,5548,5555,5853,5856,5859,6088,6091,6120,6127,6196,6199,6278,6289,6328,6331,6350,6352],[14,5368,5362],{"id":5369},"asynchronous-concurrency-with-vertx-part-2",[18,5371,5372,5373,5378,5379,5384],{},"CoffeeScript\nVert.x supports JavaScript through the ",[24,5374,5377],{"href":5375,"rel":5376},"https://developer.mozilla.org/en/docs/Rhino",[28],"Rhino JavaScript engine",". Although\nJavaScript is a decent language once you get to know it, I prefer ",[24,5380,5383],{"href":5381,"rel":5382},"http://www.coffeescript.org",[28],"CoffeeScript",", a\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\ncompiled JavaScript file.\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:",[234,5386,5388],{"className":1239,"code":5387,"language":1241,"meta":141,"style":141},"\nfoo = (a, b) -> a + b\n\n",[147,5389,5390,5394],{"__ignoreMap":141},[242,5391,5392],{"class":244,"line":245},[242,5393,389],{"emptyLinePlaceholder":388},[242,5395,5396],{"class":244,"line":277},[242,5397,5398],{},"foo = (a, b) -> a + b\n",[18,5400,5401],{},"Translates to the JavaScript code",[234,5403,5405],{"className":1239,"code":5404,"language":1241,"meta":141,"style":141},"\nvar foo = function (a, b) {\n return a + b; // (the last statement is returned)\n}\n\n",[147,5406,5407,5411,5416,5421],{"__ignoreMap":141},[242,5408,5409],{"class":244,"line":245},[242,5410,389],{"emptyLinePlaceholder":388},[242,5412,5413],{"class":244,"line":277},[242,5414,5415],{},"var foo = function (a, b) {\n",[242,5417,5418],{"class":244,"line":305},[242,5419,5420],{}," return a + b; // (the last statement is returned)\n",[242,5422,5423],{"class":244,"line":327},[242,5424,547],{},[18,5426,5427],{},"Also parentheses around function arguments are optional",[234,5429,5431],{"className":1239,"code":5430,"language":1241,"meta":141,"style":141},"\n foo a, b, c\n # same as\n foo(a, b, c)\n\n",[147,5432,5433,5437,5442,5447],{"__ignoreMap":141},[242,5434,5435],{"class":244,"line":245},[242,5436,389],{"emptyLinePlaceholder":388},[242,5438,5439],{"class":244,"line":277},[242,5440,5441],{}," foo a, b, c\n",[242,5443,5444],{"class":244,"line":305},[242,5445,5446],{}," # same as\n",[242,5448,5449],{"class":244,"line":327},[242,5450,5451],{}," foo(a, b, c)\n",[18,5453,5454],{},"The translated source code from the example described in the last post is",[234,5456,5458],{"className":1239,"code":5457,"language":1241,"meta":141,"style":141},"\nvertx = require 'vertx'\naddress = 'example.address'\nhandler = (message, replier) ->\n stdout.println \"sender sent \" + message\n replier \"pong 1\", (message, replier) ->\n # and so on\nvertx.eventBus.registerHandler address, handler\nvertx.eventBus.send address, \"ping 1\", (message, replier) ->\n stdout.println \"handler sent \" + message\n replier \"ping 2\", (message, replier) ->\n # and so on\n\n",[147,5459,5460,5464,5469,5474,5479,5484,5489,5494,5499,5504,5509,5514],{"__ignoreMap":141},[242,5461,5462],{"class":244,"line":245},[242,5463,389],{"emptyLinePlaceholder":388},[242,5465,5466],{"class":244,"line":277},[242,5467,5468],{},"vertx = require 'vertx'\n",[242,5470,5471],{"class":244,"line":305},[242,5472,5473],{},"address = 'example.address'\n",[242,5475,5476],{"class":244,"line":327},[242,5477,5478],{},"handler = (message, replier) ->\n",[242,5480,5481],{"class":244,"line":359},[242,5482,5483],{}," stdout.println \"sender sent \" + message\n",[242,5485,5486],{"class":244,"line":365},[242,5487,5488],{}," replier \"pong 1\", (message, replier) ->\n",[242,5490,5491],{"class":244,"line":375},[242,5492,5493],{}," # and so on\n",[242,5495,5496],{"class":244,"line":385},[242,5497,5498],{},"vertx.eventBus.registerHandler address, handler\n",[242,5500,5501],{"class":244,"line":392},[242,5502,5503],{},"vertx.eventBus.send address, \"ping 1\", (message, replier) ->\n",[242,5505,5506],{"class":244,"line":550},[242,5507,5508],{}," stdout.println \"handler sent \" + message\n",[242,5510,5511],{"class":244,"line":555},[242,5512,5513],{}," replier \"ping 2\", (message, replier) ->\n",[242,5515,5516],{"class":244,"line":571},[242,5517,5493],{},[18,5519,5520],{},"The shorter function declaration notation is a huge improvement, especially when dealing with the kind of\ncallback-heavy code that is prevalent when dealing with asynchronous concurrency.",[18,5522,5523,5524,5529],{},"The Sleeping Barber Problem\nTo challenge vert.x with something more exciting than ping-pong, I decided to model a basic concurrency problem that\nmirrors some of the challenges that our new application will face – the\nfamous ",[24,5525,5528],{"href":5526,"rel":5527},"http://en.wikipedia.org/wiki/Sleeping_barber_problem",[28],"Sleeping Barber Problem",":",[18,5531,5532],{},"The analogy is based upon a hypothetical barber shop with one barber. The barber has one barber chair and a waiting room\nwith a number of chairs in it. When the barber finishes cutting a customer’s hair, he dismisses the customer and then\ngoes to the waiting room to see if there are other customers waiting. If there are, he brings one of them back to the\nchair and cuts his hair. If there are no other customers waiting, he returns to his chair and sleeps in it.\nEach customer, when he arrives, looks to see what the barber is doing. If the barber is sleeping, then the customer\nwakes him up and sits in the chair. If the barber is cutting hair, then the customer goes to the waiting room. If there\nis a free chair in the waiting room, the customer sits in it and waits his turn. If there is no free chair, then the\ncustomer leaves. Based on a naïve analysis, the above description should ensure that the shop functions correctly, with\nthe barber cutting the hair of anyone who arrives until there are no more customers, and then sleeping until the next\ncustomer arrives. In practice, there are a number of problems that can occur that are illustrative of general scheduling\nproblems.",[18,5534,5535,5536,5541,5542,5547],{},"I’ve ",[24,5537,5540],{"href":5538,"rel":5539},"https://github.com/OttoAllmendinger/term-paper-stm",[28],"previously solved this problem","\nusing ",[24,5543,5546],{"href":5544,"rel":5545},"http://en.wikipedia.org/wiki/Software_transactional_memory",[28],"Software Transactional Memory"," and was interested how\nthe message-passing style of vert.x compares.",[18,5549,5550,5551,5554],{},"Barber.coffee\nThe barber shop problem nicely separates into two systems: a ",[147,5552,5553],{},"barber"," message handler that keeps track of incoming\ncustomers and manages the queue, and set of callback methods representing the customer, which initiate a communication\nsequence with the message handler. The following code defines the barber message handler.",[234,5556,5558],{"className":1239,"code":5557,"language":1241,"meta":141,"style":141},"\nvertx = require 'vertx'\naddr = 'barber'\nwaitTime = -> Math.random() * 100\nbarber = ->\n # the state of the message handler lives\n # in this closure\n busy = false\n queue = []\n freeSeats = 20\n # make the system a little indeterministic\n log = (message) ->\n stdout.println \"barber: #{message}\"\n # the following methods define the core behavior\n checkQueue = ->\n if queue.length > 0\n serveCustomer queue.shift()\n freeSeats += 1\n return true\n else\n return false\n serveCustomer = ({customer, replier}) ->\n log \"serving #{customer}\"\n busy = true\n replier 'serve', (message, replier) ->\n vertx.setTimer waitTime(), ->\n log \"done serving #{customer}\"\n busy = checkQueue()\n replier 'done'\n # this is the handler's callback method that\n # is being returned by the barber function\n (message, replier) ->\n customer = message\n if busy\n # there is an intermediate state where we know that we\n # have to queue the customer because there aren't any\n # free seats, but the customer must first acknowledge\n # the waiting state before we can actually put him in\n # the queue.\n if freeSeats > 0\n freeSeats -= 1\n log \"sending #{customer} to queue\"\n replier 'busy', (message, replier) ->\n # customer waiting ack\n queue.push {customer, replier}\n log \"queued #{customer} - \" +\n \"length: #{queue.length} - free seats: #{freeSeats}\"\n else\n replier 'full'\n else\n serveCustomer {customer, replier}\nexports.start = ->\n vertx.eventBus.registerHandler addr, barber()\n\n",[147,5559,5560,5564,5568,5573,5578,5583,5588,5593,5598,5603,5608,5613,5618,5623,5628,5633,5638,5643,5648,5653,5658,5662,5668,5674,5680,5686,5692,5698,5704,5710,5716,5722,5728,5734,5740,5746,5752,5758,5764,5770,5776,5782,5788,5794,5800,5806,5812,5818,5824,5830,5835,5841,5847],{"__ignoreMap":141},[242,5561,5562],{"class":244,"line":245},[242,5563,389],{"emptyLinePlaceholder":388},[242,5565,5566],{"class":244,"line":277},[242,5567,5468],{},[242,5569,5570],{"class":244,"line":305},[242,5571,5572],{},"addr = 'barber'\n",[242,5574,5575],{"class":244,"line":327},[242,5576,5577],{},"waitTime = -> Math.random() * 100\n",[242,5579,5580],{"class":244,"line":359},[242,5581,5582],{},"barber = ->\n",[242,5584,5585],{"class":244,"line":365},[242,5586,5587],{}," # the state of the message handler lives\n",[242,5589,5590],{"class":244,"line":375},[242,5591,5592],{}," # in this closure\n",[242,5594,5595],{"class":244,"line":385},[242,5596,5597],{}," busy = false\n",[242,5599,5600],{"class":244,"line":392},[242,5601,5602],{}," queue = []\n",[242,5604,5605],{"class":244,"line":550},[242,5606,5607],{}," freeSeats = 20\n",[242,5609,5610],{"class":244,"line":555},[242,5611,5612],{}," # make the system a little indeterministic\n",[242,5614,5615],{"class":244,"line":571},[242,5616,5617],{}," log = (message) ->\n",[242,5619,5620],{"class":244,"line":583},[242,5621,5622],{}," stdout.println \"barber: #{message}\"\n",[242,5624,5625],{"class":244,"line":682},[242,5626,5627],{}," # the following methods define the core behavior\n",[242,5629,5630],{"class":244,"line":688},[242,5631,5632],{}," checkQueue = ->\n",[242,5634,5635],{"class":244,"line":693},[242,5636,5637],{}," if queue.length > 0\n",[242,5639,5640],{"class":244,"line":699},[242,5641,5642],{}," serveCustomer queue.shift()\n",[242,5644,5645],{"class":244,"line":2056},[242,5646,5647],{}," freeSeats += 1\n",[242,5649,5650],{"class":244,"line":2061},[242,5651,5652],{}," return true\n",[242,5654,5655],{"class":244,"line":5191},[242,5656,5657],{}," else\n",[242,5659,5660],{"class":244,"line":5196},[242,5661,5178],{},[242,5663,5665],{"class":244,"line":5664},22,[242,5666,5667],{}," serveCustomer = ({customer, replier}) ->\n",[242,5669,5671],{"class":244,"line":5670},23,[242,5672,5673],{}," log \"serving #{customer}\"\n",[242,5675,5677],{"class":244,"line":5676},24,[242,5678,5679],{}," busy = true\n",[242,5681,5683],{"class":244,"line":5682},25,[242,5684,5685],{}," replier 'serve', (message, replier) ->\n",[242,5687,5689],{"class":244,"line":5688},26,[242,5690,5691],{}," vertx.setTimer waitTime(), ->\n",[242,5693,5695],{"class":244,"line":5694},27,[242,5696,5697],{}," log \"done serving #{customer}\"\n",[242,5699,5701],{"class":244,"line":5700},28,[242,5702,5703],{}," busy = checkQueue()\n",[242,5705,5707],{"class":244,"line":5706},29,[242,5708,5709],{}," replier 'done'\n",[242,5711,5713],{"class":244,"line":5712},30,[242,5714,5715],{}," # this is the handler's callback method that\n",[242,5717,5719],{"class":244,"line":5718},31,[242,5720,5721],{}," # is being returned by the barber function\n",[242,5723,5725],{"class":244,"line":5724},32,[242,5726,5727],{}," (message, replier) ->\n",[242,5729,5731],{"class":244,"line":5730},33,[242,5732,5733],{}," customer = message\n",[242,5735,5737],{"class":244,"line":5736},34,[242,5738,5739],{}," if busy\n",[242,5741,5743],{"class":244,"line":5742},35,[242,5744,5745],{}," # there is an intermediate state where we know that we\n",[242,5747,5749],{"class":244,"line":5748},36,[242,5750,5751],{}," # have to queue the customer because there aren't any\n",[242,5753,5755],{"class":244,"line":5754},37,[242,5756,5757],{}," # free seats, but the customer must first acknowledge\n",[242,5759,5761],{"class":244,"line":5760},38,[242,5762,5763],{}," # the waiting state before we can actually put him in\n",[242,5765,5767],{"class":244,"line":5766},39,[242,5768,5769],{}," # the queue.\n",[242,5771,5773],{"class":244,"line":5772},40,[242,5774,5775],{}," if freeSeats > 0\n",[242,5777,5779],{"class":244,"line":5778},41,[242,5780,5781],{}," freeSeats -= 1\n",[242,5783,5785],{"class":244,"line":5784},42,[242,5786,5787],{}," log \"sending #{customer} to queue\"\n",[242,5789,5791],{"class":244,"line":5790},43,[242,5792,5793],{}," replier 'busy', (message, replier) ->\n",[242,5795,5797],{"class":244,"line":5796},44,[242,5798,5799],{}," # customer waiting ack\n",[242,5801,5803],{"class":244,"line":5802},45,[242,5804,5805],{}," queue.push {customer, replier}\n",[242,5807,5809],{"class":244,"line":5808},46,[242,5810,5811],{}," log \"queued #{customer} - \" +\n",[242,5813,5815],{"class":244,"line":5814},47,[242,5816,5817],{}," \"length: #{queue.length} - free seats: #{freeSeats}\"\n",[242,5819,5821],{"class":244,"line":5820},48,[242,5822,5823],{}," else\n",[242,5825,5827],{"class":244,"line":5826},49,[242,5828,5829],{}," replier 'full'\n",[242,5831,5833],{"class":244,"line":5832},50,[242,5834,5657],{},[242,5836,5838],{"class":244,"line":5837},51,[242,5839,5840],{}," serveCustomer {customer, replier}\n",[242,5842,5844],{"class":244,"line":5843},52,[242,5845,5846],{},"exports.start = ->\n",[242,5848,5850],{"class":244,"line":5849},53,[242,5851,5852],{}," vertx.eventBus.registerHandler addr, barber()\n",[18,5854,5855],{},"The state of the barber is encoded by the callback method that will be called for an upcoming event and the values of\nthe variables defined in the closure. By being able to store repliers you can easily trigger remote state changes\natomically, when they should occur.",[18,5857,5858],{},"Customer.coffee\nLet’s define the behavior of the customer in a separate file",[234,5860,5862],{"className":1239,"code":5861,"language":1241,"meta":141,"style":141},"\nvertx = require 'vertx'\naddr = 'barber'\nwaitTime = -> Math.random() * 100\nsendCustomer = (i) ->\n # As with the barber, the customer's state is\n # defined in this closure. The variables will\n # be modified by the callback methods that are\n # triggered by the message handler's replies.\n waiting = false\n beingServed = false\n log = (message) ->\n stdout.println \"customer #{i}: #{message}\"\n # just a shorthand\n send = (message, callback) ->\n vertx.eventBus.send addr, message, callback\n # factor out the exit method:\n # a customer can exit after having been served\n # or when the queue is full\n exit = (message) ->\n log message + \" - exiting\"\n # this method doesn't send a response\n # via the replier\n getHaircut = (message, replier) ->\n waiting = false\n beingServed = true\n log \"being served\"\n replier 'being-served', exit\n log \"enters\"\n send \"customer #{i}\", (message, replier) ->\n switch message\n when 'busy'\n waiting = true\n log 'waiting'\n replier 'waiting', getHaircut\n when 'serve'\n getHaircut message, replier\n when 'full'\n exit message\n# a loop that continuously sends customers\n# to the barber\nsendCustomerLoop = (i) ->\n sendCustomer i\n vertx.setTimer waitTime(), -> sendCustomerLoop i + 1\nexports.start = ->\n sendCustomerLoop 1\n\n",[147,5863,5864,5868,5872,5876,5880,5885,5890,5895,5900,5905,5910,5915,5919,5924,5929,5934,5939,5944,5949,5954,5959,5964,5969,5974,5979,5984,5989,5994,5999,6004,6009,6014,6019,6024,6029,6034,6039,6044,6049,6054,6059,6064,6069,6074,6079,6083],{"__ignoreMap":141},[242,5865,5866],{"class":244,"line":245},[242,5867,389],{"emptyLinePlaceholder":388},[242,5869,5870],{"class":244,"line":277},[242,5871,5468],{},[242,5873,5874],{"class":244,"line":305},[242,5875,5572],{},[242,5877,5878],{"class":244,"line":327},[242,5879,5577],{},[242,5881,5882],{"class":244,"line":359},[242,5883,5884],{},"sendCustomer = (i) ->\n",[242,5886,5887],{"class":244,"line":365},[242,5888,5889],{}," # As with the barber, the customer's state is\n",[242,5891,5892],{"class":244,"line":375},[242,5893,5894],{}," # defined in this closure. The variables will\n",[242,5896,5897],{"class":244,"line":385},[242,5898,5899],{}," # be modified by the callback methods that are\n",[242,5901,5902],{"class":244,"line":392},[242,5903,5904],{}," # triggered by the message handler's replies.\n",[242,5906,5907],{"class":244,"line":550},[242,5908,5909],{}," waiting = false\n",[242,5911,5912],{"class":244,"line":555},[242,5913,5914],{}," beingServed = false\n",[242,5916,5917],{"class":244,"line":571},[242,5918,5617],{},[242,5920,5921],{"class":244,"line":583},[242,5922,5923],{}," stdout.println \"customer #{i}: #{message}\"\n",[242,5925,5926],{"class":244,"line":682},[242,5927,5928],{}," # just a shorthand\n",[242,5930,5931],{"class":244,"line":688},[242,5932,5933],{}," send = (message, callback) ->\n",[242,5935,5936],{"class":244,"line":693},[242,5937,5938],{}," vertx.eventBus.send addr, message, callback\n",[242,5940,5941],{"class":244,"line":699},[242,5942,5943],{}," # factor out the exit method:\n",[242,5945,5946],{"class":244,"line":2056},[242,5947,5948],{}," # a customer can exit after having been served\n",[242,5950,5951],{"class":244,"line":2061},[242,5952,5953],{}," # or when the queue is full\n",[242,5955,5956],{"class":244,"line":5191},[242,5957,5958],{}," exit = (message) ->\n",[242,5960,5961],{"class":244,"line":5196},[242,5962,5963],{}," log message + \" - exiting\"\n",[242,5965,5966],{"class":244,"line":5664},[242,5967,5968],{}," # this method doesn't send a response\n",[242,5970,5971],{"class":244,"line":5670},[242,5972,5973],{}," # via the replier\n",[242,5975,5976],{"class":244,"line":5676},[242,5977,5978],{}," getHaircut = (message, replier) ->\n",[242,5980,5981],{"class":244,"line":5682},[242,5982,5983],{}," waiting = false\n",[242,5985,5986],{"class":244,"line":5688},[242,5987,5988],{}," beingServed = true\n",[242,5990,5991],{"class":244,"line":5694},[242,5992,5993],{}," log \"being served\"\n",[242,5995,5996],{"class":244,"line":5700},[242,5997,5998],{}," replier 'being-served', exit\n",[242,6000,6001],{"class":244,"line":5706},[242,6002,6003],{}," log \"enters\"\n",[242,6005,6006],{"class":244,"line":5712},[242,6007,6008],{}," send \"customer #{i}\", (message, replier) ->\n",[242,6010,6011],{"class":244,"line":5718},[242,6012,6013],{}," switch message\n",[242,6015,6016],{"class":244,"line":5724},[242,6017,6018],{}," when 'busy'\n",[242,6020,6021],{"class":244,"line":5730},[242,6022,6023],{}," waiting = true\n",[242,6025,6026],{"class":244,"line":5736},[242,6027,6028],{}," log 'waiting'\n",[242,6030,6031],{"class":244,"line":5742},[242,6032,6033],{}," replier 'waiting', getHaircut\n",[242,6035,6036],{"class":244,"line":5748},[242,6037,6038],{}," when 'serve'\n",[242,6040,6041],{"class":244,"line":5754},[242,6042,6043],{}," getHaircut message, replier\n",[242,6045,6046],{"class":244,"line":5760},[242,6047,6048],{}," when 'full'\n",[242,6050,6051],{"class":244,"line":5766},[242,6052,6053],{}," exit message\n",[242,6055,6056],{"class":244,"line":5772},[242,6057,6058],{},"# a loop that continuously sends customers\n",[242,6060,6061],{"class":244,"line":5778},[242,6062,6063],{},"# to the barber\n",[242,6065,6066],{"class":244,"line":5784},[242,6067,6068],{},"sendCustomerLoop = (i) ->\n",[242,6070,6071],{"class":244,"line":5790},[242,6072,6073],{}," sendCustomer i\n",[242,6075,6076],{"class":244,"line":5796},[242,6077,6078],{}," vertx.setTimer waitTime(), -> sendCustomerLoop i + 1\n",[242,6080,6081],{"class":244,"line":5802},[242,6082,5846],{},[242,6084,6085],{"class":244,"line":5808},[242,6086,6087],{}," sendCustomerLoop 1\n",[18,6089,6090],{},"barbershop.coffee\nThis time, we want to run both handler and sender in the same process, for easier testing.",[234,6092,6094],{"className":1239,"code":6093,"language":1241,"meta":141,"style":141},"\nbarber = require 'barber'\ncustomer = require 'customer'\nbarber.start()\ncustomer.start()\n\n",[147,6095,6096,6100,6105,6110,6115],{"__ignoreMap":141},[242,6097,6098],{"class":244,"line":245},[242,6099,389],{"emptyLinePlaceholder":388},[242,6101,6102],{"class":244,"line":277},[242,6103,6104],{},"barber = require 'barber'\n",[242,6106,6107],{"class":244,"line":305},[242,6108,6109],{},"customer = require 'customer'\n",[242,6111,6112],{"class":244,"line":327},[242,6113,6114],{},"barber.start()\n",[242,6116,6117],{"class":244,"line":359},[242,6118,6119],{},"customer.start()\n",[18,6121,6122,6123,6126],{},"Running the shop\nWhen we start the ",[147,6124,6125],{},"barbershop.coffee"," script, we can see in the log that the shop is running as it is supposed to:",[234,6128,6130],{"className":1239,"code":6129,"language":1241,"meta":141,"style":141},"\ncustomer 1: enters\nbarber: serving customer 1\ncustomer 1: being served\nbarber: done serving customer 1\ncustomer 1: done - exiting\ncustomer 2: enters\nbarber: serving customer 2\ncustomer 2: being served\nbarber: done serving customer 2\ncustomer 2: done - exiting\ncustomer 3: enters\n[...]\n\n",[147,6131,6132,6136,6141,6146,6151,6156,6161,6166,6171,6176,6181,6186,6191],{"__ignoreMap":141},[242,6133,6134],{"class":244,"line":245},[242,6135,389],{"emptyLinePlaceholder":388},[242,6137,6138],{"class":244,"line":277},[242,6139,6140],{},"customer 1: enters\n",[242,6142,6143],{"class":244,"line":305},[242,6144,6145],{},"barber: serving customer 1\n",[242,6147,6148],{"class":244,"line":327},[242,6149,6150],{},"customer 1: being served\n",[242,6152,6153],{"class":244,"line":359},[242,6154,6155],{},"barber: done serving customer 1\n",[242,6157,6158],{"class":244,"line":365},[242,6159,6160],{},"customer 1: done - exiting\n",[242,6162,6163],{"class":244,"line":375},[242,6164,6165],{},"customer 2: enters\n",[242,6167,6168],{"class":244,"line":385},[242,6169,6170],{},"barber: serving customer 2\n",[242,6172,6173],{"class":244,"line":392},[242,6174,6175],{},"customer 2: being served\n",[242,6177,6178],{"class":244,"line":550},[242,6179,6180],{},"barber: done serving customer 2\n",[242,6182,6183],{"class":244,"line":555},[242,6184,6185],{},"customer 2: done - exiting\n",[242,6187,6188],{"class":244,"line":571},[242,6189,6190],{},"customer 3: enters\n",[242,6192,6193],{"class":244,"line":583},[242,6194,6195],{},"[...]\n",[18,6197,6198],{},"This is what the output looks like when there is no congestion at all. By chance, these customers came in just after the\nprevious customer was served. If we wait a little longer, we can see a customer entering while the barber is busy:",[234,6200,6202],{"className":1239,"code":6201,"language":1241,"meta":141,"style":141},"\nbarber: serving customer 3\ncustomer 3: being served\ncustomer 4: enters\nbarber: sending customer 4 to queue\ncustomer 4: waiting\nbarber: queued customer 4 - length: 1 - free seats: 19\ncustomer 5: enters\nbarber: sending customer 5 to queue\ncustomer 5: waiting\nbarber: queued customer 5 - length: 2 - free seats: 18\nbarber: done serving customer 3\nbarber: serving customer 4\ncustomer 3: done - exiting\ncustomer 4: being served\n\n",[147,6203,6204,6208,6213,6218,6223,6228,6233,6238,6243,6248,6253,6258,6263,6268,6273],{"__ignoreMap":141},[242,6205,6206],{"class":244,"line":245},[242,6207,389],{"emptyLinePlaceholder":388},[242,6209,6210],{"class":244,"line":277},[242,6211,6212],{},"barber: serving customer 3\n",[242,6214,6215],{"class":244,"line":305},[242,6216,6217],{},"customer 3: being served\n",[242,6219,6220],{"class":244,"line":327},[242,6221,6222],{},"customer 4: enters\n",[242,6224,6225],{"class":244,"line":359},[242,6226,6227],{},"barber: sending customer 4 to queue\n",[242,6229,6230],{"class":244,"line":365},[242,6231,6232],{},"customer 4: waiting\n",[242,6234,6235],{"class":244,"line":375},[242,6236,6237],{},"barber: queued customer 4 - length: 1 - free seats: 19\n",[242,6239,6240],{"class":244,"line":385},[242,6241,6242],{},"customer 5: enters\n",[242,6244,6245],{"class":244,"line":392},[242,6246,6247],{},"barber: sending customer 5 to queue\n",[242,6249,6250],{"class":244,"line":550},[242,6251,6252],{},"customer 5: waiting\n",[242,6254,6255],{"class":244,"line":555},[242,6256,6257],{},"barber: queued customer 5 - length: 2 - free seats: 18\n",[242,6259,6260],{"class":244,"line":571},[242,6261,6262],{},"barber: done serving customer 3\n",[242,6264,6265],{"class":244,"line":583},[242,6266,6267],{},"barber: serving customer 4\n",[242,6269,6270],{"class":244,"line":682},[242,6271,6272],{},"customer 3: done - exiting\n",[242,6274,6275],{"class":244,"line":688},[242,6276,6277],{},"customer 4: being served\n",[18,6279,6280,6281,6284,6285,6288],{},"As you can see, customer 4 was added to the queue and is being served right customer 3 is done. But what happens if the\nqueue is full? Let’s set ",[147,6282,6283],{},"waitTime = -> Math.random() * 80"," in ",[147,6286,6287],{},"customer.coffee"," so that there are a few more customers\nentering than leaving.",[234,6290,6292],{"className":1239,"code":6291,"language":1241,"meta":141,"style":141},"\ncustomer 34: enters\nbarber: sending customer 34 to queue\ncustomer 34: waiting\nbarber: queued customer 34 - length: 20 - free seats: 0\ncustomer 35: enters\ncustomer 35: full - exiting\n\n",[147,6293,6294,6298,6303,6308,6313,6318,6323],{"__ignoreMap":141},[242,6295,6296],{"class":244,"line":245},[242,6297,389],{"emptyLinePlaceholder":388},[242,6299,6300],{"class":244,"line":277},[242,6301,6302],{},"customer 34: enters\n",[242,6304,6305],{"class":244,"line":305},[242,6306,6307],{},"barber: sending customer 34 to queue\n",[242,6309,6310],{"class":244,"line":327},[242,6311,6312],{},"customer 34: waiting\n",[242,6314,6315],{"class":244,"line":359},[242,6316,6317],{},"barber: queued customer 34 - length: 20 - free seats: 0\n",[242,6319,6320],{"class":244,"line":365},[242,6321,6322],{},"customer 35: enters\n",[242,6324,6325],{"class":244,"line":375},[242,6326,6327],{},"customer 35: full - exiting\n",[18,6329,6330],{},"New customers are being turned away, as expected. The important thing is that there is no deadlocks and no invalid\nstates, which can be easily checked by reading the log output. Knowing that there is just one callback method being\nexecuted at any point in time is a great help when reasoning about the program.",[18,6332,6333,6334,6337,6338,6341,6342,6345,6346,6349],{},"Conclusion\nThe central primitive is the construct ",[147,6335,6336],{},"replier(send_message, next_state)",". The ",[147,6339,6340],{},"replier"," triggers a state transition in\nthe remote system through ",[147,6343,6344],{},"send_message"," and defines the local ",[147,6347,6348],{},"next_state",".\nIf you can model your system as something similar to linked state machines, this concurrency approach is easy to\nimplement and very powerful.",[2393,6351],{},[763,6353,2775],{},{"title":141,"searchDepth":277,"depth":277,"links":6355},[],[775,3866],"2013-04-24T12:34:33","CoffeeScript\\nVert.x supports JavaScript through the Rhino JavaScript engine. Although\\nJavaScript is a decent language once you get to know it, I prefer CoffeeScript, a\\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\\ncompiled JavaScript file.\\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:","https://synyx.de/blog/asynchronous-concurrency-with-vert-x-part-2/",{},"/blog/asynchronous-concurrency-with-vert-x-part-2",{"title":5362,"description":6363},"CoffeeScript\nVert.x supports JavaScript through the Rhino JavaScript engine. Although\nJavaScript is a decent language once you get to know it, I prefer CoffeeScript, a\nlanguage that compiles to JavaScript. Luckily, vert.x has built-in support for CoffeeScript, so I can use it nearly\ntransparently. You will only notice the JavaScript under the hood when reading stack traces, which will refer to the\ncompiled JavaScript file.\nFor the examples in this blog post, the only thing you need to know a little CoffeeScript:","asynchronous-concurrency-with-vert-x-part-2","blog/asynchronous-concurrency-with-vert-x-part-2",[6367,6368,415,6369],"coffeescript","concurrency","vertx","CoffeeScript Vert.x supports JavaScript through the Rhino JavaScript engine. Although JavaScript is a decent language once you get to know it, I prefer CoffeeScript, a language that compiles to JavaScript.…","jAhQ7HOciuqYnRkEMPmBHkaC1DBrMMrda632jXz81ds",{"id":6373,"title":6374,"author":6375,"body":6376,"category":6677,"date":6678,"description":6679,"extension":779,"link":6680,"meta":6681,"navigation":388,"path":6682,"seo":6683,"slug":6685,"stem":6686,"tags":6687,"teaser":6688,"__hash__":6689},"blog/blog/asynchronous-concurrency-with-vert-x-part-1.md","Asynchronous concurrency with vert.x – Part 1",[5364],{"type":11,"value":6377,"toc":6673},[6378,6381,6412,6415,6427,6489,6492,6552,6555,6574,6583,6662,6671],[14,6379,6374],{"id":6380},"asynchronous-concurrency-with-vertx-part-1",[18,6382,6383,6384,6389,6390,6393,6394,6397,6398,6401,6402,6405,6406,6411],{},"Event-Driven Concurrency\nAt synyx, we are looking at ",[24,6385,6388],{"href":6386,"rel":6387},"http://www.vertx.io",[28],"vert.x"," for an upcoming project where we are building a system that\nwill need to scale under load. The tag-line of vert.x is ",[89,6391,6392],{},"effortless asynchronous application development for the\nmodern web and enterprise",", which fits the bill, so I decided to play around with it a little bit.\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\nAnybody who has has used jQuery’s ",[147,6395,6396],{},"$.ajax"," should have some idea of what event-driven concurrency means: an event loop\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\nfunction can run simultaneously. The same is true for ",[147,6399,6400],{},"setTimeout",", which is used extensively for animations: adjust the\nproperties of an element a little bit each call, then return to the event loop.\nThis is the reason why there is no ",[147,6403,6404],{},"sleep()"," function in JavaScript – the browser would freeze, the user couldn’t\ninteract with the web page. Each callback method must be short-running.\nWith ",[24,6407,6410],{"href":6408,"rel":6409},"https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers",[28],"WebWorkers",", you can now also perform client-side\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\npassing.",[18,6413,6414],{},"vert.x\nVert.x brings this concept to the server side on top of the JVM. It allows writing applications using a event-driven\nconcurrency model. There are bindings for basically every language that runs on top of the JVM: Java, Ruby, Groovy,\nPython and JavaScript. The distributed event bus provides seamless scaling over multiple cores or hosts. You perform a\ncomputation one process, send data via the event bus to another process where a callback method is executed.",[18,6416,6417,6418,6423,6424,5529],{},"Event bus\nA short example in JavaScript that uses the event bus from\nthe ",[24,6419,6422],{"href":6420,"rel":6421},"https://github.com/vert-x/vert.x/blob/master/vertx-examples/src/main/javascript/eventbus",[28],"vert.x github repository"," –\ndefine a message handler that simply displays messages sent to the event bus address ",[147,6425,6426],{},"example.address",[234,6428,6430],{"className":1239,"code":6429,"language":1241,"meta":141,"style":141},"\n// file handler.js\nload('vertx.js')\nvar eb = vertx.eventBus;\nvar address = 'example.address'\nvar handler = function(message) {\n stdout.println('Received message ' + message)\n}\neb.registerHandler(address, handler);\nfunction vertxStop() {\n eb.unregisterHandler(address, handler);\n}\n\n",[147,6431,6432,6436,6441,6446,6451,6456,6461,6466,6470,6475,6480,6485],{"__ignoreMap":141},[242,6433,6434],{"class":244,"line":245},[242,6435,389],{"emptyLinePlaceholder":388},[242,6437,6438],{"class":244,"line":277},[242,6439,6440],{},"// file handler.js\n",[242,6442,6443],{"class":244,"line":305},[242,6444,6445],{},"load('vertx.js')\n",[242,6447,6448],{"class":244,"line":327},[242,6449,6450],{},"var eb = vertx.eventBus;\n",[242,6452,6453],{"class":244,"line":359},[242,6454,6455],{},"var address = 'example.address'\n",[242,6457,6458],{"class":244,"line":365},[242,6459,6460],{},"var handler = function(message) {\n",[242,6462,6463],{"class":244,"line":375},[242,6464,6465],{}," stdout.println('Received message ' + message)\n",[242,6467,6468],{"class":244,"line":385},[242,6469,547],{},[242,6471,6472],{"class":244,"line":392},[242,6473,6474],{},"eb.registerHandler(address, handler);\n",[242,6476,6477],{"class":244,"line":550},[242,6478,6479],{},"function vertxStop() {\n",[242,6481,6482],{"class":244,"line":555},[242,6483,6484],{}," eb.unregisterHandler(address, handler);\n",[242,6486,6487],{"class":244,"line":571},[242,6488,547],{},[18,6490,6491],{},"We put the program that sends messages in a different file to achieve process isolation:",[234,6493,6495],{"className":1239,"code":6494,"language":1241,"meta":141,"style":141},"\n// file sender.js\nload('vertx.js')\nvar eb = vertx.eventBus;\nvar address = 'example.address'\nvertx.setPeriodic(2000, sendMessage)\nvar count = 0\nfunction sendMessage() {\n var msg = \"some-message-\" + count++;\n eb.send(address, msg);\n stdout.println(\"sent message \" + msg)\n}\n\n",[147,6496,6497,6501,6506,6510,6514,6518,6523,6528,6533,6538,6543,6548],{"__ignoreMap":141},[242,6498,6499],{"class":244,"line":245},[242,6500,389],{"emptyLinePlaceholder":388},[242,6502,6503],{"class":244,"line":277},[242,6504,6505],{},"// file sender.js\n",[242,6507,6508],{"class":244,"line":305},[242,6509,6445],{},[242,6511,6512],{"class":244,"line":327},[242,6513,6450],{},[242,6515,6516],{"class":244,"line":359},[242,6517,6455],{},[242,6519,6520],{"class":244,"line":365},[242,6521,6522],{},"vertx.setPeriodic(2000, sendMessage)\n",[242,6524,6525],{"class":244,"line":375},[242,6526,6527],{},"var count = 0\n",[242,6529,6530],{"class":244,"line":385},[242,6531,6532],{},"function sendMessage() {\n",[242,6534,6535],{"class":244,"line":392},[242,6536,6537],{}," var msg = \"some-message-\" + count++;\n",[242,6539,6540],{"class":244,"line":550},[242,6541,6542],{}," eb.send(address, msg);\n",[242,6544,6545],{"class":244,"line":555},[242,6546,6547],{}," stdout.println(\"sent message \" + msg)\n",[242,6549,6550],{"class":244,"line":571},[242,6551,547],{},[18,6553,6554],{},"Both programs are then started separately using the vertx runtime. They can then communicate on the event bus via the\nnetwork:",[234,6556,6558],{"className":1239,"code":6557,"language":1241,"meta":141,"style":141},"\n# vertx run handler.js -cluster -cluster-port 10001 &\n# vertx run sender.js -cluster -cluster-port 10002\n\n",[147,6559,6560,6564,6569],{"__ignoreMap":141},[242,6561,6562],{"class":244,"line":245},[242,6563,389],{"emptyLinePlaceholder":388},[242,6565,6566],{"class":244,"line":277},[242,6567,6568],{},"# vertx run handler.js -cluster -cluster-port 10001 &\n",[242,6570,6571],{"class":244,"line":305},[242,6572,6573],{},"# vertx run sender.js -cluster -cluster-port 10002\n",[18,6575,6576,6577,6582],{},"Repliers\nThis described system can be extended by the use\nof ",[24,6578,6581],{"href":6579,"rel":6580},"http://vertx.io/core_manual_js.html#replying-to-messages",[28],"repliers",", which can be used to start a dialog between a\nmessage handler and a sender. The sender and the replier both live in the same file this time:",[234,6584,6586],{"className":1239,"code":6585,"language":1241,"meta":141,"style":141},"\nvar vertx = require('vertx'); // alternative import\nvar address = \"example.address\";\nvar handler = function (message, replier) {\n stdout.println(\"sender sent \" + message);\n replier(\"pong 1\", function (message, replier) {\n // and so on\n });\n}\nvertx.eventBus.registerHandler(address, handler);\nvertx.eventBus.send(address, \"ping 1\", function (message, replier) {\n stdout.println(\"handler sent \" + message);\n replier(\"ping 2\", function(message, replier) {\n // and so on\n });\n});\n\n",[147,6587,6588,6592,6597,6602,6607,6612,6617,6622,6626,6630,6635,6640,6645,6650,6654,6658],{"__ignoreMap":141},[242,6589,6590],{"class":244,"line":245},[242,6591,389],{"emptyLinePlaceholder":388},[242,6593,6594],{"class":244,"line":277},[242,6595,6596],{},"var vertx = require('vertx'); // alternative import\n",[242,6598,6599],{"class":244,"line":305},[242,6600,6601],{},"var address = \"example.address\";\n",[242,6603,6604],{"class":244,"line":327},[242,6605,6606],{},"var handler = function (message, replier) {\n",[242,6608,6609],{"class":244,"line":359},[242,6610,6611],{}," stdout.println(\"sender sent \" + message);\n",[242,6613,6614],{"class":244,"line":365},[242,6615,6616],{}," replier(\"pong 1\", function (message, replier) {\n",[242,6618,6619],{"class":244,"line":375},[242,6620,6621],{}," // and so on\n",[242,6623,6624],{"class":244,"line":385},[242,6625,537],{},[242,6627,6628],{"class":244,"line":392},[242,6629,547],{},[242,6631,6632],{"class":244,"line":550},[242,6633,6634],{},"vertx.eventBus.registerHandler(address, handler);\n",[242,6636,6637],{"class":244,"line":555},[242,6638,6639],{},"vertx.eventBus.send(address, \"ping 1\", function (message, replier) {\n",[242,6641,6642],{"class":244,"line":571},[242,6643,6644],{}," stdout.println(\"handler sent \" + message);\n",[242,6646,6647],{"class":244,"line":583},[242,6648,6649],{}," replier(\"ping 2\", function(message, replier) {\n",[242,6651,6652],{"class":244,"line":682},[242,6653,6621],{},[242,6655,6656],{"class":244,"line":688},[242,6657,537],{},[242,6659,6660],{"class":244,"line":693},[242,6661,586],{},[74,6663,6665,6666,6670],{"id":6664},"every-sent-message-can-be-acknowledged-with-a-reply-by-the-other-side-and-vice-versa-this-concurrency-model-is-very-easy-to-grasp-and-very-powerful-we-will-use-it-in-the-next-part-of-this-series-where-we-tackle-the-sleeping-barber-problem-stay-tuned","Every sent message can be acknowledged with a reply by the other side and vice versa. This concurrency model is very easy to grasp and very powerful. We will use it in the next part of this series, where we tackle the ",[24,6667,6669],{"href":5526,"rel":6668},[28],"Sleeping barber problem"," – stay tuned!",[763,6672,2775],{},{"title":141,"searchDepth":277,"depth":277,"links":6674},[6675],{"id":6664,"depth":277,"text":6676},"Every sent message can be acknowledged with a reply by the other side and vice versa. This concurrency model is very easy to grasp and very powerful. We will use it in the next part of this series, where we tackle the Sleeping barber problem – stay tuned!",[775,3866],"2013-04-15T20:59:34","Event-Driven Concurrency\\nAt synyx, we are looking at vert.x for an upcoming project where we are building a system that\\nwill need to scale under load. The tag-line of vert.x is effortless asynchronous application development for the\\nmodern web and enterprise, which fits the bill, so I decided to play around with it a little bit.\\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\\nAnybody who has has used jQuery’s $.ajax should have some idea of what event-driven concurrency means: an event loop\\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\\nfunction can run simultaneously. The same is true for setTimeout, which is used extensively for animations: adjust the\\nproperties of an element a little bit each call, then return to the event loop.\\nThis is the reason why there is no sleep() function in JavaScript – the browser would freeze, the user couldn’t\\ninteract with the web page. Each callback method must be short-running.\\nWith WebWorkers, you can now also perform client-side\\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\\npassing.","https://synyx.de/blog/asynchronous-concurrency-with-vert-x-part-1/",{},"/blog/asynchronous-concurrency-with-vert-x-part-1",{"title":6374,"description":6684},"Event-Driven Concurrency\nAt synyx, we are looking at vert.x for an upcoming project where we are building a system that\nwill need to scale under load. The tag-line of vert.x is effortless asynchronous application development for the\nmodern web and enterprise, which fits the bill, so I decided to play around with it a little bit.\nThe advantage of event-driven concurrency compared to traditional technologies is the reduced risk of deadlocks,\nlivelocks and race conditions. Using mutexes and semaphores correctly is extremely difficult and can lead to very subtle\nbugs that are difficult to reproduce. The downside is that information can only be shared by passing messages.\nAnybody who has has used jQuery’s $.ajax should have some idea of what event-driven concurrency means: an event loop\ntriggers predefined callbacks after a certain event happens. In that case, the system is retrieving the data in the\nbackground, while your JavaScript program can do something else in the meantime, like respond to user events. Once the\ndata has arrived, the callback method is triggered and the data is passed as a function argument – no other callback\nfunction can run simultaneously. The same is true for setTimeout, which is used extensively for animations: adjust the\nproperties of an element a little bit each call, then return to the event loop.\nThis is the reason why there is no sleep() function in JavaScript – the browser would freeze, the user couldn’t\ninteract with the web page. Each callback method must be short-running.\nWith WebWorkers, you can now also perform client-side\ncomputation without blocking the main event loop, putting your multi-core CPU to use. The mechanism of communication\nbetween the background task and the main task is the same as with doing asynchronous IO – using callbacks and message\npassing.","asynchronous-concurrency-with-vert-x-part-1","blog/asynchronous-concurrency-with-vert-x-part-1",[6368,415,6369],"Event-Driven Concurrency At synyx, we are looking at vert.x for an upcoming project where we are building a system that will need to scale under load. The tag-line of vert.x…","DkNmAOE6c6bSo2QAVLXKERm9hchogl5z2xWxAovYy3Q",{"id":6691,"title":6692,"author":6693,"body":6694,"category":7079,"date":7080,"description":7081,"extension":779,"link":7082,"meta":7083,"navigation":388,"path":7084,"seo":7085,"slug":6698,"stem":7087,"tags":7088,"teaser":7092,"__hash__":7093},"blog/blog/properly-calculating-time-differences-in-javascript.md","Properly calculating time differences in JavaScript",[5364],{"type":11,"value":6695,"toc":7077},[6696,6699,6706,6727,6756,6759,6794,6797,6811,6819,6858,6861,6881,6892,6901,6920,6935,6996,7002,7039,7052,7064,7075],[14,6697,6692],{"id":6698},"properly-calculating-time-differences-in-javascript",[18,6700,6701,6702,6705],{},"Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the ",[89,6703,6704],{},"Date","object and do\nsome calculating.",[18,6707,6708,6709,6712,6713,6716,6717,6719,6720,6723,6724,6726],{},"As a JavaScript veteran you know that you have to use ",[147,6710,6711],{},"new Date()"," instead of ",[147,6714,6715],{},"Date()"," because the second one returns a\nstring for some reason, you recall that the month of October is identified by the number ",[147,6718,4096],{}," because we start counting\nthe months starting at ",[147,6721,6722],{},"0"," and quickly figure out that subtracting two ",[147,6725,6704],{}," objects results in a number which is the\namount of milliseconds passed between two moments.",[234,6728,6730],{"className":1239,"code":6729,"language":1241,"meta":141,"style":141},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = new Date(2012, 9, 27);\nvar d2 = new Date(2012, 9, 28);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\n\n",[147,6731,6732,6736,6741,6746,6751],{"__ignoreMap":141},[242,6733,6734],{"class":244,"line":245},[242,6735,389],{"emptyLinePlaceholder":388},[242,6737,6738],{"class":244,"line":277},[242,6739,6740],{},"var DAY_IN_MS = 24 * 60 * 60 * 1000;\n",[242,6742,6743],{"class":244,"line":305},[242,6744,6745],{},"var d1 = new Date(2012, 9, 27);\n",[242,6747,6748],{"class":244,"line":327},[242,6749,6750],{},"var d2 = new Date(2012, 9, 28);\n",[242,6752,6753],{"class":244,"line":359},[242,6754,6755],{},"console.log((d2 - d1) / DAY_IN_MS); // yields 1\n",[18,6757,6758],{},"Looks fine, doesn’t it? So just wrap it in a function, unit-test it and be done with it? Not so fast there. Let’s just\nchange the dates ever so slightly",[234,6760,6762],{"className":1239,"code":6761,"language":1241,"meta":141,"style":141},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = new Date(2012, 9, 27);\nvar d2 = new Date(2012, 9, 28);\nvar d3 = new Date(2012, 9, 29);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\nconsole.log((d3 - d2) / DAY_IN_MS); // yields 1.0416666666666667\n\n",[147,6763,6764,6768,6772,6776,6780,6785,6789],{"__ignoreMap":141},[242,6765,6766],{"class":244,"line":245},[242,6767,389],{"emptyLinePlaceholder":388},[242,6769,6770],{"class":244,"line":277},[242,6771,6740],{},[242,6773,6774],{"class":244,"line":305},[242,6775,6745],{},[242,6777,6778],{"class":244,"line":327},[242,6779,6750],{},[242,6781,6782],{"class":244,"line":359},[242,6783,6784],{},"var d3 = new Date(2012, 9, 29);\n",[242,6786,6787],{"class":244,"line":365},[242,6788,6755],{},[242,6790,6791],{"class":244,"line":375},[242,6792,6793],{},"console.log((d3 - d2) / DAY_IN_MS); // yields 1.0416666666666667\n",[18,6795,6796],{},"This is the point where most developers start cursing. Is this a new way in which JavaScript is broken? It isn’t,\nbecause the number is completely accurate.",[18,6798,6799,6800,6803,6804,464,6807,6810],{},"The JavaScript object created by ",[147,6801,6802],{},"new Date(2012, 9, 28)"," represents midnight on the 28th of October, 2012 ",[89,6805,6806],{},"in your local\ntime zone",[147,6808,6809],{},"new Date(2012, 9, 29)"," represents midnight the following day.",[18,6812,6813,6814,452],{},"Subtracting the first from the seconds yields the number of milliseconds that have passed between those two moments,\nwhich, as you probably have guessed, includes the extra hour put in because\nof ",[24,6815,6818],{"href":6816,"rel":6817},"http://www.timeanddate.com/worldclock/clockchange.html?n=37",[28],"daylight savings time",[234,6820,6822],{"className":1239,"code":6821,"language":1241,"meta":141,"style":141},"\n> new Date(2012, 9, 29);\nMon Oct 29 2012 00:00:00 GMT+0100 (CET)\n> new Date(2012, 9, 28);\nSun Oct 28 2012 00:00:00 GMT+0200 (CEST)\n> (new Date(2012, 9, 29) - new Date(2012, 9, 28)) / 60 / 60 / 100\n25\n\n",[147,6823,6824,6828,6833,6838,6843,6848,6853],{"__ignoreMap":141},[242,6825,6826],{"class":244,"line":245},[242,6827,389],{"emptyLinePlaceholder":388},[242,6829,6830],{"class":244,"line":277},[242,6831,6832],{},"> new Date(2012, 9, 29);\n",[242,6834,6835],{"class":244,"line":305},[242,6836,6837],{},"Mon Oct 29 2012 00:00:00 GMT+0100 (CET)\n",[242,6839,6840],{"class":244,"line":327},[242,6841,6842],{},"> new Date(2012, 9, 28);\n",[242,6844,6845],{"class":244,"line":359},[242,6846,6847],{},"Sun Oct 28 2012 00:00:00 GMT+0200 (CEST)\n",[242,6849,6850],{"class":244,"line":365},[242,6851,6852],{},"> (new Date(2012, 9, 29) - new Date(2012, 9, 28)) / 60 / 60 / 100\n",[242,6854,6855],{"class":244,"line":375},[242,6856,6857],{},"25\n",[18,6859,6860],{},"So where is the error? The error is in our assumption that a day has 24 hours, because depending on how you define a\nday, it hasn’t – October 28th 2012 has 25 hours.",[18,6862,6863,6864,470,6867,6871,6872,1402,6876,6880],{},"If you Google “JavaScript time difference”, most people just use\n",[147,6865,6866],{},"Math.round",[24,6868,1594],{"href":6869,"rel":6870},"http://psoug.org/snippet/Javascript-Calculate-time-difference-between-two-dates_116.htm",[28],") or simply\nuse flat-out buggy\ncode (",[24,6873,1594],{"href":6874,"rel":6875},"https://web.archive.org/web/20151119010127/http://www-10.lotus.com:80/ldd/ddwiki.nsf/dx/Various_Time_Differences_in_JavaScript",[28],[24,6877,3902],{"href":6878,"rel":6879},"http://www.javascriptkit.com/javatutors/datedifference.shtml",[28],")\nand call it a day (pun intended), but that is not how we roll here.",[18,6882,6883,6884,6887,6888,6891],{},"What do we really mean when we ask ",[89,6885,6886],{},"“How many days have passed between two dates in the calendar”","? We usually mean\n",[89,6889,6890],{},"“How many midnights have happened between these two dates?”",". Unfortunately, because of DST, you can’t just use the\nnumber of milliseconds between two dates at midnight to calculate how many midnights have happened, because some of them\nare more or less than 24 hours apart. If only there was a magical place that doesn’t have this madness going on…",[18,6893,6894,6895,6900],{},"Luckily, there is, and that place is ",[24,6896,6899],{"href":6897,"rel":6898},"http://en.wikipedia.org/wiki/Coordinated_Universal_Time",[28],"UTC",". UTC is a time\nmeasuring system that does not have daylight savings time.",[18,6902,6903,1402,6906],{},[211,6904,6905],{},"Edit:",[89,6907,6908,6909,6914,6915,452],{},"as pointed out in the comments, the rabbit hole goes down even further – officially, even in UTC, a day might\nhave more than 24 hours because of leap seconds. Fortunately for us, the ECMA-262\nspecification ",[24,6910,6913],{"href":6911,"rel":6912},"http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.1",[28],"explicitly ignores leap seconds"," and we can\ngo about our business. If JavaScript would implement UTC correctly, we would have to account for leap seconds or\nuse ",[24,6916,6919],{"href":6917,"rel":6918},"http://en.wikipedia.org/wiki/Universal_Time#Versions",[28],"UT1",[18,6921,6922,6923,6925,6926,6928,6929,1402,6931,6934],{},"The JavaScript Date API is just as beautiful as most other JavaScript APIs: While the only useful use of the ",[147,6924,6704],{},"\nobject is by using it as a constructor (with ",[147,6927,4575],{},"), the way to use UTC is by using the ",[89,6930,1067],{},[147,6932,6933],{},"Date.UTC"," which\nreturns a unix timestamp. This is the JavaScript time API in a nutshell:",[234,6936,6938],{"className":1239,"code":6937,"language":1241,"meta":141,"style":141},"\n> new Date(2012, 9, 29);\nMon Oct 29 2012 00:00:00 GMT+0100 (CET) // (a somewhat useful object)\n> Date(2012, 9, 29);\n'Mon Nov 05 2012 16:18:12 GMT+0100 (CET)' // (a string - no relation to the parameters)\n> Date.UTC(2012, 9, 29);\n1351468800000 // (unix time in milliseconds)\n> new Date.UTC(2012, 9, 29); // failure\nTypeError: function UTC() { [native code] } is not a constructor\n at repl:1:9\n [....]\n\n",[147,6939,6940,6944,6948,6953,6958,6966,6971,6976,6981,6986,6991],{"__ignoreMap":141},[242,6941,6942],{"class":244,"line":245},[242,6943,389],{"emptyLinePlaceholder":388},[242,6945,6946],{"class":244,"line":277},[242,6947,6832],{},[242,6949,6950],{"class":244,"line":305},[242,6951,6952],{},"Mon Oct 29 2012 00:00:00 GMT+0100 (CET) // (a somewhat useful object)\n",[242,6954,6955],{"class":244,"line":327},[242,6956,6957],{},"> Date(2012, 9, 29);\n",[242,6959,6960,6963],{"class":244,"line":359},[242,6961,6962],{},"'Mon Nov 05 2012 16:18:12 GMT+0100 (CET)'",[242,6964,6965],{}," // (a string - no relation to the parameters)\n",[242,6967,6968],{"class":244,"line":365},[242,6969,6970],{},"> Date.UTC(2012, 9, 29);\n",[242,6972,6973],{"class":244,"line":375},[242,6974,6975],{},"1351468800000 // (unix time in milliseconds)\n",[242,6977,6978],{"class":244,"line":385},[242,6979,6980],{},"> new Date.UTC(2012, 9, 29); // failure\n",[242,6982,6983],{"class":244,"line":392},[242,6984,6985],{},"TypeError: function UTC() { [native code] } is not a constructor\n",[242,6987,6988],{"class":244,"line":550},[242,6989,6990],{}," at repl:1:9\n",[242,6992,6993],{"class":244,"line":555},[242,6994,6995],{}," [....]\n",[18,6997,6998,6999,7001],{},"The correct calculation, without using ",[147,7000,6866],{}," or other hacks therefore is",[234,7003,7005],{"className":1239,"code":7004,"language":1241,"meta":141,"style":141},"\nvar DAY_IN_MS = 24 * 60 * 60 * 1000;\nvar d1 = Date.UTC(2012, 9, 27);\nvar d2 = Date.UTC(2012, 9, 28);\nvar d3 = Date.UTC(2012, 9, 29);\nconsole.log((d2 - d1) / DAY_IN_MS); // yields 1\nconsole.log((d3 - d2) / DAY_IN_MS); // yields 1\n\n",[147,7006,7007,7011,7015,7020,7025,7030,7034],{"__ignoreMap":141},[242,7008,7009],{"class":244,"line":245},[242,7010,389],{"emptyLinePlaceholder":388},[242,7012,7013],{"class":244,"line":277},[242,7014,6740],{},[242,7016,7017],{"class":244,"line":305},[242,7018,7019],{},"var d1 = Date.UTC(2012, 9, 27);\n",[242,7021,7022],{"class":244,"line":327},[242,7023,7024],{},"var d2 = Date.UTC(2012, 9, 28);\n",[242,7026,7027],{"class":244,"line":359},[242,7028,7029],{},"var d3 = Date.UTC(2012, 9, 29);\n",[242,7031,7032],{"class":244,"line":365},[242,7033,6755],{},[242,7035,7036],{"class":244,"line":375},[242,7037,7038],{},"console.log((d3 - d2) / DAY_IN_MS); // yields 1\n",[18,7040,7041,7042,7045,7046,7051],{},"These kinds of bugs are sneaky because they only show up for certain input values (I wonder if I would have noticed it\nif I hadn’t tested the code last week around the DST change) and usually don’t show up in unit tests unless you happen\nto know what you are looking for. The results are often ",[89,7043,7044],{},"nearly"," correct, and we are not used to thinking about time\nzones and\nwe ",[24,7047,7050],{"href":7048,"rel":7049},"http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time",[28],"often hold invalid assumptions about time",".\nAlways using UTC isn’t a solution either, because sometimes we want the local time zone to be considered.",[18,7053,7054,7055,7060,7061,7063],{},"Libraries like ",[24,7056,7059],{"href":7057,"rel":7058},"http://momentjs.com/",[28],"Moment.js"," help, but the real protection against these kinds of bugs is to know\nabout time zones, time measurement system and thinking about what you are actually calculating instead of simply\nthrowing a ",[147,7062,6866],{}," in there to make it all work.",[18,7065,7066,7067,7070,7071,7074],{},"Just as anybody that has had the pleasure of seeing ",[89,7068,7069],{},"Rent"," will tell you: while a year has ",[89,7072,7073],{},"five hundred twenty-five\nthousand six hundred minutes",", it still is difficult to measure the time of the year.",[763,7076,2775],{},{"title":141,"searchDepth":277,"depth":277,"links":7078},[],[775],"2012-11-05T17:05:23","Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the Dateobject and do\\nsome calculating.","https://synyx.de/blog/properly-calculating-time-differences-in-javascript/",{},"/blog/properly-calculating-time-differences-in-javascript",{"title":6692,"description":7086},"Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We\nwant to calculate the difference between two dates, measured in days. Easy, you say, just use the Dateobject and do\nsome calculating.","blog/properly-calculating-time-differences-in-javascript",[7089,415,7090,7091],"date","time","utc","Let me tell you a tale about a fat-client application that has nice some time-related logic written in JavaScript. We want to calculate the difference between two dates, measured in…","ni8qXO9StrZax4AqNEra8EirfvOftQEpRiXb9e9ojTo",{"id":7095,"title":7096,"author":7097,"body":7099,"category":7364,"date":7366,"description":7367,"extension":779,"link":7368,"meta":7369,"navigation":388,"path":7370,"seo":7371,"slug":7103,"stem":7373,"tags":7374,"teaser":7378,"__hash__":7379},"blog/blog/on-cross-device-mobile-development-part-2.md","On cross-device mobile development – Part 2",[7098],"krupicka",{"type":11,"value":7100,"toc":7357},[7101,7104,7117,7121,7144,7148,7180,7184,7208,7212,7219,7223,7226,7230,7234,7259,7263,7282,7286,7301,7305,7337,7341,7354],[14,7102,7096],{"id":7103},"on-cross-device-mobile-development-part-2",[18,7105,7106,7107,7112,7113,7116],{},"In the ",[24,7108,7111],{"href":7109,"rel":7110},"http://mobile.synyx.de/2010/08/on-cross-device-mobile-development-part-1/",[28],"previous part"," of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as ",[89,7114,7115],{},"the web\nstack","). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …",[74,7118,7120],{"id":7119},"what-does-native-have-what-web-stack-hasnt","What does native have, what web stack hasn’t?",[18,7122,7123,7124,7127,7128,7131,7132,7135,7136,7139,7140,7143],{},"If you want the answer to this question boiled down to one simple word: ",[211,7125,7126],{},"abstraction",". In an application you don’t\nwant to think in terms of ",[147,7129,7130],{},"\u003Cul>"," elements, to which you append ",[147,7133,7134],{},"\u003Cli>"," elements, styled adequately with CSS to harbor\nitems populated from SQLite statements and react to code which is painstakingly attached to every list item to react to\ndifferent user inputs. You would usually think about the problem in terms of — for example — a ",[147,7137,7138],{},"ListController"," and an\nattached ",[147,7141,7142],{},"DataSource",", freeing you from the task of layouting your list. And you don’t want to have to code all this\nboilerplate from scratch. It is not the problem you want to solve. It is something left for a framework.",[74,7145,7147],{"id":7146},"competing-with-native-sdks","Competing with native SDKs",[18,7149,7150,7151,7156,7157,7162,7163,7165,7166,7171,7172,7175,7176,7179],{},"Even before the days of mobile applications, web application developers pushed hard to create frameworks to match up\nagainst their competitors from the native world. An increasing number of those are now also reconsidering deployment on\nsmall mobile devices and are joined by new JavaScript frameworks especially tailored to mobile\ndevices. ",[24,7152,7155],{"href":7153,"rel":7154},"https://web.archive.org/web/20210302121558/https://phonegap.com/",[28],"PhoneGap"," — which we refered to already in\nthe first part of the series — is\nnow ",[24,7158,7161],{"href":7159,"rel":7160},"https://web.archive.org/web/20140122061413/http://phonegap.com/2010/07/19/it%e2%80%99s-easier-than-ever-for-symbian-developers-to-build-mobile-apps-with-phonegap/",[28],"fully embraced","\nby Nokia for their Symbian smartphone operating system. Palm has his whole mobile user experience built around ",[89,7164,7115],{},", accordingly naming their operating system ",[24,7167,7170],{"href":7168,"rel":7169},"http://developer.palm.com/",[28],"WebOS",". The need for mobile ",[89,7173,7174],{},"web stack","\nframeworks is growing. The rest of this article will introduce some of those frameworks, but first a short recap of what\n",[211,7177,7178],{},"abstractions"," we would expect from such a framework, compared to their native siblings:",[1232,7181,7183],{"id":7182},"models","Models",[18,7185,7186,7187,7192,7193,7200,7201,452],{},"The way the data is retrieved and stored on the device comprises our data model. Because of constraints in memory\nfootprint and also because of security considerations, mobile SDKs should try to provide abstraction layers for your\ndata storage. As an example, the iPhone SDK incorporates a subsystem\ncalled ",[24,7188,7191],{"href":7189,"rel":7190},"http://developer.apple.com/iphone/library/documentation/DataManagement/Conceptual/iPhoneCoreData01/",[28],"CoreData",".\nIt provides modelling tools and stub generation to easily integrate with ",[24,7194,7197],{"href":7195,"rel":7196},"http://developer.apple.com/iphone/library/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html",[28],[147,7198,7199],{},"UITableView","\ncontrols. Android, while staying more on the lower SQLite level, also provides similar integration to their native ",[24,7202,7205],{"href":7203,"rel":7204},"http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html",[28],[147,7206,7207],{},"ListView",[1232,7209,7211],{"id":7210},"views-user-interface-ui-elements","Views & user interface (UI) elements",[18,7213,7214,7218],{},[139,7215],{"alt":7216,"src":7217},"\"iPhone UI Controls\"","https://media.synyx.de/uploads//2010/07/iPhone-UI-Controls-e1280510112988.png","\nEvery mobile SDK contains a selection of useful UI elements: buttons, sliders, text entry controls, switches and on a\nlarger scale prebuilt view elements for tabular data, images, display of rich text or maps for geolocative services.\nUsually every of those elements or views exhibits a callback interface tailored towards the particular item. These can\nbecome quite sophisticated: views for tabular data call back for touches on a particular row and/or column or map views\nrequesting to redraw the viewport according to new coordinates after scrolling occured.",[1232,7220,7222],{"id":7221},"controllers","Controllers",[18,7224,7225],{},"The backbone of every applications, they are the heavy lifters which glue your models and views together. Often the\ntasks to be done — for example managing the display of overviews and increasingly detailled item views or also an\neditable table view of data from an underlying data source — are abstract and common enough, that a selection of generic\ncontrollers can eliminate boilerplate code and provide a feature rich set of actions out of the box.",[716,7227,7229],{"id":7228},"javascript-frameworks-the-small-the-huge-and-the-familiar","JavaScript frameworks … the small, the huge and the familiar",[1232,7231,7233],{"id":7232},"jqtouch","jQTouch",[18,7235,7236,7237,7241,7242,7247,7248,7255,7256,7258],{},"Our first framework is perhaps the one which contradicts our demands the most. ",[24,7238,7233],{"href":7239,"rel":7240},"http://jqtouch.com",[28]," is a\nlightweight layer on the ever-present ",[24,7243,7246],{"href":7244,"rel":7245},"http://jquery.com",[28],"jQuery"," library. And with lightweight we are talking about a\nmeager 577 source lines of code. Obviously one cannot expect a lot of the desired abstractions, yet still jQTouch has\nits own eligibility. It provides mainly an implementation of one of the most common view abstractions — a stack of menus\nand toolbars to navigate forward and backward — bundled with finely tuned themes for an iPhone-like or more generic\nmobile UI. While you still have to provide the code for more complex controllers and also data model abstraction, it can\nbe used for those cases, where an already deployed web application employing jQuery, should be quickly ported to a\nmobile device. Furthermore it features resource-preloading as also access to geolocation APIs in mobile WebKit\nimplementations. The missing model and controller code can be adapted from existing desktop browser jQuery extensions,\nwhich are numerous. It is now maintained by Jonathan Stark, whose book ",[24,7249,7252],{"href":7250,"rel":7251},"http://building-iphone-apps.labs.oreilly.com/",[28],[89,7253,7254],{},"Building iPhone Apps with HTML, CSS &\nJavaScript"," we already mentioned in part one of this article series and\nwhich is still a great resource for ",[89,7257,7174],{}," development on mobile devices.",[1232,7260,7262],{"id":7261},"sencha-touch","Sencha Touch",[18,7264,7265,7270,7271,7275,7276,7281],{},[24,7266,7269],{"href":7267,"rel":7268},"http://www.sencha.com/",[28],"Sencha Labs"," (formerly ExtJS) are already well-known for their desktop browser frameworks.\nWith ",[24,7272,7262],{"href":7273,"rel":7274},"http://www.sencha.com/products/touch/",[28]," they provide abstractions for many of the use cases we talked\nabout above. This includes controllers for tool- or tabbar navigation, data sources which can be attached to analogous\nviews — which then are updated automatically, common entry controls like textfields or date pickers, map views and media\nlike audio and video. Every control provided has an extensive list of event callbacks, that you can easily attach your\nfunctions to. From the ",[24,7277,7280],{"href":7278,"rel":7279},"http://www.sencha.com/products/touch/demos.php",[28],"demos"," it becomes clear that they also push hard\ntowards the emerging tablet devices by providing a full-stack framework for even sophisticated applications. It should\nbe noted though, that it is dual-licensed, with commercial use entailing a (small) fee.",[1232,7283,7285],{"id":7284},"jquery-mobile","jQuery mobile",[18,7287,7288,7289,7294,7295,7300],{},"For people, who in the past have worked with ",[24,7290,7293],{"href":7291,"rel":7292},"http://jqueryui.com",[28],"jQuery UI"," for web applications, John Resig recently\nannounced ",[24,7296,7299],{"href":7297,"rel":7298},"http://jquerymobile.com/",[28],"jQuery Mobile",". While it is still in planning & internal beta, it could prove as\nthe missing link from the formerly mentioned jQTouch. Projecting from the existing jQuery UI framework, this would\nprovide a good amount of UI controls and common view abstractions, while maintaining the slick feeling of traditional\njQuery programming. It will also come with themes, which match typical native mobile controls and a strong mission\nstatement to make it a real cross-platform alternative, beyond the iOS platform usually targeted by other frameworks.",[1232,7302,7304],{"id":7303},"google-web-toolkit","Google Web Toolkit",[18,7306,7307,7308,7312,7313,7318,7319,7324,7325,7330,7331,7336],{},"Last but definitely not least, we have to include an old familiar\nfriend: ",[24,7309,7304],{"href":7310,"rel":7311},"http://code.google.com/webtoolkit/",[28]," (GWT) has already matured for desktop browsers. It also\nhas something to provide, which other JavaScript frameworks lack: extensive IDE support with a strictly typed language\nunderneath. Especially Android developers will feel more at home, when developing with Java. While originally targeted\nat desktop browsers, its extensibility makes it easy to retarget mobile\nplatforms. ",[24,7314,7317],{"href":7315,"rel":7316},"http://code.google.com/p/gwt-mobile-webkit/",[28],"GWT Mobile WebKit"," extends GWT with support for touch\ninterfaces and also bundles libraries to\nabstract ",[24,7320,7323],{"href":7321,"rel":7322},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Geolocation",[28],"geolocation services"," and\nthe ",[24,7326,7329],{"href":7327,"rel":7328},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Database",[28],"SQLite"," database — something even\nmissing from the original Android SDK. Views can be created programmatically\nor ",[24,7332,7335],{"href":7333,"rel":7334},"http://code.google.com/webtoolkit/doc/latest/DevGuideUiBinder.html",[28],"declaratively"," and provide a rich API for\ncallbacks on user input.",[74,7338,7340],{"id":7339},"what-is-left-to-be-said","What is left to be said",[18,7342,7343,7344,7346,7347,7349,7350,7353],{},"The ",[89,7345,7174],{}," mobile development community is certainly on the move to sophisticated frameworks. From small\nmicroframeworks to full-stack frameworks like ",[89,7348,7262],{}," or ",[89,7351,7352],{},"GWT",", a range of tastes for different development\nstyles is served. While we tried to give a bigger perspective on what is available, we still haven’t even touched topics\nlike the upcoming HTML5 — which together with CSS3 will bring fast animations and sophisticated graphics — or actual\ncross-device behaviour. What can be definitely said is, that these developments — wether or not one decides to jump on\nthis bandwagon — increase the diversity of mobile development and provide new insights in how we think about mobile\napplication development.",[18,7355,7356],{},"To be continued some time …",{"title":141,"searchDepth":277,"depth":277,"links":7358},[7359,7360,7363],{"id":7119,"depth":277,"text":7120},{"id":7146,"depth":277,"text":7147,"children":7361},[7362],{"id":7228,"depth":305,"text":7229},{"id":7339,"depth":277,"text":7340},[7365],"mobile-blog","2010-09-07T12:00:39","In the previous part of this series we took\\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\\nfor, shouldn’t it? Not quite …","https://synyx.de/blog/on-cross-device-mobile-development-part-2/",{},"/blog/on-cross-device-mobile-development-part-2",{"title":7096,"description":7372},"In the previous part of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …","blog/on-cross-device-mobile-development-part-2",[7375,7376,238,7377,415],"android","css","iphone","In the previous part of this series we took a look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web stack). A…","YieTuihm_5oZzpoNHWVn3JDuz9uBqr4EfoBq26hjIl0",{"id":7381,"title":7382,"author":7383,"body":7384,"category":8914,"date":8916,"description":8917,"extension":779,"link":8918,"meta":8919,"navigation":388,"path":8920,"seo":8921,"slug":7388,"stem":8923,"tags":8924,"teaser":8925,"__hash__":8926},"blog/blog/on-cross-device-mobile-development-part-1.md","On cross-device mobile development – Part 1",[7098],{"type":11,"value":7385,"toc":8908},[7386,7389,7396,7400,7403,7407,7416,7721,7724,8425,8428,8629,8640,8687,8690,8858,8862,8871,8874,8881,8885,8888,8902,8905],[14,7387,7382],{"id":7388},"on-cross-device-mobile-development-part-1",[18,7390,7391,7392,7395],{},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. ",[89,7393,7394],{},"That’s the theory at least."," In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.",[74,7397,7399],{"id":7398},"its-not-in-the-browser","It’s (not) in the browser",[18,7401,7402],{},"Developing applications with HTML, CSS & Javascript is a fundamentally different experience. It already is on the\nnormal desktop with its widescreen browsers. On a mobile device it becomes a game changer. We are accustomed to\ngraphical user interface (GUI) toolkits with their more direct manipulation of data through on-screen elements like\nbuttons. While browsers and HTML provide their own set of UI elements and the ability to wire events like clicks to\nJavascript callbacks, they lack a lot of the bells and whistles. Especially in the handling of client-side data — the\nmodel in model-view-controller (MVC) so to say — and the controller abstraction even modern browsers lack the feature\nrichness of common native SDKs. In the rest of this article we will give a short overview on which web technologies map\nto which concept in the MVC approach and how you would basically structure an application based on such technologies.",[74,7404,7406],{"id":7405},"of-models-views-controllers","Of models, views & controllers",[18,7408,7409,7410,7415],{},"In ",[24,7411,7414],{"href":7412,"rel":7413},"http://building-iphone-apps.labs.oreilly.com/ch04.html",[28],"“Building iPhone Apps with HTML, CSS and JavaScript”","\nJonathan Stark describes how to turn websites into single page iPhone applications. Views are implemented straight\nforward with plain HTML and CSS, throwing in some bits and pieces to make browser standard behavior of HTML elements\nmore native like. To give an example, we will show how to create a graphical navigation bar and switch between different\nscreens. First we need some HTML and CSS snippets to create the layout:",[234,7417,7419],{"className":236,"code":7418,"language":238,"meta":141,"style":141},"\u003Cul id=\"navigation\">\n \u003Cli>\u003Ca class=\"current\" href=\"#home\">Home\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#new\">New things\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#favorites\">I like those\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#more\">More content\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#info\">About\u003C/a>\u003C/li>\n\u003C/ul>\n\u003Cdiv class=\"view\" id=\"home\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"new\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"favorites\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"more\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"info\">...\u003C/div>\n",[147,7420,7421,7436,7474,7502,7530,7558,7586,7594,7621,7646,7671,7696],{"__ignoreMap":141},[242,7422,7423,7425,7427,7429,7431,7434],{"class":244,"line":245},[242,7424,249],{"class":248},[242,7426,82],{"class":252},[242,7428,399],{"class":255},[242,7430,259],{"class":248},[242,7432,7433],{"class":262},"\"navigation\"",[242,7435,274],{"class":248},[242,7437,7438,7440,7442,7445,7447,7450,7452,7455,7458,7460,7463,7466,7468,7470,7472],{"class":244,"line":277},[242,7439,280],{"class":248},[242,7441,85],{"class":252},[242,7443,7444],{"class":248},">\u003C",[242,7446,24],{"class":252},[242,7448,7449],{"class":255}," class",[242,7451,259],{"class":248},[242,7453,7454],{"class":262},"\"current\"",[242,7456,7457],{"class":255}," href",[242,7459,259],{"class":248},[242,7461,7462],{"class":262},"\"#home\"",[242,7464,7465],{"class":248},">Home\u003C/",[242,7467,24],{"class":252},[242,7469,406],{"class":248},[242,7471,85],{"class":252},[242,7473,274],{"class":248},[242,7475,7476,7478,7480,7482,7484,7486,7488,7491,7494,7496,7498,7500],{"class":244,"line":305},[242,7477,280],{"class":248},[242,7479,85],{"class":252},[242,7481,7444],{"class":248},[242,7483,24],{"class":252},[242,7485,7457],{"class":255},[242,7487,259],{"class":248},[242,7489,7490],{"class":262},"\"#new\"",[242,7492,7493],{"class":248},">New things\u003C/",[242,7495,24],{"class":252},[242,7497,406],{"class":248},[242,7499,85],{"class":252},[242,7501,274],{"class":248},[242,7503,7504,7506,7508,7510,7512,7514,7516,7519,7522,7524,7526,7528],{"class":244,"line":327},[242,7505,280],{"class":248},[242,7507,85],{"class":252},[242,7509,7444],{"class":248},[242,7511,24],{"class":252},[242,7513,7457],{"class":255},[242,7515,259],{"class":248},[242,7517,7518],{"class":262},"\"#favorites\"",[242,7520,7521],{"class":248},">I like those\u003C/",[242,7523,24],{"class":252},[242,7525,406],{"class":248},[242,7527,85],{"class":252},[242,7529,274],{"class":248},[242,7531,7532,7534,7536,7538,7540,7542,7544,7547,7550,7552,7554,7556],{"class":244,"line":359},[242,7533,280],{"class":248},[242,7535,85],{"class":252},[242,7537,7444],{"class":248},[242,7539,24],{"class":252},[242,7541,7457],{"class":255},[242,7543,259],{"class":248},[242,7545,7546],{"class":262},"\"#more\"",[242,7548,7549],{"class":248},">More content\u003C/",[242,7551,24],{"class":252},[242,7553,406],{"class":248},[242,7555,85],{"class":252},[242,7557,274],{"class":248},[242,7559,7560,7562,7564,7566,7568,7570,7572,7575,7578,7580,7582,7584],{"class":244,"line":365},[242,7561,280],{"class":248},[242,7563,85],{"class":252},[242,7565,7444],{"class":248},[242,7567,24],{"class":252},[242,7569,7457],{"class":255},[242,7571,259],{"class":248},[242,7573,7574],{"class":262},"\"#info\"",[242,7576,7577],{"class":248},">About\u003C/",[242,7579,24],{"class":252},[242,7581,406],{"class":248},[242,7583,85],{"class":252},[242,7585,274],{"class":248},[242,7587,7588,7590,7592],{"class":244,"line":375},[242,7589,378],{"class":248},[242,7591,82],{"class":252},[242,7593,274],{"class":248},[242,7595,7596,7598,7600,7602,7604,7607,7609,7611,7614,7617,7619],{"class":244,"line":385},[242,7597,249],{"class":248},[242,7599,223],{"class":252},[242,7601,7449],{"class":255},[242,7603,259],{"class":248},[242,7605,7606],{"class":262},"\"view\"",[242,7608,399],{"class":255},[242,7610,259],{"class":248},[242,7612,7613],{"class":262},"\"home\"",[242,7615,7616],{"class":248},">...\u003C/",[242,7618,223],{"class":252},[242,7620,274],{"class":248},[242,7622,7623,7625,7627,7629,7631,7633,7635,7637,7640,7642,7644],{"class":244,"line":392},[242,7624,249],{"class":248},[242,7626,223],{"class":252},[242,7628,7449],{"class":255},[242,7630,259],{"class":248},[242,7632,7606],{"class":262},[242,7634,399],{"class":255},[242,7636,259],{"class":248},[242,7638,7639],{"class":262},"\"new\"",[242,7641,7616],{"class":248},[242,7643,223],{"class":252},[242,7645,274],{"class":248},[242,7647,7648,7650,7652,7654,7656,7658,7660,7662,7665,7667,7669],{"class":244,"line":550},[242,7649,249],{"class":248},[242,7651,223],{"class":252},[242,7653,7449],{"class":255},[242,7655,259],{"class":248},[242,7657,7606],{"class":262},[242,7659,399],{"class":255},[242,7661,259],{"class":248},[242,7663,7664],{"class":262},"\"favorites\"",[242,7666,7616],{"class":248},[242,7668,223],{"class":252},[242,7670,274],{"class":248},[242,7672,7673,7675,7677,7679,7681,7683,7685,7687,7690,7692,7694],{"class":244,"line":555},[242,7674,249],{"class":248},[242,7676,223],{"class":252},[242,7678,7449],{"class":255},[242,7680,259],{"class":248},[242,7682,7606],{"class":262},[242,7684,399],{"class":255},[242,7686,259],{"class":248},[242,7688,7689],{"class":262},"\"more\"",[242,7691,7616],{"class":248},[242,7693,223],{"class":252},[242,7695,274],{"class":248},[242,7697,7698,7700,7702,7704,7706,7708,7710,7712,7715,7717,7719],{"class":244,"line":571},[242,7699,249],{"class":248},[242,7701,223],{"class":252},[242,7703,7449],{"class":255},[242,7705,259],{"class":248},[242,7707,7606],{"class":262},[242,7709,399],{"class":255},[242,7711,259],{"class":248},[242,7713,7714],{"class":262},"\"info\"",[242,7716,7616],{"class":248},[242,7718,223],{"class":252},[242,7720,274],{"class":248},[18,7722,7723],{},"The navigational items are then replaced with nice button graphics via CSS and positioned on the page.",[234,7725,7728],{"className":7726,"code":7727,"language":7376,"meta":141,"style":141},"language-css shiki shiki-themes github-light github-dark","/* navigation is a fixed block */\n#navigation {\n position: fixed;\n top: 0px;\n left: 0px;\n width: 320px;\n margin: 0;\n padding: 0;\n}\n#navigation li {\n display: block;\n position: absolute;\n}\n#navigation li a {\n display: block;\n position: absolute;\n height: 48px;\n width: 80px;\n top: 0px;\n text-indent: -9999px;\n}\na[href=\"#home\"] {\n background: url(home.png);\n left: 0px;\n}\na[href=\"#home\"].current {\n background: url(home-current.png);\n}\na[href=\"#new\"] {\n background: url(new.png);\n left: 80px;\n}\na[href=\"#new\"].current {\n background: url(new-current.png);\n}\na[href=\"#favorites\"] {\n background: url(favorites.png);\n left: 160px;\n}\na[href=\"#favorites\"].current {\n background: url(favorites-current.png);\n}\na[href=\"#more\"] {\n background: url(more.png);\n left: 240px;\n}\na[href=\"#info\"] {\n background: url(info.png);\n position: fixed;\n width: 48px;\n height: 48px;\n bottom: 16px;\n right: 16px;\n}\n/* finally position the views themselves */\ndiv.view {\n position: absolute;\n top: 48px;\n left: 0px;\n width: 320px;\n height: 396px;\n}\n",[147,7729,7730,7735,7742,7755,7769,7782,7796,7807,7818,7822,7831,7843,7854,7858,7869,7879,7889,7903,7916,7928,7942,7946,7963,7980,7992,7996,8016,8031,8035,8049,8064,8076,8080,8098,8113,8117,8131,8146,8159,8163,8181,8196,8200,8214,8229,8242,8246,8260,8275,8285,8297,8309,8322,8335,8340,8346,8356,8367,8380,8393,8406,8420],{"__ignoreMap":141},[242,7731,7732],{"class":244,"line":245},[242,7733,7734],{"class":1097},"/* navigation is a fixed block */\n",[242,7736,7737,7740],{"class":244,"line":277},[242,7738,7739],{"class":255},"#navigation",[242,7741,435],{"class":248},[242,7743,7744,7747,7750,7753],{"class":244,"line":305},[242,7745,7746],{"class":448}," position",[242,7748,7749],{"class":248},": ",[242,7751,7752],{"class":448},"fixed",[242,7754,1111],{"class":248},[242,7756,7757,7760,7762,7764,7767],{"class":244,"line":327},[242,7758,7759],{"class":448}," top",[242,7761,7749],{"class":248},[242,7763,6722],{"class":448},[242,7765,7766],{"class":422},"px",[242,7768,1111],{"class":248},[242,7770,7771,7774,7776,7778,7780],{"class":244,"line":359},[242,7772,7773],{"class":448}," left",[242,7775,7749],{"class":248},[242,7777,6722],{"class":448},[242,7779,7766],{"class":422},[242,7781,1111],{"class":248},[242,7783,7784,7787,7789,7792,7794],{"class":244,"line":365},[242,7785,7786],{"class":448}," width",[242,7788,7749],{"class":248},[242,7790,7791],{"class":448},"320",[242,7793,7766],{"class":422},[242,7795,1111],{"class":248},[242,7797,7798,7801,7803,7805],{"class":244,"line":375},[242,7799,7800],{"class":448}," margin",[242,7802,7749],{"class":248},[242,7804,6722],{"class":448},[242,7806,1111],{"class":248},[242,7808,7809,7812,7814,7816],{"class":244,"line":385},[242,7810,7811],{"class":448}," padding",[242,7813,7749],{"class":248},[242,7815,6722],{"class":448},[242,7817,1111],{"class":248},[242,7819,7820],{"class":244,"line":392},[242,7821,547],{"class":248},[242,7823,7824,7826,7829],{"class":244,"line":550},[242,7825,7739],{"class":255},[242,7827,7828],{"class":252}," li",[242,7830,435],{"class":248},[242,7832,7833,7836,7838,7841],{"class":244,"line":555},[242,7834,7835],{"class":448}," display",[242,7837,7749],{"class":248},[242,7839,7840],{"class":448},"block",[242,7842,1111],{"class":248},[242,7844,7845,7847,7849,7852],{"class":244,"line":571},[242,7846,7746],{"class":448},[242,7848,7749],{"class":248},[242,7850,7851],{"class":448},"absolute",[242,7853,1111],{"class":248},[242,7855,7856],{"class":244,"line":583},[242,7857,547],{"class":248},[242,7859,7860,7862,7864,7867],{"class":244,"line":682},[242,7861,7739],{"class":255},[242,7863,7828],{"class":252},[242,7865,7866],{"class":252}," a",[242,7868,435],{"class":248},[242,7870,7871,7873,7875,7877],{"class":244,"line":688},[242,7872,7835],{"class":448},[242,7874,7749],{"class":248},[242,7876,7840],{"class":448},[242,7878,1111],{"class":248},[242,7880,7881,7883,7885,7887],{"class":244,"line":693},[242,7882,7746],{"class":448},[242,7884,7749],{"class":248},[242,7886,7851],{"class":448},[242,7888,1111],{"class":248},[242,7890,7891,7894,7896,7899,7901],{"class":244,"line":699},[242,7892,7893],{"class":448}," height",[242,7895,7749],{"class":248},[242,7897,7898],{"class":448},"48",[242,7900,7766],{"class":422},[242,7902,1111],{"class":248},[242,7904,7905,7907,7909,7912,7914],{"class":244,"line":2056},[242,7906,7786],{"class":448},[242,7908,7749],{"class":248},[242,7910,7911],{"class":448},"80",[242,7913,7766],{"class":422},[242,7915,1111],{"class":248},[242,7917,7918,7920,7922,7924,7926],{"class":244,"line":2061},[242,7919,7759],{"class":448},[242,7921,7749],{"class":248},[242,7923,6722],{"class":448},[242,7925,7766],{"class":422},[242,7927,1111],{"class":248},[242,7929,7930,7933,7935,7938,7940],{"class":244,"line":5191},[242,7931,7932],{"class":448}," text-indent",[242,7934,7749],{"class":248},[242,7936,7937],{"class":448},"-9999",[242,7939,7766],{"class":422},[242,7941,1111],{"class":248},[242,7943,7944],{"class":244,"line":5196},[242,7945,547],{"class":248},[242,7947,7948,7950,7953,7956,7958,7960],{"class":244,"line":5664},[242,7949,24],{"class":252},[242,7951,7952],{"class":248},"[",[242,7954,7955],{"class":255},"href",[242,7957,259],{"class":422},[242,7959,7462],{"class":262},[242,7961,7962],{"class":248},"] {\n",[242,7964,7965,7968,7970,7973,7975,7978],{"class":244,"line":5670},[242,7966,7967],{"class":448}," background",[242,7969,7749],{"class":248},[242,7971,7972],{"class":448},"url",[242,7974,458],{"class":248},[242,7976,7977],{"class":473},"home.png",[242,7979,1621],{"class":248},[242,7981,7982,7984,7986,7988,7990],{"class":244,"line":5676},[242,7983,7773],{"class":448},[242,7985,7749],{"class":248},[242,7987,6722],{"class":448},[242,7989,7766],{"class":422},[242,7991,1111],{"class":248},[242,7993,7994],{"class":244,"line":5682},[242,7995,547],{"class":248},[242,7997,7998,8000,8002,8004,8006,8008,8011,8014],{"class":244,"line":5688},[242,7999,24],{"class":252},[242,8001,7952],{"class":248},[242,8003,7955],{"class":255},[242,8005,259],{"class":422},[242,8007,7462],{"class":262},[242,8009,8010],{"class":248},"]",[242,8012,8013],{"class":255},".current",[242,8015,435],{"class":248},[242,8017,8018,8020,8022,8024,8026,8029],{"class":244,"line":5694},[242,8019,7967],{"class":448},[242,8021,7749],{"class":248},[242,8023,7972],{"class":448},[242,8025,458],{"class":248},[242,8027,8028],{"class":473},"home-current.png",[242,8030,1621],{"class":248},[242,8032,8033],{"class":244,"line":5700},[242,8034,547],{"class":248},[242,8036,8037,8039,8041,8043,8045,8047],{"class":244,"line":5706},[242,8038,24],{"class":252},[242,8040,7952],{"class":248},[242,8042,7955],{"class":255},[242,8044,259],{"class":422},[242,8046,7490],{"class":262},[242,8048,7962],{"class":248},[242,8050,8051,8053,8055,8057,8059,8062],{"class":244,"line":5712},[242,8052,7967],{"class":448},[242,8054,7749],{"class":248},[242,8056,7972],{"class":448},[242,8058,458],{"class":248},[242,8060,8061],{"class":473},"new.png",[242,8063,1621],{"class":248},[242,8065,8066,8068,8070,8072,8074],{"class":244,"line":5718},[242,8067,7773],{"class":448},[242,8069,7749],{"class":248},[242,8071,7911],{"class":448},[242,8073,7766],{"class":422},[242,8075,1111],{"class":248},[242,8077,8078],{"class":244,"line":5724},[242,8079,547],{"class":248},[242,8081,8082,8084,8086,8088,8090,8092,8094,8096],{"class":244,"line":5730},[242,8083,24],{"class":252},[242,8085,7952],{"class":248},[242,8087,7955],{"class":255},[242,8089,259],{"class":422},[242,8091,7490],{"class":262},[242,8093,8010],{"class":248},[242,8095,8013],{"class":255},[242,8097,435],{"class":248},[242,8099,8100,8102,8104,8106,8108,8111],{"class":244,"line":5736},[242,8101,7967],{"class":448},[242,8103,7749],{"class":248},[242,8105,7972],{"class":448},[242,8107,458],{"class":248},[242,8109,8110],{"class":473},"new-current.png",[242,8112,1621],{"class":248},[242,8114,8115],{"class":244,"line":5742},[242,8116,547],{"class":248},[242,8118,8119,8121,8123,8125,8127,8129],{"class":244,"line":5748},[242,8120,24],{"class":252},[242,8122,7952],{"class":248},[242,8124,7955],{"class":255},[242,8126,259],{"class":422},[242,8128,7518],{"class":262},[242,8130,7962],{"class":248},[242,8132,8133,8135,8137,8139,8141,8144],{"class":244,"line":5754},[242,8134,7967],{"class":448},[242,8136,7749],{"class":248},[242,8138,7972],{"class":448},[242,8140,458],{"class":248},[242,8142,8143],{"class":473},"favorites.png",[242,8145,1621],{"class":248},[242,8147,8148,8150,8152,8155,8157],{"class":244,"line":5760},[242,8149,7773],{"class":448},[242,8151,7749],{"class":248},[242,8153,8154],{"class":448},"160",[242,8156,7766],{"class":422},[242,8158,1111],{"class":248},[242,8160,8161],{"class":244,"line":5766},[242,8162,547],{"class":248},[242,8164,8165,8167,8169,8171,8173,8175,8177,8179],{"class":244,"line":5772},[242,8166,24],{"class":252},[242,8168,7952],{"class":248},[242,8170,7955],{"class":255},[242,8172,259],{"class":422},[242,8174,7518],{"class":262},[242,8176,8010],{"class":248},[242,8178,8013],{"class":255},[242,8180,435],{"class":248},[242,8182,8183,8185,8187,8189,8191,8194],{"class":244,"line":5778},[242,8184,7967],{"class":448},[242,8186,7749],{"class":248},[242,8188,7972],{"class":448},[242,8190,458],{"class":248},[242,8192,8193],{"class":473},"favorites-current.png",[242,8195,1621],{"class":248},[242,8197,8198],{"class":244,"line":5784},[242,8199,547],{"class":248},[242,8201,8202,8204,8206,8208,8210,8212],{"class":244,"line":5790},[242,8203,24],{"class":252},[242,8205,7952],{"class":248},[242,8207,7955],{"class":255},[242,8209,259],{"class":422},[242,8211,7546],{"class":262},[242,8213,7962],{"class":248},[242,8215,8216,8218,8220,8222,8224,8227],{"class":244,"line":5796},[242,8217,7967],{"class":448},[242,8219,7749],{"class":248},[242,8221,7972],{"class":448},[242,8223,458],{"class":248},[242,8225,8226],{"class":473},"more.png",[242,8228,1621],{"class":248},[242,8230,8231,8233,8235,8238,8240],{"class":244,"line":5802},[242,8232,7773],{"class":448},[242,8234,7749],{"class":248},[242,8236,8237],{"class":448},"240",[242,8239,7766],{"class":422},[242,8241,1111],{"class":248},[242,8243,8244],{"class":244,"line":5808},[242,8245,547],{"class":248},[242,8247,8248,8250,8252,8254,8256,8258],{"class":244,"line":5814},[242,8249,24],{"class":252},[242,8251,7952],{"class":248},[242,8253,7955],{"class":255},[242,8255,259],{"class":422},[242,8257,7574],{"class":262},[242,8259,7962],{"class":248},[242,8261,8262,8264,8266,8268,8270,8273],{"class":244,"line":5820},[242,8263,7967],{"class":448},[242,8265,7749],{"class":248},[242,8267,7972],{"class":448},[242,8269,458],{"class":248},[242,8271,8272],{"class":473},"info.png",[242,8274,1621],{"class":248},[242,8276,8277,8279,8281,8283],{"class":244,"line":5826},[242,8278,7746],{"class":448},[242,8280,7749],{"class":248},[242,8282,7752],{"class":448},[242,8284,1111],{"class":248},[242,8286,8287,8289,8291,8293,8295],{"class":244,"line":5832},[242,8288,7786],{"class":448},[242,8290,7749],{"class":248},[242,8292,7898],{"class":448},[242,8294,7766],{"class":422},[242,8296,1111],{"class":248},[242,8298,8299,8301,8303,8305,8307],{"class":244,"line":5837},[242,8300,7893],{"class":448},[242,8302,7749],{"class":248},[242,8304,7898],{"class":448},[242,8306,7766],{"class":422},[242,8308,1111],{"class":248},[242,8310,8311,8314,8316,8318,8320],{"class":244,"line":5843},[242,8312,8313],{"class":448}," bottom",[242,8315,7749],{"class":248},[242,8317,5214],{"class":448},[242,8319,7766],{"class":422},[242,8321,1111],{"class":248},[242,8323,8324,8327,8329,8331,8333],{"class":244,"line":5849},[242,8325,8326],{"class":448}," right",[242,8328,7749],{"class":248},[242,8330,5214],{"class":448},[242,8332,7766],{"class":422},[242,8334,1111],{"class":248},[242,8336,8338],{"class":244,"line":8337},54,[242,8339,547],{"class":248},[242,8341,8343],{"class":244,"line":8342},55,[242,8344,8345],{"class":1097},"/* finally position the views themselves */\n",[242,8347,8349,8351,8354],{"class":244,"line":8348},56,[242,8350,223],{"class":252},[242,8352,8353],{"class":255},".view",[242,8355,435],{"class":248},[242,8357,8359,8361,8363,8365],{"class":244,"line":8358},57,[242,8360,7746],{"class":448},[242,8362,7749],{"class":248},[242,8364,7851],{"class":448},[242,8366,1111],{"class":248},[242,8368,8370,8372,8374,8376,8378],{"class":244,"line":8369},58,[242,8371,7759],{"class":448},[242,8373,7749],{"class":248},[242,8375,7898],{"class":448},[242,8377,7766],{"class":422},[242,8379,1111],{"class":248},[242,8381,8383,8385,8387,8389,8391],{"class":244,"line":8382},59,[242,8384,7773],{"class":448},[242,8386,7749],{"class":248},[242,8388,6722],{"class":448},[242,8390,7766],{"class":422},[242,8392,1111],{"class":248},[242,8394,8396,8398,8400,8402,8404],{"class":244,"line":8395},60,[242,8397,7786],{"class":448},[242,8399,7749],{"class":248},[242,8401,7791],{"class":448},[242,8403,7766],{"class":422},[242,8405,1111],{"class":248},[242,8407,8409,8411,8413,8416,8418],{"class":244,"line":8408},61,[242,8410,7893],{"class":448},[242,8412,7749],{"class":248},[242,8414,8415],{"class":448},"396",[242,8417,7766],{"class":422},[242,8419,1111],{"class":248},[242,8421,8423],{"class":244,"line":8422},62,[242,8424,547],{"class":248},[18,8426,8427],{},"This can act as a simple framework for an application with multiple views. Now we still need to make our navigation\ncontroller to switch between the different views. jQuery to the rescue — a small snippet initializes our view hierarchy\nand provides switching capabilities:",[234,8429,8431],{"className":413,"code":8430,"language":415,"meta":141,"style":141},"$(document).ready(function () {\n var navitems = $(\"#navigation li a\");\n navitems.click(function () {\n navitems.removeClass(\"current\");\n var ref = $(this).addClass(\"current\").attr(\"href\");\n /* hide the other views, show the one navigated to\n and trigger a custom event */\n $(\"div.view\").hide();\n $(ref).show().trigger(\"becameActive\");\n });\n $(\"div.view\").hide();\n $(\"div.view.current\").show().trigger(\"becameActive\");\n});\n",[147,8432,8433,8450,8469,8483,8497,8533,8538,8543,8560,8583,8587,8602,8625],{"__ignoreMap":141},[242,8434,8435,8438,8441,8444,8446,8448],{"class":244,"line":245},[242,8436,8437],{"class":255},"$",[242,8439,8440],{"class":248},"(document).",[242,8442,8443],{"class":255},"ready",[242,8445,458],{"class":248},[242,8447,1067],{"class":422},[242,8449,1070],{"class":248},[242,8451,8452,8454,8457,8459,8462,8464,8467],{"class":244,"line":277},[242,8453,1075],{"class":422},[242,8455,8456],{"class":248}," navitems ",[242,8458,259],{"class":422},[242,8460,8461],{"class":255}," $",[242,8463,458],{"class":248},[242,8465,8466],{"class":262},"\"#navigation li a\"",[242,8468,1621],{"class":248},[242,8470,8471,8474,8477,8479,8481],{"class":244,"line":305},[242,8472,8473],{"class":248}," navitems.",[242,8475,8476],{"class":255},"click",[242,8478,458],{"class":248},[242,8480,1067],{"class":422},[242,8482,1070],{"class":248},[242,8484,8485,8488,8491,8493,8495],{"class":244,"line":327},[242,8486,8487],{"class":248}," navitems.",[242,8489,8490],{"class":255},"removeClass",[242,8492,458],{"class":248},[242,8494,7454],{"class":262},[242,8496,1621],{"class":248},[242,8498,8499,8501,8504,8506,8508,8510,8512,8514,8517,8519,8521,8523,8526,8528,8531],{"class":244,"line":359},[242,8500,4520],{"class":422},[242,8502,8503],{"class":248}," ref ",[242,8505,259],{"class":422},[242,8507,8461],{"class":255},[242,8509,458],{"class":248},[242,8511,524],{"class":448},[242,8513,92],{"class":248},[242,8515,8516],{"class":255},"addClass",[242,8518,458],{"class":248},[242,8520,7454],{"class":262},[242,8522,92],{"class":248},[242,8524,8525],{"class":255},"attr",[242,8527,458],{"class":248},[242,8529,8530],{"class":262},"\"href\"",[242,8532,1621],{"class":248},[242,8534,8535],{"class":244,"line":365},[242,8536,8537],{"class":1097}," /* hide the other views, show the one navigated to\n",[242,8539,8540],{"class":244,"line":375},[242,8541,8542],{"class":1097}," and trigger a custom event */\n",[242,8544,8545,8548,8550,8553,8555,8558],{"class":244,"line":385},[242,8546,8547],{"class":255}," $",[242,8549,458],{"class":248},[242,8551,8552],{"class":262},"\"div.view\"",[242,8554,92],{"class":248},[242,8556,8557],{"class":255},"hide",[242,8559,493],{"class":248},[242,8561,8562,8564,8567,8570,8573,8576,8578,8581],{"class":244,"line":392},[242,8563,8547],{"class":255},[242,8565,8566],{"class":248},"(ref).",[242,8568,8569],{"class":255},"show",[242,8571,8572],{"class":248},"().",[242,8574,8575],{"class":255},"trigger",[242,8577,458],{"class":248},[242,8579,8580],{"class":262},"\"becameActive\"",[242,8582,1621],{"class":248},[242,8584,8585],{"class":244,"line":550},[242,8586,1116],{"class":248},[242,8588,8589,8592,8594,8596,8598,8600],{"class":244,"line":555},[242,8590,8591],{"class":255}," $",[242,8593,458],{"class":248},[242,8595,8552],{"class":262},[242,8597,92],{"class":248},[242,8599,8557],{"class":255},[242,8601,493],{"class":248},[242,8603,8604,8606,8608,8611,8613,8615,8617,8619,8621,8623],{"class":244,"line":571},[242,8605,8591],{"class":255},[242,8607,458],{"class":248},[242,8609,8610],{"class":262},"\"div.view.current\"",[242,8612,92],{"class":248},[242,8614,8569],{"class":255},[242,8616,8572],{"class":248},[242,8618,8575],{"class":255},[242,8620,458],{"class":248},[242,8622,8580],{"class":262},[242,8624,1621],{"class":248},[242,8626,8627],{"class":244,"line":583},[242,8628,586],{"class":248},[18,8630,8631,8632,8635,8636,8639],{},"With custom events like ",[147,8633,8634],{},"becameActive"," it is easy to create new callbacks that are invoked when state changes occur — in\nthis example when switching the active view through our navigation controller. For brevity we will omit a elaborate\nexample of controller code, but if for example you want code to run when switching to the ",[147,8637,8638],{},"#new"," view, you could simply\nwrite:",[234,8641,8643],{"className":413,"code":8642,"language":415,"meta":141,"style":141},"$(\"#new\").bind(\"becameActive\", function (event) {\n $(event.target).doSomething();\n});\n",[147,8644,8645,8671,8683],{"__ignoreMap":141},[242,8646,8647,8649,8651,8653,8655,8657,8659,8661,8663,8665,8667,8669],{"class":244,"line":245},[242,8648,8437],{"class":255},[242,8650,458],{"class":248},[242,8652,7490],{"class":262},[242,8654,92],{"class":248},[242,8656,4277],{"class":255},[242,8658,458],{"class":248},[242,8660,8580],{"class":262},[242,8662,464],{"class":248},[242,8664,1067],{"class":422},[242,8666,470],{"class":248},[242,8668,474],{"class":473},[242,8670,1199],{"class":248},[242,8672,8673,8675,8678,8681],{"class":244,"line":277},[242,8674,8591],{"class":255},[242,8676,8677],{"class":248},"(event.target).",[242,8679,8680],{"class":255},"doSomething",[242,8682,493],{"class":248},[242,8684,8685],{"class":244,"line":305},[242,8686,586],{"class":248},[18,8688,8689],{},"So what is still missing now? We still haven’t provided a data model to operate on. While data provided via web services\nis not a big deal by the use of JSON-APIs, we would certainly want to have a local data storage too. For quite some\ntime already, web engines provide a local SQLite-based storage system. On application initialization we can simply\nsetup some tables to hold our data and send statements from anywhere in the application later on:",[234,8691,8693],{"className":413,"code":8692,"language":415,"meta":141,"style":141},"// open a database, providing it's name, version,\n// maximum size and display name\nvar database = openDatabase(\"Example\", \"1.0\", 1048576, \"Example Database\");\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\n \"CREATE TABLE IF NOT EXISTS data\" +\n \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\" +\n \"... more declarations ...);\",\n );\n});\n// other statements are issued the same way\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\"SELECT * FROM data;\", function (transaction, result) {\n // do something with 'result'\n });\n});\n",[147,8694,8695,8700,8705,8739,8757,8767,8775,8782,8789,8794,8798,8803,8819,8845,8850,8854],{"__ignoreMap":141},[242,8696,8697],{"class":244,"line":245},[242,8698,8699],{"class":1097},"// open a database, providing it's name, version,\n",[242,8701,8702],{"class":244,"line":277},[242,8703,8704],{"class":1097},"// maximum size and display name\n",[242,8706,8707,8709,8712,8714,8717,8719,8722,8724,8727,8729,8732,8734,8737],{"class":244,"line":305},[242,8708,4813],{"class":422},[242,8710,8711],{"class":248}," database ",[242,8713,259],{"class":422},[242,8715,8716],{"class":255}," openDatabase",[242,8718,458],{"class":248},[242,8720,8721],{"class":262},"\"Example\"",[242,8723,464],{"class":248},[242,8725,8726],{"class":262},"\"1.0\"",[242,8728,464],{"class":248},[242,8730,8731],{"class":448},"1048576",[242,8733,464],{"class":248},[242,8735,8736],{"class":262},"\"Example Database\"",[242,8738,1621],{"class":248},[242,8740,8741,8744,8747,8749,8751,8753,8755],{"class":244,"line":327},[242,8742,8743],{"class":248},"database.",[242,8745,8746],{"class":255},"transaction",[242,8748,458],{"class":248},[242,8750,1067],{"class":422},[242,8752,470],{"class":248},[242,8754,8746],{"class":473},[242,8756,1199],{"class":248},[242,8758,8759,8762,8765],{"class":244,"line":359},[242,8760,8761],{"class":248}," transaction.",[242,8763,8764],{"class":255},"executeSQL",[242,8766,4743],{"class":248},[242,8768,8769,8772],{"class":244,"line":365},[242,8770,8771],{"class":262}," \"CREATE TABLE IF NOT EXISTS data\"",[242,8773,8774],{"class":422}," +\n",[242,8776,8777,8780],{"class":244,"line":375},[242,8778,8779],{"class":262}," \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\"",[242,8781,8774],{"class":422},[242,8783,8784,8787],{"class":244,"line":385},[242,8785,8786],{"class":262}," \"... more declarations ...);\"",[242,8788,580],{"class":248},[242,8790,8791],{"class":244,"line":392},[242,8792,8793],{"class":248}," );\n",[242,8795,8796],{"class":244,"line":550},[242,8797,586],{"class":248},[242,8799,8800],{"class":244,"line":555},[242,8801,8802],{"class":1097},"// other statements are issued the same way\n",[242,8804,8805,8807,8809,8811,8813,8815,8817],{"class":244,"line":571},[242,8806,8743],{"class":248},[242,8808,8746],{"class":255},[242,8810,458],{"class":248},[242,8812,1067],{"class":422},[242,8814,470],{"class":248},[242,8816,8746],{"class":473},[242,8818,1199],{"class":248},[242,8820,8821,8823,8825,8827,8830,8832,8834,8836,8838,8840,8843],{"class":244,"line":583},[242,8822,8761],{"class":248},[242,8824,8764],{"class":255},[242,8826,458],{"class":248},[242,8828,8829],{"class":262},"\"SELECT * FROM data;\"",[242,8831,464],{"class":248},[242,8833,1067],{"class":422},[242,8835,470],{"class":248},[242,8837,8746],{"class":473},[242,8839,464],{"class":248},[242,8841,8842],{"class":473},"result",[242,8844,1199],{"class":248},[242,8846,8847],{"class":244,"line":682},[242,8848,8849],{"class":1097}," // do something with 'result'\n",[242,8851,8852],{"class":244,"line":688},[242,8853,1116],{"class":248},[242,8855,8856],{"class":244,"line":693},[242,8857,586],{"class":248},[74,8859,8861],{"id":8860},"putting-it-all-together","Putting it all together",[18,8863,8864,8865,1402,8868],{},"A question remains: ",[89,8866,8867],{},"when this is the basic skeleton of an application, how do i put this on an actual\ndevice?",[139,8869],{"alt":141,"src":8870},"https://media.synyx.de/uploads//2010/08/jquery-html-css-example-e1281692547570.png",[18,8872,8873],{},"Example in Simulator",[18,8875,8876,8877,8880],{},"While this could be simply served by a web server to a mobile device, the main reason for developing applications\ninstead of websites is the ability to expose them through an application store (e.g. Apples AppStore or the Android\nMarket). We won’t go into much detail here now — this is something to be discussed in the following blog posts in this\nseries — a simple answer is provided by a small framework\nnamed ",[24,8878,7155],{"href":7153,"rel":8879},[28],". PhoneGap provides the developer with\na cross-platform deployment environment, which makes use of the web engines on the different mobile devices. It\nbasically is a fullscreen web browser view, without any UI elements and other decorations but with added support for\naccessing hardware features of the actual device. Next to the already mentioned features, which are part of the modern\nweb engines, it gives access to features like cameras, accelerometer or access to the native phonebook of your mobile\nphone. You simply drop all your HTML, CSS & JavaScript into a PhoneGap application package which is built for your\nparticular device and are ready to deploy through any application store or similar channel you like.",[74,8882,8884],{"id":8883},"whats-next","What’s next?",[18,8886,8887],{},"So developing mobile applications with HTML, CSS & JavaScript is actually easy, isn’t it? Well, with the basic ideas\noutlined above it is — until it isn’t anymore. In the parts of this series which are still to come, we will answer some\nquestions, that might be crucial to your development cycle:",[82,8889,8890,8893,8896,8899],{},[85,8891,8892],{},"What problems might arise with JavaScript as a development language? Is there enough tool support for ease of\ndevelopment?",[85,8894,8895],{},"What do i have to program from scratch and which frameworks are available, that erase my need of boilerplate code?",[85,8897,8898],{},"What about the performance, for example when sophisticated animations are desired?",[85,8900,8901],{},"Is my application really cross-platform, when using these technologies, and does it behave exactly the same on every\ndevice?",[18,8903,8904],{},"Stay tuned for more.",[763,8906,8907],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":141,"searchDepth":277,"depth":277,"links":8909},[8910,8911,8912,8913],{"id":7398,"depth":277,"text":7399},{"id":7405,"depth":277,"text":7406},{"id":8860,"depth":277,"text":8861},{"id":8883,"depth":277,"text":8884},[7365,8915],"tutorial","2010-08-13T12:17:29","Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\\nseveral mobile platforms at once.","https://synyx.de/blog/on-cross-device-mobile-development-part-1/",{},"/blog/on-cross-device-mobile-development-part-1",{"title":7382,"description":8922},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.","blog/on-cross-device-mobile-development-part-1",[7375,7376,238,7377,415],"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team of developers might be small or knowledge about…","yJGRhWop8a6UwlupSTSgkogMBabnFF7yVveZDcNWM8Y",[8928,8931,8933,8936,8939,8942,8945,8948,8951,8954,8957,8960,8963,8965,8968,8971,8974,8977,8980,8983,8986,8989,8991,8994,8997,9000,9003,9005,9008,9011,9014,9017,9020,9023,9026,9029,9032,9035,9038,9041,9044,9047,9050,9053,9056,9059,9062,9065,9068,9071,9074,9077,9080,9083,9086,9089,9092,9095,9098,9101,9104,9107,9110,9113,9116,9119,9122,9125,9127,9130,9132,9135,9138,9141,9144,9147,9150,9153,9156,9159,9162,9165,9168,9171,9174,9177,9180,9183,9186,9188,9191,9194,9197,9200,9203,9206,9209,9211,9214,9216,9219,9222,9225,9228,9230,9233,9236,9239,9242,9245,9248,9250,9252,9255,9258,9261,9264,9267,9270,9273,9276,9279,9282,9285,9288,9291,9294,9297,9300,9303,9304,9306,9309,9312,9315,9318,9321,9324,9327,9330,9333,9336],{"slug":8929,"name":8930},"abel","Jennifer Abel",{"slug":5364,"name":8932},"Otto Allmendinger",{"slug":8934,"name":8935},"antony","Ben Antony",{"slug":8937,"name":8938},"arrasz","Joachim Arrasz",{"slug":8940,"name":8941},"bauer","David Bauer",{"slug":8943,"name":8944},"bechtold","Janine Bechtold",{"slug":8946,"name":8947},"boersig","Jasmin Börsig",{"slug":8949,"name":8950},"buch","Fabian Buch",{"slug":8952,"name":8953},"buchloh","Aljona Buchloh",{"slug":8955,"name":8956},"burgard","Julia Burgard",{"slug":8958,"name":8959},"caspar-schwedes","Caspar Schwedes",{"slug":8961,"name":8962},"christina-schmitt","Christina Schmitt",{"slug":4228,"name":8964},"Michael Clausen",{"slug":8966,"name":8967},"contargo_poetzsch","Thomas Pötzsch",{"slug":8969,"name":8970},"damrath","Sebastian Damrath",{"slug":8972,"name":8973},"daniel","Markus Daniel",{"slug":8975,"name":8976},"dasch","Julia Dasch",{"slug":8978,"name":8979},"denman","Joffrey Denman",{"slug":8981,"name":8982},"dfuchs","Daniel Fuchs",{"slug":8984,"name":8985},"dobler","Max Dobler",{"slug":8987,"name":8988},"dobriakov","Vladimir Dobriakov",{"slug":8990,"name":8990},"dreiqbik",{"slug":8992,"name":8993},"dschaefer","Denise Schäfer",{"slug":8995,"name":8996},"dschneider","Dominik Schneider",{"slug":8998,"name":8999},"duerlich","Isabell Duerlich",{"slug":9001,"name":9002},"dutkowski","Bernd Dutkowski",{"slug":9004,"name":9004},"eifler",{"slug":9006,"name":9007},"essig","Tim Essig",{"slug":9009,"name":9010},"ferstl","Maximilian Ferstl",{"slug":9012,"name":9013},"fey","Prisca Fey",{"slug":9015,"name":9016},"frank","Leonard Frank",{"slug":9018,"name":9019},"franke","Arnold Franke",{"slug":9021,"name":9022},"frischer","Nicolette Rudmann",{"slug":9024,"name":9025},"fuchs","Petra Fuchs",{"slug":9027,"name":9028},"gari","Sarah Gari",{"slug":9030,"name":9031},"gast","Gast",{"slug":9033,"name":9034},"graf","Johannes Graf",{"slug":9036,"name":9037},"grammlich","Daniela Grammlich",{"slug":9039,"name":9040},"guthardt","Sabrina Guthardt",{"slug":9042,"name":9043},"haeussler","Johannes Häussler",{"slug":9045,"name":9046},"hammann","Daniel Hammann",{"slug":9048,"name":9049},"heetel","Julian Heetel",{"slug":9051,"name":9052},"heft","Florian Heft",{"slug":9054,"name":9055},"heib","Sebastian Heib",{"slug":9057,"name":9058},"heisler","Ida Heisler",{"slug":9060,"name":9061},"helm","Patrick Helm",{"slug":9063,"name":9064},"herbold","Michael Herbold",{"slug":9066,"name":9067},"hofmann","Peter Hofmann",{"slug":9069,"name":9070},"hopf","Florian Hopf",{"slug":9072,"name":9073},"jaud","Alina Jaud",{"slug":9075,"name":9076},"jayasinghe","Robin De Silva Jayasinghe",{"slug":9078,"name":9079},"jbuch","Jonathan Buch",{"slug":9081,"name":9082},"junghanss","Gitta Junghanß",{"slug":9084,"name":9085},"kadyietska","Khrystyna Kadyietska",{"slug":9087,"name":9088},"kannegiesser","Marc Kannegiesser",{"slug":9090,"name":9091},"karoly","Robert Károly",{"slug":9093,"name":9094},"karrasz","Katja Arrasz-Schepanski",{"slug":9096,"name":9097},"kaufmann","Florian Kaufmann",{"slug":9099,"name":9100},"kesler","Mike Kesler",{"slug":9102,"name":9103},"kirchgaessner","Bettina Kirchgäßner",{"slug":9105,"name":9106},"klem","Yannic Klem",{"slug":9108,"name":9109},"klenk","Timo Klenk",{"slug":9111,"name":9112},"knell","Tobias Knell",{"slug":9114,"name":9115},"knoll","Anna-Lena Knoll",{"slug":9117,"name":9118},"knorre","Matthias Knorre",{"slug":9120,"name":9121},"koenig","Melanie König",{"slug":9123,"name":9124},"kraft","Thomas Kraft",{"slug":7098,"name":9126},"Florian Krupicka",{"slug":9128,"name":9129},"kuehn","Christian Kühn",{"slug":795,"name":9131},"Christian Lange",{"slug":9133,"name":9134},"larrasz","Luca Arrasz",{"slug":9136,"name":9137},"leist","Sascha Leist",{"slug":9139,"name":9140},"lihs","Michael Lihs",{"slug":9142,"name":9143},"linsin","David Linsin",{"slug":9145,"name":9146},"maniyar","Christian Maniyar",{"slug":9148,"name":9149},"martin","Björnie",{"slug":9151,"name":9152},"martin-koch","Martin Koch",{"slug":9154,"name":9155},"matt","Tobias Matt",{"slug":9157,"name":9158},"mennerich","Christian Mennerich",{"slug":9160,"name":9161},"menz","Alexander Menz",{"slug":9163,"name":9164},"meseck","Frederick Meseck",{"slug":9166,"name":9167},"messner","Oliver Messner",{"slug":9169,"name":9170},"michael-ploed","Michael Plöd",{"slug":9172,"name":9173},"mies","Marius Mies",{"slug":9175,"name":9176},"mihai","Alina Mihai",{"slug":9178,"name":9179},"moeller","Jörg Möller",{"slug":9181,"name":9182},"mohr","Rebecca Mohr",{"slug":9184,"name":9185},"moretti","David Moretti",{"slug":3611,"name":9187},"Sven Müller",{"slug":9189,"name":9190},"muessig","Alexander Müssig",{"slug":9192,"name":9193},"neupokoev","Grigory Neupokoev",{"slug":9195,"name":9196},"nussbaecher","Carmen Nussbächer",{"slug":9198,"name":9199},"ochs","Pascal Ochs",{"slug":9201,"name":9202},"oelhoff","Jan Oelhoff",{"slug":9204,"name":9205},"oengel","Yasin Öngel",{"slug":9207,"name":9208},"oezsoy","Enis Özsoy",{"slug":3884,"name":9210},"Maya Posch",{"slug":9212,"name":9213},"ralfmueller","Ralf Müller",{"slug":9215,"name":9215},"redakteur",{"slug":9217,"name":9218},"reich","Michael Reich",{"slug":9220,"name":9221},"reinhard","Karl-Ludwig Reinhard",{"slug":9223,"name":9224},"rmueller","Rebecca Müller",{"slug":9226,"name":9227},"rosum","Jan Rosum",{"slug":9229,"name":9229},"rueckert",{"slug":9231,"name":9232},"ruessel","Sascha Rüssel",{"slug":9234,"name":9235},"sauter","Moritz Sauter",{"slug":9237,"name":9238},"schaefer","Julian Schäfer",{"slug":9240,"name":9241},"scherer","Petra Scherer",{"slug":9243,"name":9244},"schlicht","Anne Schlicht",{"slug":9246,"name":9247},"schmidt","Jürgen Schmidt",{"slug":3612,"name":9249},"Tobias Schneider",{"slug":9,"name":9251},"Benjamin Seber",{"slug":9253,"name":9254},"sommer","Marc Sommer",{"slug":9256,"name":9257},"speaker-fels","Jakob Fels",{"slug":9259,"name":9260},"speaker-gierke","Oliver Gierke",{"slug":9262,"name":9263},"speaker-krupa","Malte Krupa",{"slug":9265,"name":9266},"speaker-mader","Jochen Mader",{"slug":9268,"name":9269},"speaker-meusel","Tim Meusel",{"slug":9271,"name":9272},"speaker-milke","Oliver Milke",{"slug":9274,"name":9275},"speaker-paluch","Mark Paluch",{"slug":9277,"name":9278},"speaker-schad","Jörg Schad",{"slug":9280,"name":9281},"speaker-schalanda","Jochen Schalanda",{"slug":9283,"name":9284},"speaker-schauder","Jens Schauder",{"slug":9286,"name":9287},"speaker-unterstein","Johannes Unterstein",{"slug":9289,"name":9290},"speaker-wolff","Eberhard Wolff",{"slug":9292,"name":9293},"speaker-zoerner","Stefan Zörner",{"slug":9295,"name":9296},"stefan-belger","Stefan Belger",{"slug":9298,"name":9299},"steinegger","Roland Steinegger",{"slug":9301,"name":9302},"stern","sternchen synyx",{"slug":3631,"name":3631},{"slug":3404,"name":9305},"Mateusz Szulc",{"slug":9307,"name":9308},"tamara","Tamara Tunczinger",{"slug":9310,"name":9311},"theuer","Tobias Theuer",{"slug":9313,"name":9314},"thieme","Sandra Thieme",{"slug":9316,"name":9317},"thies-clasen","Marudor",{"slug":9319,"name":9320},"toernstroem","Olle Törnström",{"slug":9322,"name":9323},"ullinger","Max Ullinger",{"slug":9325,"name":9326},"ulrich","Stephan Ulrich",{"slug":9328,"name":9329},"wagner","Stefan Wagner",{"slug":9331,"name":9332},"weigel","Andreas Weigel",{"slug":9334,"name":9335},"werner","Fabian Werner",{"slug":9337,"name":9338},"wolke","Sören Wolke",["Reactive",9340],{"$scookieConsent":9341,"$ssite-config":9343},{"functional":9342,"analytics":9342},false,{"_priority":9344,"env":9348,"name":9349,"url":9350},{"name":9345,"env":9346,"url":9347},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",9353],{"category-javascript":-1,"authors":-1},"/blog/tags/javascript"]