\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",[372,465,466,500,527,549,581,587,597,607,613],{"__ignoreMap":11},[467,468,471,475,478,482,485,489,492,494,497],"span",{"class":469,"line":470},"line",1,[467,472,474],{"class":473},"sVt8B","\u003C",[467,476,374],{"class":477},"s9eBZ",[467,479,481],{"class":480},"sScJk"," action",[467,483,484],{"class":473},"=",[467,486,488],{"class":487},"sZZnC","\"/heroes\"",[467,490,491],{"class":480}," method",[467,493,484],{"class":473},[467,495,496],{"class":487},"\"post\"",[467,498,499],{"class":473},">\n",[467,501,502,505,508,511,513,516,519,521,524],{"class":469,"line":12},[467,503,504],{"class":473}," \u003C",[467,506,507],{"class":477},"input",[467,509,510],{"class":480}," type",[467,512,484],{"class":473},[467,514,515],{"class":487},"\"text\"",[467,517,518],{"class":480}," name",[467,520,484],{"class":473},[467,522,523],{"class":487},"\"hero\"",[467,525,526],{"class":473}," />\n",[467,528,530,532,534,536,538,540,542,544,547],{"class":469,"line":529},3,[467,531,504],{"class":473},[467,533,507],{"class":477},[467,535,510],{"class":480},[467,537,484],{"class":473},[467,539,515],{"class":487},[467,541,518],{"class":480},[467,543,484],{"class":473},[467,545,546],{"class":487},"\"superpower\"",[467,548,526],{"class":473},[467,550,552,554,556,558,560,563,566,568,571,574,576,579],{"class":469,"line":551},4,[467,553,504],{"class":473},[467,555,452],{"class":477},[467,557,510],{"class":480},[467,559,484],{"class":473},[467,561,562],{"class":487},"\"submit\"",[467,564,565],{"class":480}," is",[467,567,484],{"class":473},[467,569,570],{"class":487},"\"ajax-submit\"",[467,572,573],{"class":480}," data-target",[467,575,484],{"class":473},[467,577,578],{"class":487},"\"hero-container\"",[467,580,499],{"class":473},[467,582,584],{"class":469,"line":583},5,[467,585,586],{"class":473}," add new item\n",[467,588,590,593,595],{"class":469,"line":589},6,[467,591,592],{"class":473}," \u003C/",[467,594,452],{"class":477},[467,596,499],{"class":473},[467,598,600,603,605],{"class":469,"line":599},7,[467,601,602],{"class":473},"\u003C/",[467,604,374],{"class":477},[467,606,499],{"class":473},[467,608,610],{"class":469,"line":609},8,[467,611,612],{"emptyLinePlaceholder":29},"\n",[467,614,616,618,620,623,625,627,630,632],{"class":469,"line":615},9,[467,617,474],{"class":473},[467,619,448],{"class":477},[467,621,622],{"class":480}," id",[467,624,484],{"class":473},[467,626,578],{"class":487},[467,628,629],{"class":473},">\u003C/",[467,631,448],{"class":477},[467,633,499],{"class":473},[459,635,639],{"className":636,"code":637,"language":638,"meta":11,"style":11},"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",[372,640,641,659,667,706,717,735,756,761,766,771,776,792,804],{"__ignoreMap":11},[467,642,643,647,650,653,656],{"class":469,"line":470},[467,644,646],{"class":645},"szBVR","class",[467,648,649],{"class":480}," AjaxSubmitButton",[467,651,652],{"class":645}," extends",[467,654,655],{"class":480}," HTMLButtonElement",[467,657,658],{"class":473}," {\n",[467,660,661,664],{"class":469,"line":12},[467,662,663],{"class":480}," connectedCallback",[467,665,666],{"class":473},"() {\n",[467,668,669,673,676,679,682,685,688,691,694,698,701,704],{"class":469,"line":529},[467,670,672],{"class":671},"sj4cs"," this",[467,674,675],{"class":473},".",[467,677,678],{"class":480},"addEventListener",[467,680,681],{"class":473},"(",[467,683,684],{"class":487},"\"click\"",[467,686,687],{"class":473},", ",[467,689,690],{"class":645},"async",[467,692,693],{"class":473}," (",[467,695,697],{"class":696},"s4XuR","event",[467,699,700],{"class":473},") ",[467,702,703],{"class":645},"=>",[467,705,658],{"class":473},[467,707,708,711,714],{"class":469,"line":551},[467,709,710],{"class":473}," event.",[467,712,713],{"class":480},"preventDefault",[467,715,716],{"class":473},"();\n",[467,718,719,722,725,727,730,733],{"class":469,"line":583},[467,720,721],{"class":645}," let",[467,723,724],{"class":473}," html ",[467,726,484],{"class":645},[467,728,729],{"class":645}," await",[467,731,732],{"class":480}," ajaxFormSubmit",[467,734,716],{"class":473},[467,736,737,740,743,745,748,751,753],{"class":469,"line":589},[467,738,739],{"class":473}," document.",[467,741,742],{"class":480},"getElementById",[467,744,681],{"class":473},[467,746,747],{"class":671},"this",[467,749,750],{"class":473},".dataset.target).innerHTML ",[467,752,484],{"class":645},[467,754,755],{"class":473}," html;\n",[467,757,758],{"class":469,"line":599},[467,759,760],{"class":473}," });\n",[467,762,763],{"class":469,"line":609},[467,764,765],{"class":473}," }\n",[467,767,768],{"class":469,"line":615},[467,769,770],{"class":473},"}\n",[467,772,774],{"class":469,"line":773},10,[467,775,612],{"emptyLinePlaceholder":29},[467,777,779,782,785,787,789],{"class":469,"line":778},11,[467,780,781],{"class":473},"customElements.",[467,783,784],{"class":480},"define",[467,786,681],{"class":473},[467,788,570],{"class":487},[467,790,791],{"class":473},", AjaxSubmitButton, {\n",[467,793,795,798,801],{"class":469,"line":794},12,[467,796,797],{"class":473}," extends: ",[467,799,800],{"class":487},"\"button\"",[467,802,803],{"class":473},",\n",[467,805,807],{"class":469,"line":806},13,[467,808,809],{"class":473},"});\n",[54,811,812,813,816],{},"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 ",[372,814,815],{},"AjaxSubmitButtons"," zu erstellen.",[54,818,819,820,823,824,829],{},"Kommen zur Laufzeit weitere ",[372,821,822],{},"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, ",[61,825,828],{"href":826,"rel":827},"https://developer.mozilla.org/de/docs/Glossary/Progressive_Enhancement",[65],"Progressive Enhancement"," genannt. HTML\nbeschreibt den Inhalt, CSS macht es bunt, und zu guter Letzt verbessern wir die Benutzererfahrung mit JavaScript.",[54,831,832],{},"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:",[459,834,838],{"className":835,"code":836,"language":837,"meta":11,"style":11},"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",[372,839,840,845,850,855,860,865,870,875,879,884,889,893,898,903,909,914,920],{"__ignoreMap":11},[467,841,842],{"class":469,"line":470},[467,843,844],{},"@PostMapping(value = \"/heroes\")\n",[467,846,847],{"class":469,"line":12},[467,848,849],{},"public String addSuperhero(\n",[467,851,852],{"class":469,"line":529},[467,853,854],{}," @RequestParam String hero,\n",[467,856,857],{"class":469,"line":551},[467,858,859],{}," @RequestParam String superpower,\n",[467,861,862],{"class":469,"line":583},[467,863,864],{}," @RequestHeader(name = \"X-Requested-With\", defaultValue = \"\") String requestedWith,\n",[467,866,867],{"class":469,"line":589},[467,868,869],{}," Model model\n",[467,871,872],{"class":469,"line":599},[467,873,874],{}," ) {\n",[467,876,877],{"class":469,"line":609},[467,878,612],{"emptyLinePlaceholder":29},[467,880,881],{"class":469,"line":615},[467,882,883],{}," model.addAttribute(\"hero\", hero);\n",[467,885,886],{"class":469,"line":773},[467,887,888],{}," model.addAttribute(\"superpower\", superpower);\n",[467,890,891],{"class":469,"line":778},[467,892,612],{"emptyLinePlaceholder":29},[467,894,895],{"class":469,"line":794},[467,896,897],{}," if (\"ajax\".equals(requestedWith)) {\n",[467,899,900],{"class":469,"line":806},[467,901,902],{}," return \"fragments/hero-fragment :: hero-fragment\";\n",[467,904,906],{"class":469,"line":905},14,[467,907,908],{}," }\n",[467,910,912],{"class":469,"line":911},15,[467,913,612],{"emptyLinePlaceholder":29},[467,915,917],{"class":469,"line":916},16,[467,918,919],{}," return \"full-page-including-the-hero-fragment\";\n",[467,921,923],{"class":469,"line":922},17,[467,924,770],{},[49,926,928],{"id":927},"auf-dem-weg-zur-single-page-application","Auf dem Weg zur Single Page Application",[54,930,931],{},"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.",[54,933,934],{},"Kurz gesagt: Wir bauen unser Frontend ohne modernes JavaScript Framework und sind (trotzdem) glücklich.",[54,936,937],{},"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.",[939,940,942],"h3",{"id":941},"herausforderungen-die-kommen-könnten","Herausforderungen die kommen (könnten)",[309,944,945],{},[312,946,947],{},[436,948,949],{},"Progressive Enhancement Denkweise",[54,951,952],{},"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.",[309,954,955],{},[312,956,957],{},[436,958,959],{},"History Handling",[54,961,962,963,968],{},"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 ",[61,964,967],{"href":965,"rel":966},"https://developer.mozilla.org/en-US/docs/Web/API/History",[65],"history API"," ändern ist im Bereich des Möglichen 😉",[309,970,971],{},[312,972,973],{},[436,974,975],{},"State",[54,977,978,979,984],{},"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 ",[61,980,983],{"href":981,"rel":982},"https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent",[65],"Custom Events"," ausreichen.",[986,987,988],"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":11,"searchDepth":12,"depth":12,"links":990},[991,992,993,994],{"id":303,"depth":12,"text":304},{"id":350,"depth":12,"text":351},{"id":424,"depth":12,"text":415},{"id":927,"depth":12,"text":928,"children":995},[996],{"id":941,"depth":529,"text":942},[998,227],"developer-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.","https://synyx.de/blog/frameworkless-frontend-und-trotzdem-gluecklich/",{},"/blog/frameworkless-frontend-und-trotzdem-gluecklich",{"title":241,"description":250},"frameworkless-frontend-und-trotzdem-gluecklich","blog/frameworkless-frontend-und-trotzdem-gluecklich",[1008,638,1009],"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":1013,"title":1014,"author":1015,"body":1016,"category":1968,"date":1969,"description":1970,"extension":18,"link":1971,"meta":1972,"navigation":29,"path":1973,"seo":1974,"slug":1020,"stem":1975,"tags":1976,"teaser":1977,"__hash__":1978},"blog/blog/wie-meine-entwicklungsumgebung-eingerichtet-ist.md","Wie meine Entwicklungsumgebung eingerichtet ist",[33],{"type":8,"value":1017,"toc":1963},[1018,1021,1024,1027,1050,1053,1056,1071,1077,1080,1162,1181,1184,1295,1298,1313,1316,1347,1350,1432,1542,1549,1556,1810,1813,1816,1957,1960],[45,1019,1014],{"id":1020},"wie-meine-entwicklungsumgebung-eingerichtet-ist",[54,1022,1023],{},"Beim synyx Camp vor zwei Wochen haben wir uns unter anderem über das Setup unserer Entwicklungsumgebungen unterhalten.\nIm Folgenden möchte ich kurz berichten, wie ich meine eingerichtet habe und welche Programme ich in meiner alltäglichen\nArbeit nicht mehr missen möchte.",[54,1025,1026],{},"Kurz angerissen werden:",[309,1028,1029,1036,1043],{},[312,1030,1031],{},[61,1032,1035],{"href":1033,"rel":1034},"https://synyx.de/blog/2018-11-09-entwicklungsumgebung/?page=1#terminal",[65],"Terminal",[312,1037,1038],{},[61,1039,1042],{"href":1040,"rel":1041},"https://synyx.de/blog/2018-11-09-entwicklungsumgebung/?page=1#git",[65],"Git",[312,1044,1045],{},[61,1046,1049],{"href":1047,"rel":1048},"https://synyx.de/blog/2018-11-09-entwicklungsumgebung/?page=1#programme",[65],"Programme",[49,1051,1035],{"id":1052},"terminal",[54,1054,1055],{},"Ich arbeite mit einem MacBook und habe von Anfang an iTerm2 genutzt. Weshalb weiß ich leider nicht mehr. Vielleicht gab\nes damals für das von Haus aus installierte Terminal keine Möglichkeit ein Dark Theme zu verwenden. Vielleicht gab es\ndamals auch nicht die Möglichkeit ein Terminalfenster in der Horizontalen zu splitten. Beides ist mittlerweile möglich.\nAndere von mir genutzte iTerm2 Features kommen mir gerade nicht in den Sinn…",[54,1057,1058,1059,1064,1065,1070],{},"Viel interessanter finde ich jedenfalls die Frage, welche Shell installiert ist und wie sie personalisiert ist. Ich\nverwende ",[61,1060,1063],{"href":1061,"rel":1062},"https://github.com/zsh-users/zsh",[65],"zsh"," und natürlich(?)\nauch ",[61,1066,1069],{"href":1067,"rel":1068},"https://github.com/robbyrussell/oh-my-zsh",[65],"oh-my-zsh",", um meine Shell im Look & Feel anzupassen und mit Plugins\nerweitern zu können.",[54,1072,1073],{},[87,1074],{"alt":1075,"src":1076},"Code Beispiel","https://media.synyx.de/uploads/2019/03/prompt-1-768x578.png",[54,1078,1079],{},"Folgende oh-my-zsh Plugins habe ich installiert:",[309,1081,1082,1104,1132,1147],{},[312,1083,1084,1085],{},"z",[309,1086,1087,1094],{},[312,1088,1089,1090,1093],{},"führt eine Liste von Pfaden, in welche man auf der Shell mit ",[372,1091,1092],{},"cd"," navigiert und matcht die eingegebenen Zeichen\nauf den meist besuchten Pfad",[312,1095,1096,1097,1100,1101],{},"z. B. führt bei mir der Befehl ",[372,1098,1099],{},"z dot"," zu ",[372,1102,1103],{},"/Users/seber/projects/git/dotfiles",[312,1105,1106,1111],{},[61,1107,1110],{"href":1108,"rel":1109},"https://github.com/lukechilds/zsh-better-npm-completion",[65],"zsh-better-npm-completion",[309,1112,1113,1116],{},[312,1114,1115],{},"drücke Tab, um projektspezifische npm Task Vorschläge zu bekommen",[312,1117,1118,1119,687,1122,687,1125,264,1128,1131],{},"z. B. bekomme ich im Screenshot die Tasks ",[372,1120,1121],{},"lint",[372,1123,1124],{},"lint:watch",[372,1126,1127],{},"start",[372,1129,1130],{},"start:watch"," vorgeschlagen",[312,1133,1134,1139,1144,1146],{},[61,1135,1138],{"href":1136,"rel":1137},"https://github.com/zsh-users/zsh-autosuggestions",[65],"zsh-autosuggestions",[309,1140,1141],{},[312,1142,1143],{},"fange an zu tippen und der Befehl wird ausgegraut vervollständigt",[154,1145],{},"drücke Pfeiltaste rechts und Enter, um ihn auszuführen",[312,1148,1149,1154,1159,1161],{},[61,1150,1153],{"href":1151,"rel":1152},"https://github.com/zsh-users/zsh-syntax-highlighting",[65],"zsh-syntax-highlighting",[309,1155,1156],{},[312,1157,1158],{},"hebt das auszuführende Programm farblich hervor",[154,1160],{},"(nützlich um zu sehen, ob ich mich vertippt habe)",[54,1163,1164,1165,1170,1171,1176,1177,1180],{},"Als Prompt habe ich lange Zeit die ",[61,1166,1169],{"href":1167,"rel":1168},"https://github.com/sindresorhus/pure",[65],"pure prompt"," verwendet. Sie ist super schlank\nund zeigt nützliche Infos zum aktuellen git Repository an. Vor wenigen Wochen bin ich jedoch\nzur ",[61,1172,1175],{"href":1173,"rel":1174},"https://github.com/denysdovhan/spaceship-prompt",[65],"spaceship-prompt"," gewechselt. Einerseits um einfach mal wieder\nwas neues auszuprobieren, andererseits weil sie mir von Haus aus die Version der Programmiersprache des aktuellen\nProjektes anzeigt. Im Screenshot oben sieht man nach dem Wechsel in das ",[372,1178,1179],{},"workshop-maze-vr"," Verzeichnis den Hinweis, dass\ndas Projekt ein nodeJS Projekt ist und die bash session aktuell v10.9.0 verwendet. Neben nodeJS werden einige andere\nProgrammiersprachen unterstützt, Java leider nicht.",[54,1182,1183],{},"Die Anordnung der spaceship prompt Dinge und ob sie überhaupt angezeigt werden sollen, kann konfiguriert werden:",[459,1185,1189],{"className":1186,"code":1187,"language":1188,"meta":11,"style":11},"language-bash shiki shiki-themes github-light github-dark","SPACESHIP_PROMPT_ORDER=(\n time # Time stamps section\n user # Username section\n dir # Current directory section\n git # Git section (git_branch + git_status)\n node # Node.js section\n docker # Docker section\n exec_time # Execution time\n line_sep # Line break\n jobs # Background jobs indicator\n exit_code # Exit code section\n char # Prompt character\n)\n","bash",[372,1190,1191,1201,1210,1218,1226,1234,1242,1250,1258,1266,1274,1282,1290],{"__ignoreMap":11},[467,1192,1193,1196,1198],{"class":469,"line":470},[467,1194,1195],{"class":473},"SPACESHIP_PROMPT_ORDER",[467,1197,484],{"class":645},[467,1199,1200],{"class":473},"(\n",[467,1202,1203,1206],{"class":469,"line":12},[467,1204,1205],{"class":645}," time",[467,1207,1209],{"class":1208},"sJ8bj"," # Time stamps section\n",[467,1211,1212,1215],{"class":469,"line":529},[467,1213,1214],{"class":487}," user",[467,1216,1217],{"class":1208}," # Username section\n",[467,1219,1220,1223],{"class":469,"line":551},[467,1221,1222],{"class":487}," dir",[467,1224,1225],{"class":1208}," # Current directory section\n",[467,1227,1228,1231],{"class":469,"line":583},[467,1229,1230],{"class":487}," git",[467,1232,1233],{"class":1208}," # Git section (git_branch + git_status)\n",[467,1235,1236,1239],{"class":469,"line":589},[467,1237,1238],{"class":487}," node",[467,1240,1241],{"class":1208}," # Node.js section\n",[467,1243,1244,1247],{"class":469,"line":599},[467,1245,1246],{"class":487}," docker",[467,1248,1249],{"class":1208}," # Docker section\n",[467,1251,1252,1255],{"class":469,"line":609},[467,1253,1254],{"class":487}," exec_time",[467,1256,1257],{"class":1208}," # Execution time\n",[467,1259,1260,1263],{"class":469,"line":615},[467,1261,1262],{"class":487}," line_sep",[467,1264,1265],{"class":1208}," # Line break\n",[467,1267,1268,1271],{"class":469,"line":773},[467,1269,1270],{"class":487}," jobs",[467,1272,1273],{"class":1208}," # Background jobs indicator\n",[467,1275,1276,1279],{"class":469,"line":778},[467,1277,1278],{"class":487}," exit_code",[467,1280,1281],{"class":1208}," # Exit code section\n",[467,1283,1284,1287],{"class":469,"line":794},[467,1285,1286],{"class":487}," char",[467,1288,1289],{"class":1208}," # Prompt character\n",[467,1291,1292],{"class":469,"line":806},[467,1293,1294],{"class":473},")\n",[54,1296,1297],{},"und auch das Symbol mit welchem eine Zeile beginnen soll. Ich wollte weiterhin das Zeichen der pure prompt verwenden und\nhabe daher den default überschrieben:",[459,1299,1301],{"className":1186,"code":1300,"language":1188,"meta":11,"style":11},"SPACESHIP_CHAR_SYMBOL='❯ '\n",[372,1302,1303],{"__ignoreMap":11},[467,1304,1305,1308,1310],{"class":469,"line":470},[467,1306,1307],{"class":473},"SPACESHIP_CHAR_SYMBOL",[467,1309,484],{"class":645},[467,1311,1312],{"class":487},"'❯ '\n",[49,1314,1042],{"id":1315},"git",[54,1317,1318,1319,264,1322,1325,1326,687,1331,687,1336,687,1341,1346],{},"Git verwende ich überwiegend über die Konsole. Mit grafischen Programmen bin ich nie richtig warm geworden. Angefangen\nmit ",[372,1320,1321],{},"gitk",[372,1323,1324],{},"git gui"," auf einem Linux Rechner bin ich\nüber ",[61,1327,1330],{"href":1328,"rel":1329},"https://git-cola.github.io/",[65],"git-cola",[61,1332,1335],{"href":1333,"rel":1334},"https://www.sourcetreeapp.com/",[65],"SourceTree",[61,1337,1340],{"href":1338,"rel":1339},"https://www.gitkraken.com/",[65],"GitKraken",[61,1342,1345],{"href":1343,"rel":1344},"https://git-fork.com/",[65],"Fork","\nund noch ein paar andere gestolpert. Aber das Geklicke und der Kontextwechsel zwischen IDE und $Werkzeug haben mich\nimmer gestört. Auch visuell fand ich bis auf GitKraken und neuerdings Fork nichts ansprechend (ja, ist mir wichtig, hat\nman anfangs schon beim Dark Theme fürs Terminal bemerkt 🙄). Hauptsächlich hat mich aber das Nichtvorhandensein bzw. die\numständliche Handhabung des rebasen gestört. Das geht für mich am schnellsten auf der Konsole mit eigenen git aliasen.",[54,1348,1349],{},"Meine häufigsten Befehle sind:",[309,1351,1352,1369,1382,1395,1412],{},[312,1353,1354,1357],{},[372,1355,1356],{},"git up",[309,1358,1359,1366],{},[312,1360,1361,1362,1365],{},"hole den aktuellen Stand von ",[372,1363,1364],{},"remote"," mit einem rebase",[312,1367,1368],{},"lösche alle lokalen Branches die bereits gemerged wurden",[312,1370,1371,1374,1379,1381],{},[372,1372,1373],{},"git cm \"jetzt funktionierts wirklich!!!1elf\"",[309,1375,1376],{},[312,1377,1378],{},"comitte alle Änderungen mit der folgenden commit message",[154,1380],{},"(inklusive neu angelegte Dateien)",[312,1383,1384,1387,1392,1394],{},[372,1385,1386],{},"git amend",[309,1388,1389],{},[312,1390,1391],{},"füge alle Änderungen dem letzten HEAD commit hinzu",[154,1393],{},"(exklusive neu angelegte Dateien)",[312,1396,1397,1400],{},[372,1398,1399],{},"git ll",[309,1401,1402,1405],{},[312,1403,1404],{},"drucke eine Liste aller commit hashes und messages des aktuellen Branches ab HEAD",[312,1406,1407,1408,1411],{},"die Länge der Liste kann mit ",[372,1409,1410],{},"-42"," begrenzt werden",[312,1413,1414,264,1417,1420],{},[372,1415,1416],{},"git fix",[372,1418,1419],{},"git ri",[309,1421,1422,1429],{},[312,1423,1424,1425,1428],{},"erzeuge einen ",[372,1426,1427],{},"fixup"," commit und räume die commits mit einem interactive rebase auf",[312,1430,1431],{},"workflow ist dann wie folgt:",[459,1433,1435],{"className":1186,"code":1434,"language":1188,"meta":11,"style":11},"❯ git ll -5\na52747d (HEAD -> master, origin/master, origin/HEAD) foo\nb858f05 bar\n4954b3d baz\n782d959 bum\n2b1c7c6 buff\n❯ git add .\n❯ git fix 782d959\n❯ git ri 782d959^\n",[372,1436,1437,1451,1474,1482,1490,1498,1506,1518,1530],{"__ignoreMap":11},[467,1438,1439,1442,1445,1448],{"class":469,"line":470},[467,1440,1441],{"class":480},"❯",[467,1443,1444],{"class":487}," git",[467,1446,1447],{"class":487}," ll",[467,1449,1450],{"class":671}," -5\n",[467,1452,1453,1456,1459,1462,1465,1468,1471],{"class":469,"line":12},[467,1454,1455],{"class":480},"a52747d",[467,1457,1458],{"class":473}," (HEAD -",[467,1460,1461],{"class":645},">",[467,1463,1464],{"class":487}," master,",[467,1466,1467],{"class":487}," origin/master,",[467,1469,1470],{"class":487}," origin/HEAD",[467,1472,1473],{"class":473},") foo\n",[467,1475,1476,1479],{"class":469,"line":529},[467,1477,1478],{"class":480},"b858f05",[467,1480,1481],{"class":487}," bar\n",[467,1483,1484,1487],{"class":469,"line":551},[467,1485,1486],{"class":480},"4954b3d",[467,1488,1489],{"class":487}," baz\n",[467,1491,1492,1495],{"class":469,"line":583},[467,1493,1494],{"class":480},"782d959",[467,1496,1497],{"class":487}," bum\n",[467,1499,1500,1503],{"class":469,"line":589},[467,1501,1502],{"class":480},"2b1c7c6",[467,1504,1505],{"class":487}," buff\n",[467,1507,1508,1510,1512,1515],{"class":469,"line":599},[467,1509,1441],{"class":480},[467,1511,1444],{"class":487},[467,1513,1514],{"class":487}," add",[467,1516,1517],{"class":487}," .\n",[467,1519,1520,1522,1524,1527],{"class":469,"line":609},[467,1521,1441],{"class":480},[467,1523,1444],{"class":487},[467,1525,1526],{"class":487}," fix",[467,1528,1529],{"class":487}," 782d959\n",[467,1531,1532,1534,1536,1539],{"class":469,"line":615},[467,1533,1441],{"class":480},[467,1535,1444],{"class":487},[467,1537,1538],{"class":487}," ri",[467,1540,1541],{"class":487}," 782d959^\n",[54,1543,1544,1545,1548],{},"Eigene git aliase können in der globalen git config unter ",[372,1546,1547],{},"~/.gitconfig"," abgelegt werden.",[54,1550,1551,1552,1555],{},"Da ich meine merge requests im Normalfall erst auf den master rebase bevor ich sie zum mergen freigebe, bin ich super\nglücklich über das ",[372,1553,1554],{},"rerere"," Feature von git. Ist das Feature aktiviert, merkt sich git beim rebasen, wie ein Konflikt\ngelöst wird. Kommt es erneut zu dem selben Konflikt, weiß git wie es diesen automatisiert zu lösen hat.",[459,1557,1559],{"className":1186,"code":1558,"language":1188,"meta":11,"style":11},"[rerere]\n enabled = true\n[alias]\n s = status\n st = stash\n co = checkout\n cob = checkout -b\n fix = commit --fixup\n ri = rebase -i --autosquash\n up = !git pull --rebase --prune && git bclean\n cm = !git add -A && git commit -m\n amend = commit -a --amend\n ll = !git --no-pager log --oneline --decorate\n bclean = \"!f() { branches=$(git branch --merged ${1-master} | grep -v \" ${1-master}$\"); [ -z \\\"$branches\\\" ] || git branch -d$branches; }; f\"\n",[372,1560,1561,1566,1577,1582,1592,1602,1612,1625,1638,1654,1681,1704,1719,1740],{"__ignoreMap":11},[467,1562,1563],{"class":469,"line":470},[467,1564,1565],{"class":473},"[rerere]\n",[467,1567,1568,1571,1574],{"class":469,"line":12},[467,1569,1570],{"class":480}," enabled",[467,1572,1573],{"class":487}," =",[467,1575,1576],{"class":671}," true\n",[467,1578,1579],{"class":469,"line":529},[467,1580,1581],{"class":473},"[alias]\n",[467,1583,1584,1587,1589],{"class":469,"line":551},[467,1585,1586],{"class":480}," s",[467,1588,1573],{"class":487},[467,1590,1591],{"class":487}," status\n",[467,1593,1594,1597,1599],{"class":469,"line":583},[467,1595,1596],{"class":480}," st",[467,1598,1573],{"class":487},[467,1600,1601],{"class":487}," stash\n",[467,1603,1604,1607,1609],{"class":469,"line":589},[467,1605,1606],{"class":480}," co",[467,1608,1573],{"class":487},[467,1610,1611],{"class":487}," checkout\n",[467,1613,1614,1617,1619,1622],{"class":469,"line":599},[467,1615,1616],{"class":480}," cob",[467,1618,1573],{"class":487},[467,1620,1621],{"class":487}," checkout",[467,1623,1624],{"class":671}," -b\n",[467,1626,1627,1630,1632,1635],{"class":469,"line":609},[467,1628,1629],{"class":480}," fix",[467,1631,1573],{"class":487},[467,1633,1634],{"class":487}," commit",[467,1636,1637],{"class":671}," --fixup\n",[467,1639,1640,1643,1645,1648,1651],{"class":469,"line":615},[467,1641,1642],{"class":480}," ri",[467,1644,1573],{"class":487},[467,1646,1647],{"class":487}," rebase",[467,1649,1650],{"class":671}," -i",[467,1652,1653],{"class":671}," --autosquash\n",[467,1655,1656,1659,1661,1664,1667,1670,1673,1676,1678],{"class":469,"line":773},[467,1657,1658],{"class":480}," up",[467,1660,1573],{"class":487},[467,1662,1663],{"class":487}," !git",[467,1665,1666],{"class":487}," pull",[467,1668,1669],{"class":671}," --rebase",[467,1671,1672],{"class":671}," --prune",[467,1674,1675],{"class":473}," && ",[467,1677,1315],{"class":480},[467,1679,1680],{"class":487}," bclean\n",[467,1682,1683,1686,1688,1690,1692,1695,1697,1699,1701],{"class":469,"line":778},[467,1684,1685],{"class":480}," cm",[467,1687,1573],{"class":487},[467,1689,1663],{"class":487},[467,1691,1514],{"class":487},[467,1693,1694],{"class":671}," -A",[467,1696,1675],{"class":473},[467,1698,1315],{"class":480},[467,1700,1634],{"class":487},[467,1702,1703],{"class":671}," -m\n",[467,1705,1706,1709,1711,1713,1716],{"class":469,"line":794},[467,1707,1708],{"class":480}," amend",[467,1710,1573],{"class":487},[467,1712,1634],{"class":487},[467,1714,1715],{"class":671}," -a",[467,1717,1718],{"class":671}," --amend\n",[467,1720,1721,1724,1726,1728,1731,1734,1737],{"class":469,"line":806},[467,1722,1723],{"class":480}," ll",[467,1725,1573],{"class":487},[467,1727,1663],{"class":487},[467,1729,1730],{"class":671}," --no-pager",[467,1732,1733],{"class":487}," log",[467,1735,1736],{"class":671}," --oneline",[467,1738,1739],{"class":671}," --decorate\n",[467,1741,1742,1745,1747,1750,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1787,1789,1791,1794,1797,1800,1802,1805,1807],{"class":469,"line":905},[467,1743,1744],{"class":480}," bclean",[467,1746,1573],{"class":487},[467,1748,1749],{"class":487}," \"!f() { branches=$(",[467,1751,1315],{"class":480},[467,1753,1754],{"class":487}," branch ",[467,1756,1757],{"class":671},"--merged",[467,1759,1760],{"class":671}," ${1",[467,1762,1763],{"class":487},"-",[467,1765,1766],{"class":473},"master",[467,1768,1769],{"class":671},"}",[467,1771,1772],{"class":645}," |",[467,1774,1775],{"class":480}," grep",[467,1777,1778],{"class":671}," -v",[467,1780,1781],{"class":487}," \" ",[467,1783,1784],{"class":671},"${1",[467,1786,1763],{"class":487},[467,1788,1766],{"class":473},[467,1790,1769],{"class":671},[467,1792,1793],{"class":487},"$\"); [ -z ",[467,1795,1796],{"class":671},"\\\"",[467,1798,1799],{"class":473},"$branches",[467,1801,1796],{"class":671},[467,1803,1804],{"class":487}," ] || git branch -d",[467,1806,1799],{"class":473},[467,1808,1809],{"class":487},"; }; f\"\n",[49,1811,1049],{"id":1812},"programme",[54,1814,1815],{},"Weitere Programme, die ich in meiner täglichen Arbeit nicht mehr missen möchte, sind:",[309,1817,1818,1840,1858,1872,1905,1926,1947],{},[312,1819,1820,91,1825,1828],{},[61,1821,1824],{"href":1822,"rel":1823},"https://brew.sh/",[65],"homebrew",[316,1826,1827],{},"(kommandozeile)",[309,1829,1830,1833],{},[312,1831,1832],{},"quasi DER Paketmanager für OSX",[312,1834,1835,1836,1839],{},"Programme installieren mit ",[372,1837,1838],{},"brew install FOO",". Schneller gehts nicht!",[312,1841,1842,91,1847,1849],{},[61,1843,1846],{"href":1844,"rel":1845},"https://github.com/direnv/direnv",[65],"direnv",[316,1848,1827],{},[309,1850,1851],{},[312,1852,1853,1854,1857],{},"lege in einem Verzeichnis eine ",[372,1855,1856],{},".envrc"," Datei ab und definiere dort z. B. die Java oder NodeJS Version, oder auch\neine andere git E-Mail-Adresse, falls man hier eine andere als die globale verwenden möchte für commits",[312,1859,1860,91,1865,1867],{},[61,1861,1864],{"href":1862,"rel":1863},"https://stedolan.github.io/jq/",[65],"jq",[316,1866,1827],{},[309,1868,1869],{},[312,1870,1871],{},"JSON Prozessor für die Kommandozeile",[312,1873,1874,91,1879,1881],{},[61,1875,1878],{"href":1876,"rel":1877},"https://github.com/nodenv/nodenv",[65],"nodenv",[316,1880,1827],{},[309,1882,1883,1892],{},[312,1884,1885,1886,1891],{},"verwalte unterschiedliche Versionen von NodeJS (Alternative zu z.\nB. ",[61,1887,1890],{"href":1888,"rel":1889},"https://github.com/creationix/nvm",[65],"Node Version Manager (nvm)",")",[312,1893,1894,1895,1898,1899,1901,1902,1904],{},"Anfang des Jahres bin ich von ",[372,1896,1897],{},"nvm"," auf ",[372,1900,1878],{}," umgestiegen, weil letzteres die Shell beim Initialisieren nicht\nblockiert. ",[372,1903,1897],{}," braucht seine Zeit zum Laden von NodeJS",[312,1906,1907,1912],{},[61,1908,1911],{"href":1909,"rel":1910},"https://www.spectacleapp.com/",[65],"spectacle",[309,1913,1914,1917,1920,1923],{},[312,1915,1916],{},"verschiebe Fenster und ändere deren Größe mit deiner Tastatur",[312,1918,1919],{},"die Tastenkombinationen sind konfigurierbar",[312,1921,1922],{},"ich hatte einen UltraWide Monitor und habe so schnell mal drei Fenster in der Breite gedrittelt verteilt",[312,1924,1925],{},"Natürlich ist das auch auf einem normalen Laptop Display sinnvoll, Fenster ohne Maus rechts/links oder oben/unten\nausrichten zu können",[312,1927,1928,1933],{},[61,1929,1932],{"href":1930,"rel":1931},"https://github.com/Clipy/Clipy",[65],"clipy",[309,1934,1935,1944],{},[312,1936,1937,1938,1943],{},"wer die copy-paste aus IntelliJ lieb gewonnen hat; clipy macht das systemweit (die Alternative wäre, das\nPowerpack für ",[61,1939,1942],{"href":1940,"rel":1941},"https://www.alfredapp.com/",[65],"Alfred"," zu kaufen)",[312,1945,1946],{},"man achte auf Passwörter! Clipy bietet die Möglichkeit, bestimmte Programme zu ignorieren. Es ist ratsam dort\nKeepassX oder sonstigen Tresor einzutragen 😉",[312,1948,1949,1952],{},[436,1950,1951],{},"spotify",[309,1953,1954],{},[312,1955,1956],{},"Musik muss natürlich sein 🎧",[54,1958,1959],{},"Vielen Dank fürs Lesen (☞゚ヮ゚)☞ (smilie eingefügt mit clipy snippets)",[986,1961,1962],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .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 .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}",{"title":11,"searchDepth":12,"depth":12,"links":1964},[1965,1966,1967],{"id":1052,"depth":12,"text":1035},{"id":1315,"depth":12,"text":1042},{"id":1812,"depth":12,"text":1049},[998,227],"2018-11-09T12:41:34","Beim synyx Camp vor zwei Wochen haben wir uns unter anderem über das Setup unserer Entwicklungsumgebungen unterhalten.\\nIm Folgenden möchte ich kurz berichten, wie ich meine eingerichtet habe und welche Programme ich in meiner alltäglichen\\nArbeit nicht mehr missen möchte.","https://synyx.de/blog/wie-meine-entwicklungsumgebung-eingerichtet-ist/",{},"/blog/wie-meine-entwicklungsumgebung-eingerichtet-ist",{"title":1014,"description":1023},"blog/wie-meine-entwicklungsumgebung-eingerichtet-ist",[1008],"Beim synyx Camp vor zwei Wochen haben wir uns unter anderem über das Setup unserer Entwicklungsumgebungen unterhalten. Im Folgenden möchte ich kurz berichten, wie ich meine eingerichtet habe und welche Programme ich in meiner alltäglichen Arbeit nicht mehr missen möchte.","38z-kKuLlmfwcQfMnzrrf1gJHoh-qvNZsZ_skAURUrY",{"id":1980,"title":1981,"author":1982,"body":1983,"category":3359,"date":3360,"description":3361,"extension":18,"link":3362,"meta":3363,"navigation":29,"path":3364,"seo":3365,"slug":1987,"stem":3366,"tags":3367,"teaser":3368,"__hash__":3369},"blog/blog/implementing-a-waiting-component-with-user-experience-in-mind.md","Implementing a waiting component with user experience in mind",[33],{"type":8,"value":1984,"toc":3354},[1985,1988,1991,1997,2015,2041,2049,2053,2056,2131,2146,2314,2330,2336,2340,2343,2349,2354,2357,2379,2583,2586,2611,2683,2698,2754,2773,2859,2888,2892,2895,2934,2937,2962,2972,3028,3037,3043,3051,3073,3174,3186,3351],[45,1986,1981],{"id":1987},"implementing-a-waiting-component-with-user-experience-in-mind",[54,1989,1990],{},"Giving fast feedback to users has been improved by single page applications over the request response cycle. However,\nthere is one serious downside with this approach. Elements are popping out of the wild on various sections everytime.\nParticular data loading indicated by a waiting animation is affected with this phenomenon. In this blog I’d like to\npresent you our solution of a UI component that takes care about delaying the rendering of the animation.",[54,1992,1993,1996],{},[436,1994,1995],{},"Disclaimer:"," we’re using React in our frontend (without server side rendering). In case you don’t know React: React\nprovides lifecycle hooks for UI components like",[309,1998,1999,2004,2009],{},[312,2000,2001],{},[372,2002,2003],{},"render",[312,2005,2006],{},[372,2007,2008],{},"willUpdate",[312,2010,2011,2012],{},"or .",[372,2013,2014],{},"didUpdate",[54,2016,2017,2018,2021,2022,2025,2026,2028,2029,2031,2032,2034,2035,2040],{},"These hooks can be used to do internal stuff your component requires to be rendered correctly. React components can\neither be updated with changing ",[372,2019,2020],{},"properties","or updating ",[372,2023,2024],{},"state",". Properties are actually the public API of the\ncomponent. The",[372,2027,2024],{},", however, is the antagonist which can only be updated by the component itself. Changing\n",[372,2030,2020],{}," or ",[372,2033,2024],{}," triggers specific lifecycle hooks and finally a rerendering of the component. Don’t hesitate to\nread the ",[61,2036,2039],{"href":2037,"rel":2038},"https://reactjs.org/docs/rendering-elements.html",[65],"react docs"," for more detail.",[54,2042,2043,2044,675],{},"tl;dr source code is available ",[61,2045,2048],{"href":2046,"rel":2047},"https://github.com/bseber/waiting",[65],"on github",[49,2050,2052],{"id":2051},"loading-or-not-loading","loading or not loading",[54,2054,2055],{},"At first we have to satisfy the basic need. The user must get feedback whether we’re loading data currently or not. The\nmost simple component takes a boolean property that reflects the current state.",[459,2057,2059],{"className":636,"code":2058,"language":638,"meta":11,"style":11},"class Waiting extends React.Component {\n render() {\n return this.props.loading ? \u003Cdiv>loading...\u003C/div> : null;\n }\n}\n",[372,2060,2061,2080,2087,2123,2127],{"__ignoreMap":11},[467,2062,2063,2065,2068,2070,2073,2075,2078],{"class":469,"line":470},[467,2064,646],{"class":645},[467,2066,2067],{"class":480}," Waiting",[467,2069,652],{"class":645},[467,2071,2072],{"class":480}," React",[467,2074,675],{"class":473},[467,2076,2077],{"class":480},"Component",[467,2079,658],{"class":473},[467,2081,2082,2085],{"class":469,"line":12},[467,2083,2084],{"class":480}," render",[467,2086,666],{"class":473},[467,2088,2089,2092,2095,2098,2101,2104,2106,2109,2111,2114,2117,2120],{"class":469,"line":529},[467,2090,2091],{"class":645}," return",[467,2093,2094],{"class":671}," this",[467,2096,2097],{"class":473},".props.loading ",[467,2099,2100],{"class":645},"?",[467,2102,2103],{"class":473}," \u003C",[467,2105,448],{"class":477},[467,2107,2108],{"class":473},">loading...\u003C/",[467,2110,448],{"class":477},[467,2112,2113],{"class":473},"> ",[467,2115,2116],{"class":645},":",[467,2118,2119],{"class":671}," null",[467,2121,2122],{"class":473},";\n",[467,2124,2125],{"class":469,"line":551},[467,2126,765],{"class":473},[467,2128,2129],{"class":469,"line":583},[467,2130,770],{"class":473},[54,2132,2133,2134,2137,2138,2141,2142,2145],{},"This component can now be used in our App. The loading info is visible as long as the",[372,2135,2136],{},"loading"," flag is set to ",[372,2139,2140],{},"true"," and\nhidden as soon as the flag is toggled.",[372,2143,2144],{},"MyDataView"," is just another component that takes care about rendering the data.",[459,2147,2149],{"className":636,"code":2148,"language":638,"meta":11,"style":11},"class MyApp extends React.Component {\n // initialState\n // no data existent and we're loading currently\n state = {\n data: null,\n loading: true\n };\n\n renderData() {\n return this.state.data ? : null;\n }\n\n render() {\n return (\n \u003Cdiv>\n\n {this.renderData()}\n \u003C/div>\n );\n }\n}\n",[372,2150,2151,2168,2173,2178,2187,2197,2205,2210,2214,2221,2239,2243,2247,2253,2260,2269,2273,2288,2298,2304,2309],{"__ignoreMap":11},[467,2152,2153,2155,2158,2160,2162,2164,2166],{"class":469,"line":470},[467,2154,646],{"class":645},[467,2156,2157],{"class":480}," MyApp",[467,2159,652],{"class":645},[467,2161,2072],{"class":480},[467,2163,675],{"class":473},[467,2165,2077],{"class":480},[467,2167,658],{"class":473},[467,2169,2170],{"class":469,"line":12},[467,2171,2172],{"class":1208}," // initialState\n",[467,2174,2175],{"class":469,"line":529},[467,2176,2177],{"class":1208}," // no data existent and we're loading currently\n",[467,2179,2180,2183,2185],{"class":469,"line":551},[467,2181,2182],{"class":696}," state",[467,2184,1573],{"class":645},[467,2186,658],{"class":473},[467,2188,2189,2192,2195],{"class":469,"line":583},[467,2190,2191],{"class":473}," data: ",[467,2193,2194],{"class":671},"null",[467,2196,803],{"class":473},[467,2198,2199,2202],{"class":469,"line":589},[467,2200,2201],{"class":473}," loading: ",[467,2203,2204],{"class":671},"true\n",[467,2206,2207],{"class":469,"line":599},[467,2208,2209],{"class":473}," };\n",[467,2211,2212],{"class":469,"line":609},[467,2213,612],{"emptyLinePlaceholder":29},[467,2215,2216,2219],{"class":469,"line":615},[467,2217,2218],{"class":480}," renderData",[467,2220,666],{"class":473},[467,2222,2223,2225,2227,2230,2232,2235,2237],{"class":469,"line":773},[467,2224,2091],{"class":645},[467,2226,2094],{"class":671},[467,2228,2229],{"class":473},".state.data ",[467,2231,2100],{"class":645},[467,2233,2234],{"class":645}," :",[467,2236,2119],{"class":671},[467,2238,2122],{"class":473},[467,2240,2241],{"class":469,"line":778},[467,2242,765],{"class":473},[467,2244,2245],{"class":469,"line":794},[467,2246,612],{"emptyLinePlaceholder":29},[467,2248,2249,2251],{"class":469,"line":806},[467,2250,2084],{"class":480},[467,2252,666],{"class":473},[467,2254,2255,2257],{"class":469,"line":905},[467,2256,2091],{"class":645},[467,2258,2259],{"class":473}," (\n",[467,2261,2262,2265,2267],{"class":469,"line":911},[467,2263,2264],{"class":473}," \u003C",[467,2266,448],{"class":477},[467,2268,499],{"class":473},[467,2270,2271],{"class":469,"line":916},[467,2272,612],{"emptyLinePlaceholder":29},[467,2274,2275,2278,2280,2282,2285],{"class":469,"line":922},[467,2276,2277],{"class":473}," {",[467,2279,747],{"class":671},[467,2281,675],{"class":473},[467,2283,2284],{"class":480},"renderData",[467,2286,2287],{"class":473},"()}\n",[467,2289,2291,2294,2296],{"class":469,"line":2290},18,[467,2292,2293],{"class":473}," \u003C/",[467,2295,448],{"class":477},[467,2297,499],{"class":473},[467,2299,2301],{"class":469,"line":2300},19,[467,2302,2303],{"class":473}," );\n",[467,2305,2307],{"class":469,"line":2306},20,[467,2308,765],{"class":473},[467,2310,2312],{"class":469,"line":2311},21,[467,2313,770],{"class":473},[54,2315,2316,2317,2322,2323,2326,2327,2329],{},"One benefit of this solution is that we now have a reusable component. We don’t have to care about the visualisation\nstuff anymore at every place. It could render the div element with a static text or it could render some more advanced\ncss animation. For instance we could change the loading animation to use this\nawesome ",[61,2318,2321],{"href":2319,"rel":2320},"https://codepen.io/dissimulate/pen/vlfyA",[65],"codepen"," with refactoring the",[372,2324,2325],{},"Waiting"," component implementation only.\nConsumers of the ",[372,2328,2325],{}," component wouldn’t have to be touched.",[54,2331,2332,2333,2335],{},"A second benefit is the really simple implementation of the ",[372,2334,2325],{}," component. Even without knowing React or\nJavaScript in detail you quickly see that a div or nothing is rendered.",[49,2337,2339],{"id":2338},"pretend-not-loading-when-its-fast","pretend not loading when it’s fast",[54,2341,2342],{},"The next step is user experience improvement. We don’t want to render the loading text",[54,2344,2345,2346,2348],{},"when the ",[372,2347,2136],{}," flag is toggled back to false within 100ms.",[75,2350,2351],{},[54,2352,2353],{},"0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no\nspecial feedback is necessary except to display the result.",[54,2355,2356],{},"Jakob Nielsen",[54,2358,2359,2360,2362,2363,2366,2367,2370,2371,2374,2375,2378],{},"To keep changes small let us first map the loading property value to the internal component state. React takes care\nabout calling ",[372,2361,2003],{}," when either new properties are given or state is changed with ",[372,2364,2365],{},"setState",". So in the constructor\nwe’re mapping the original loading flag to render the initially intended state. Let’s say the ",[316,2368,2369],{},"yep, we’re currently\nloading"," state. Soon afterwards the property will eventually swap to ",[316,2372,2373],{},"nop, we’re finished loading",". This can be\nintercepted by the ",[372,2376,2377],{},"componentWillReceiveProps"," lifecycle hook . Just like in the constructor we’re mapping the property\nto the internal state.",[459,2380,2382],{"className":636,"code":2381,"language":638,"meta":11,"style":11},"class Waiting extends React.Component {\n+ constructor(props) {\n+ super();\n+ this.state = { loading: props.loading };\n+ }\n+\n+ componentWillReceiveProps(nextProps) {\n+ if (nextProps.loading !== this.props.loading) {\n+ this.setState({ loading: nextProps.loading });\n+ }\n+ }\n+\n render() {\n- return this.props.loading ? \u003Cdiv>loading...\u003C/div> : null;\n+ return this.state.loading ? \u003Cdiv>loading...\u003C/div> : null;\n }\n}\n",[372,2383,2384,2400,2416,2425,2439,2445,2450,2464,2482,2496,2502,2508,2512,2518,2546,2575,2579],{"__ignoreMap":11},[467,2385,2386,2388,2390,2392,2394,2396,2398],{"class":469,"line":470},[467,2387,646],{"class":645},[467,2389,2067],{"class":480},[467,2391,652],{"class":645},[467,2393,2072],{"class":480},[467,2395,675],{"class":473},[467,2397,2077],{"class":480},[467,2399,658],{"class":473},[467,2401,2402,2405,2408,2410,2413],{"class":469,"line":12},[467,2403,2404],{"class":645},"+",[467,2406,2407],{"class":645}," constructor",[467,2409,681],{"class":473},[467,2411,2412],{"class":696},"props",[467,2414,2415],{"class":473},") {\n",[467,2417,2418,2420,2423],{"class":469,"line":529},[467,2419,2404],{"class":645},[467,2421,2422],{"class":671}," super",[467,2424,716],{"class":473},[467,2426,2427,2429,2431,2434,2436],{"class":469,"line":551},[467,2428,2404],{"class":645},[467,2430,672],{"class":671},[467,2432,2433],{"class":473},".state ",[467,2435,484],{"class":645},[467,2437,2438],{"class":473}," { loading: props.loading };\n",[467,2440,2441,2443],{"class":469,"line":583},[467,2442,2404],{"class":645},[467,2444,765],{"class":473},[467,2446,2447],{"class":469,"line":589},[467,2448,2449],{"class":645},"+\n",[467,2451,2452,2454,2457,2459,2462],{"class":469,"line":599},[467,2453,2404],{"class":645},[467,2455,2456],{"class":480}," componentWillReceiveProps",[467,2458,681],{"class":473},[467,2460,2461],{"class":696},"nextProps",[467,2463,2415],{"class":473},[467,2465,2466,2468,2471,2474,2477,2479],{"class":469,"line":609},[467,2467,2404],{"class":645},[467,2469,2470],{"class":645}," if",[467,2472,2473],{"class":473}," (nextProps.loading ",[467,2475,2476],{"class":645},"!==",[467,2478,2094],{"class":671},[467,2480,2481],{"class":473},".props.loading) {\n",[467,2483,2484,2486,2489,2491,2493],{"class":469,"line":615},[467,2485,2404],{"class":645},[467,2487,2488],{"class":671}," this",[467,2490,675],{"class":473},[467,2492,2365],{"class":480},[467,2494,2495],{"class":473},"({ loading: nextProps.loading });\n",[467,2497,2498,2500],{"class":469,"line":773},[467,2499,2404],{"class":645},[467,2501,908],{"class":473},[467,2503,2504,2506],{"class":469,"line":778},[467,2505,2404],{"class":645},[467,2507,765],{"class":473},[467,2509,2510],{"class":469,"line":794},[467,2511,2449],{"class":645},[467,2513,2514,2516],{"class":469,"line":806},[467,2515,2084],{"class":480},[467,2517,666],{"class":473},[467,2519,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544],{"class":469,"line":905},[467,2521,1763],{"class":645},[467,2523,2091],{"class":645},[467,2525,2094],{"class":671},[467,2527,2097],{"class":473},[467,2529,2100],{"class":645},[467,2531,2103],{"class":473},[467,2533,448],{"class":477},[467,2535,2108],{"class":473},[467,2537,448],{"class":477},[467,2539,2113],{"class":473},[467,2541,2116],{"class":645},[467,2543,2119],{"class":671},[467,2545,2122],{"class":473},[467,2547,2548,2550,2552,2554,2557,2559,2561,2563,2565,2567,2569,2571,2573],{"class":469,"line":911},[467,2549,2404],{"class":645},[467,2551,2091],{"class":645},[467,2553,2094],{"class":671},[467,2555,2556],{"class":473},".state.loading ",[467,2558,2100],{"class":645},[467,2560,2103],{"class":473},[467,2562,448],{"class":477},[467,2564,2108],{"class":473},[467,2566,448],{"class":477},[467,2568,2113],{"class":473},[467,2570,2116],{"class":645},[467,2572,2119],{"class":671},[467,2574,2122],{"class":473},[467,2576,2577],{"class":469,"line":916},[467,2578,765],{"class":473},[467,2580,2581],{"class":469,"line":922},[467,2582,770],{"class":473},[54,2584,2585],{},"So far we’ve gained nothing but complexity /o",[54,2587,2588,2589,2591,2592,2594,2595,2597,2598,2601,2602,2604,2605,2607,2608,675],{},"Now to the interesting part. As soon as the",[372,2590,2325],{}," component receives new properties we’re starting a timeout to\nupdate the internal state with a delay of 100ms. Remember react calls",[372,2593,2003],{},"on property changes as well as on state\nchanges. So",[372,2596,2003],{}," is called two times now actually. The first time it renders the same as previously ",[316,2599,2600],{},"nop, we’re not\nloading",". After 100ms",[372,2603,2365],{}," is called which triggers the second ",[372,2606,2003],{},"cycle ",[316,2609,2610],{},"yep, we’re loading",[459,2612,2614],{"className":835,"code":2613,"language":837,"meta":11,"style":11},"class Waiting extends React.Component {\n constructor() { ... }\n\n componentWillReceiveProps(nextProps) {\n if (nextProps.loading !== this.props.loading) {\n+ window.clearTimeout(this._loadingTimeout);\n+ this._loadingTimeout = window.setTimeout(() => {\n this.setState({ loading: nextProps.loading });\n+ }, 100);\n }\n\n render() { ... }\n }\n}\n",[372,2615,2616,2621,2626,2630,2635,2640,2645,2650,2655,2660,2665,2669,2674,2679],{"__ignoreMap":11},[467,2617,2618],{"class":469,"line":470},[467,2619,2620],{},"class Waiting extends React.Component {\n",[467,2622,2623],{"class":469,"line":12},[467,2624,2625],{}," constructor() { ... }\n",[467,2627,2628],{"class":469,"line":529},[467,2629,612],{"emptyLinePlaceholder":29},[467,2631,2632],{"class":469,"line":551},[467,2633,2634],{}," componentWillReceiveProps(nextProps) {\n",[467,2636,2637],{"class":469,"line":583},[467,2638,2639],{}," if (nextProps.loading !== this.props.loading) {\n",[467,2641,2642],{"class":469,"line":589},[467,2643,2644],{},"+ window.clearTimeout(this._loadingTimeout);\n",[467,2646,2647],{"class":469,"line":599},[467,2648,2649],{},"+ this._loadingTimeout = window.setTimeout(() => {\n",[467,2651,2652],{"class":469,"line":609},[467,2653,2654],{}," this.setState({ loading: nextProps.loading });\n",[467,2656,2657],{"class":469,"line":615},[467,2658,2659],{},"+ }, 100);\n",[467,2661,2662],{"class":469,"line":773},[467,2663,2664],{}," }\n",[467,2666,2667],{"class":469,"line":778},[467,2668,612],{"emptyLinePlaceholder":29},[467,2670,2671],{"class":469,"line":794},[467,2672,2673],{}," render() { ... }\n",[467,2675,2676],{"class":469,"line":806},[467,2677,2678],{}," }\n",[467,2680,2681],{"class":469,"line":905},[467,2682,770],{},[54,2684,2685,2686,2689,2690,2693,2694,2697],{},"But wait, what’s happening now when the loading property is swapped the other way around from ",[316,2687,2688],{},"yep"," to ",[316,2691,2692],{},"nop","? Remember\nthe implementation of ",[372,2695,2696],{},"MyApp"," from above?",[459,2699,2701],{"className":835,"code":2700,"language":837,"meta":11,"style":11},"class MyApp extends React.Component {\n // ...\n render() {\n return (\n \u003Cdiv>\n\n {this.renderData()}\n \u003C/div>\n );\n }\n}\n",[372,2702,2703,2708,2713,2718,2723,2728,2732,2737,2742,2746,2750],{"__ignoreMap":11},[467,2704,2705],{"class":469,"line":470},[467,2706,2707],{},"class MyApp extends React.Component {\n",[467,2709,2710],{"class":469,"line":12},[467,2711,2712],{}," // ...\n",[467,2714,2715],{"class":469,"line":529},[467,2716,2717],{}," render() {\n",[467,2719,2720],{"class":469,"line":551},[467,2721,2722],{}," return (\n",[467,2724,2725],{"class":469,"line":583},[467,2726,2727],{}," \u003Cdiv>\n",[467,2729,2730],{"class":469,"line":589},[467,2731,612],{"emptyLinePlaceholder":29},[467,2733,2734],{"class":469,"line":599},[467,2735,2736],{}," {this.renderData()}\n",[467,2738,2739],{"class":469,"line":609},[467,2740,2741],{}," \u003C/div>\n",[467,2743,2744],{"class":469,"line":615},[467,2745,2303],{},[467,2747,2748],{"class":469,"line":773},[467,2749,765],{},[467,2751,2752],{"class":469,"line":778},[467,2753,770],{},[54,2755,2756,2757,2759,2760,2763,2764,2767,2768,2770,2771,675],{},"The",[372,2758,2325],{}," component receives the updated loading flag ",[316,2761,2762],{},"false"," and delays it’s internal rendering while\n",[372,2765,2766],{},"this.renderData()"," renders the actual data. So the loading info is shortly visible amongst the data. Fortunately this\ncan be fixed easily. We just have to update immediately when the ",[372,2769,2136],{}," property is set to ",[316,2772,2762],{},[459,2774,2776],{"className":835,"code":2775,"language":837,"meta":11,"style":11},"class Waiting extends React.Component {\n constructor() { ... }\n\n componentWillReceiveProps(nextProps) {\n if (nextProps.loading !== this.props.loading) {\n window.clearTimeout(this._loadingTimeout);\n+ if (nextProps.loading) {\n this._loadingTimeout = window.setTimeout(() => {\n this.setState({ loading: nextProps.loading });\n }, 100);\n+ } else {\n+ this.setState({ loading: false });\n+ }\n }\n }\n\n render() { ... }\n}\n",[372,2777,2778,2782,2786,2790,2794,2798,2803,2808,2813,2818,2823,2828,2833,2838,2842,2846,2850,2855],{"__ignoreMap":11},[467,2779,2780],{"class":469,"line":470},[467,2781,2620],{},[467,2783,2784],{"class":469,"line":12},[467,2785,2625],{},[467,2787,2788],{"class":469,"line":529},[467,2789,612],{"emptyLinePlaceholder":29},[467,2791,2792],{"class":469,"line":551},[467,2793,2634],{},[467,2795,2796],{"class":469,"line":583},[467,2797,2639],{},[467,2799,2800],{"class":469,"line":589},[467,2801,2802],{}," window.clearTimeout(this._loadingTimeout);\n",[467,2804,2805],{"class":469,"line":599},[467,2806,2807],{},"+ if (nextProps.loading) {\n",[467,2809,2810],{"class":469,"line":609},[467,2811,2812],{}," this._loadingTimeout = window.setTimeout(() => {\n",[467,2814,2815],{"class":469,"line":615},[467,2816,2817],{}," this.setState({ loading: nextProps.loading });\n",[467,2819,2820],{"class":469,"line":773},[467,2821,2822],{}," }, 100);\n",[467,2824,2825],{"class":469,"line":778},[467,2826,2827],{},"+ } else {\n",[467,2829,2830],{"class":469,"line":794},[467,2831,2832],{},"+ this.setState({ loading: false });\n",[467,2834,2835],{"class":469,"line":806},[467,2836,2837],{},"+ }\n",[467,2839,2840],{"class":469,"line":905},[467,2841,908],{},[467,2843,2844],{"class":469,"line":911},[467,2845,765],{},[467,2847,2848],{"class":469,"line":916},[467,2849,612],{"emptyLinePlaceholder":29},[467,2851,2852],{"class":469,"line":922},[467,2853,2854],{}," render() { ... }\n",[467,2856,2857],{"class":469,"line":2290},[467,2858,770],{},[54,2860,2861,2862,2865,2866,2868,2869,2871,2872,2874,2875,2880,2881,2884,2885,1891],{},"Now we’ve gained a good user experience by not displaying the loading info if the loading property is toggled from ",[316,2863,2864],{},"yay","\nback to ",[316,2867,2692],{}," within 100ms. There is no flickering anymore o/ However, we’ve payed with some complexity in the\n",[372,2870,2325],{}," component and even have async stuff happening there. So testing consumers of the ",[372,2873,2325],{}," component could be\nconfusing. But in my opinion the better user experience is worth the complexity and tests should be fine as long\nas ",[61,2876,2879],{"href":2877,"rel":2878},"https://reactjs.org/docs/shallow-renderer.html",[65],"shallowRendering"," is used. Otherwise we have to use the timemachine\nfeature of the testing library (e.g. jest provides ",[372,2882,2883],{},"jest.useFakeTimers()"," and ",[372,2886,2887],{},"jest.runTimersToTime(100)",[49,2889,2891],{"id":2890},"improved-handling-of-data-rendering","improved handling of data rendering",[54,2893,2894],{},"Currently we have a waiting component that takes care about delaying the loading info. But the consumer is still\nresponsible to check itself whether the data is available and should be rendered or not.",[459,2896,2898],{"className":636,"code":2897,"language":638,"meta":11,"style":11},"renderData() {\n return this.state.data\n ?\n : null;\n}\n",[372,2899,2900,2906,2916,2921,2930],{"__ignoreMap":11},[467,2901,2902,2904],{"class":469,"line":470},[467,2903,2284],{"class":480},[467,2905,666],{"class":473},[467,2907,2908,2911,2913],{"class":469,"line":12},[467,2909,2910],{"class":645}," return",[467,2912,2094],{"class":671},[467,2914,2915],{"class":473},".state.data\n",[467,2917,2918],{"class":469,"line":529},[467,2919,2920],{"class":645}," ?\n",[467,2922,2923,2926,2928],{"class":469,"line":551},[467,2924,2925],{"class":645}," :",[467,2927,2119],{"class":671},[467,2929,2122],{"class":473},[467,2931,2932],{"class":469,"line":583},[467,2933,770],{"class":473},[54,2935,2936],{},"However, my collegues and my humble self could live with this redundancy actually. It is explicit and the waiting\ncomponent wouldn’t be bloated with more features and complexity. But in our project we had the following issue (amongst\nsome others…)",[54,2938,2939,2940,2942,2943,2946,2947,2950,2951,2954,2955,2957,2958,2961],{},"Given",[372,2941,2144],{},"renders a list of items with a headline and other eye candy stuff. It takes care about rendering a ",[316,2944,2945],{},"no\ndata"," info banner when the given list is empty. The default ",[372,2948,2949],{},"this.state.data"," value is an empty array instead of\nundefined or null to avoid the notorious ",[372,2952,2953],{},"Cannot read property XXX of undefined",". Then the code snippet above results in\nalways rendering ",[372,2956,2144],{}," and therefore the ",[316,2959,2960],{},"no data"," info banner (empty array is a truthy expression).",[54,2963,2964,2965,2967,2968,2971],{},"The unwanted ",[316,2966,2960],{}," info banner could be avoided by adding the ",[372,2969,2970],{},"this.state.loading"," flag to the condition. But that’s\nnot really satisfying since this adds more complexity which even will be copied and pasted into other components.",[459,2973,2975],{"className":636,"code":2974,"language":638,"meta":11,"style":11},"renderData() {\n return (this.state.data && !this.state.loading)\n ?\n : null;\n}\n",[372,2976,2977,2983,3012,3016,3024],{"__ignoreMap":11},[467,2978,2979,2981],{"class":469,"line":470},[467,2980,2284],{"class":480},[467,2982,666],{"class":473},[467,2984,2985,2987,2989,2991,2993,2996,2999,3001,3004,3007,3009],{"class":469,"line":12},[467,2986,2910],{"class":645},[467,2988,693],{"class":473},[467,2990,747],{"class":671},[467,2992,2229],{"class":473},[467,2994,2995],{"class":645},"&",[467,2997,2998],{"class":473},"amp;",[467,3000,2995],{"class":645},[467,3002,3003],{"class":473},"amp; ",[467,3005,3006],{"class":645},"!",[467,3008,747],{"class":671},[467,3010,3011],{"class":473},".state.loading)\n",[467,3013,3014],{"class":469,"line":529},[467,3015,2920],{"class":645},[467,3017,3018,3020,3022],{"class":469,"line":551},[467,3019,2925],{"class":645},[467,3021,2119],{"class":671},[467,3023,2122],{"class":473},[467,3025,3026],{"class":469,"line":583},[467,3027,770],{"class":473},[54,3029,3030,3031,3033,3034,3036],{},"Furthermore… remember the actual challenge we tried to solve with the ",[372,3032,2325],{},"component which delays the rendering of\nthe loading info? Exactly, we wanted to avoid flickering and displaying the loading info when the data is received\nwithin 100ms. Now we’ve added this again for ",[372,3035,2144],{},". The component will be unmounted and mounted within 42ms for\ninstance. The new data is visible but all eye candy around the data list (like the headline) is gone and rerendered\nwithin one blink of an eye.",[54,3038,3039,3040,3042],{},"So let’s improve the ",[372,3041,2325],{},"component to handle the rendering of it’s children. We have two react techniques to\nimplement this:",[309,3044,3045,3048],{},[312,3046,3047],{},"render props",[312,3049,3050],{},"function as child",[54,3052,3053,3054,3057,3058,3060,3061,3064,3065,2884,3067,3069,3070,3072],{},"Both are the same actually. The ",[316,3055,3056],{},"render prop"," pattern uses a function passed as component property to render something.\nThe ",[316,3059,3050],{}," pattern is… well… the same. ",[372,3062,3063],{},"children"," is just an additional property of a React component. The\ndifference between ",[316,3066,3047],{},[316,3068,3050],{}," is the syntax. Personally I prefer ",[316,3071,3047],{}," since this\nis more explicit and doesn’t leave room of misconception for people not knowing React and JSX in detail.",[459,3074,3076],{"className":636,"code":3075,"language":638,"meta":11,"style":11},"class RenderProps extends React.Component {\n render() {\n return this.renderData()} />;\n }\n}\n\nclass FunctionAsChild extends React.Component {\n render() {\n return {() => this.renderData()};\n }\n}\n",[372,3077,3078,3095,3101,3124,3128,3132,3136,3153,3159,3166,3170],{"__ignoreMap":11},[467,3079,3080,3082,3085,3087,3089,3091,3093],{"class":469,"line":470},[467,3081,646],{"class":645},[467,3083,3084],{"class":480}," RenderProps",[467,3086,652],{"class":645},[467,3088,2072],{"class":480},[467,3090,675],{"class":473},[467,3092,2077],{"class":480},[467,3094,658],{"class":473},[467,3096,3097,3099],{"class":469,"line":12},[467,3098,2084],{"class":480},[467,3100,666],{"class":473},[467,3102,3103,3105,3108,3110,3112,3115,3118,3121],{"class":469,"line":529},[467,3104,2091],{"class":645},[467,3106,3107],{"class":671}," this",[467,3109,675],{"class":473},[467,3111,2284],{"class":480},[467,3113,3114],{"class":473},"()} ",[467,3116,3117],{"class":645},"/&",[467,3119,3120],{"class":696},"gt",[467,3122,3123],{"class":473},";;\n",[467,3125,3126],{"class":469,"line":551},[467,3127,765],{"class":473},[467,3129,3130],{"class":469,"line":583},[467,3131,770],{"class":473},[467,3133,3134],{"class":469,"line":589},[467,3135,612],{"emptyLinePlaceholder":29},[467,3137,3138,3140,3143,3145,3147,3149,3151],{"class":469,"line":599},[467,3139,646],{"class":645},[467,3141,3142],{"class":480}," FunctionAsChild",[467,3144,652],{"class":645},[467,3146,2072],{"class":480},[467,3148,675],{"class":473},[467,3150,2077],{"class":480},[467,3152,658],{"class":473},[467,3154,3155,3157],{"class":469,"line":609},[467,3156,2084],{"class":480},[467,3158,666],{"class":473},[467,3160,3161,3163],{"class":469,"line":615},[467,3162,2091],{"class":645},[467,3164,3165],{"class":473}," {() => this.renderData()};\n",[467,3167,3168],{"class":469,"line":773},[467,3169,765],{"class":473},[467,3171,3172],{"class":469,"line":778},[467,3173,770],{"class":473},[54,3175,3176,3177,3179,3180,3182,3183,675],{},"The first step is to extend the ",[372,3178,2325],{},"component with a render property. Instead of returning ",[372,3181,2194],{},"when data is not\nloading we have to call ",[372,3184,3185],{},"this.props.render",[459,3187,3189],{"className":636,"code":3188,"language":638,"meta":11,"style":11},"class Waiting extends React.Component {\n constructor() { ... }\n\n componentWillReceiveProps(nextProps) { ... }\n\n+ renderContent() {\n+ return this.state.loading ? \u003Cdiv>loading...\u003C/div> : this.props.render();\n+ }\n+\n render() {\n- return this.state.loading ? \u003Cdiv>loading...\u003C/div> : null;\n+ return this.renderContent();\n }\n}\n",[372,3190,3191,3207,3219,3223,3238,3242,3251,3284,3290,3294,3300,3328,3343,3347],{"__ignoreMap":11},[467,3192,3193,3195,3197,3199,3201,3203,3205],{"class":469,"line":470},[467,3194,646],{"class":645},[467,3196,2067],{"class":480},[467,3198,652],{"class":645},[467,3200,2072],{"class":480},[467,3202,675],{"class":473},[467,3204,2077],{"class":480},[467,3206,658],{"class":473},[467,3208,3209,3211,3214,3217],{"class":469,"line":12},[467,3210,2407],{"class":645},[467,3212,3213],{"class":473},"() { ",[467,3215,3216],{"class":645},"...",[467,3218,2678],{"class":473},[467,3220,3221],{"class":469,"line":529},[467,3222,612],{"emptyLinePlaceholder":29},[467,3224,3225,3227,3229,3231,3234,3236],{"class":469,"line":551},[467,3226,2456],{"class":480},[467,3228,681],{"class":473},[467,3230,2461],{"class":696},[467,3232,3233],{"class":473},") { ",[467,3235,3216],{"class":645},[467,3237,2678],{"class":473},[467,3239,3240],{"class":469,"line":583},[467,3241,612],{"emptyLinePlaceholder":29},[467,3243,3244,3246,3249],{"class":469,"line":589},[467,3245,2404],{"class":645},[467,3247,3248],{"class":480}," renderContent",[467,3250,666],{"class":473},[467,3252,3253,3255,3257,3259,3261,3263,3265,3267,3269,3271,3273,3275,3277,3280,3282],{"class":469,"line":599},[467,3254,2404],{"class":645},[467,3256,2091],{"class":645},[467,3258,2094],{"class":671},[467,3260,2556],{"class":473},[467,3262,2100],{"class":645},[467,3264,2103],{"class":473},[467,3266,448],{"class":477},[467,3268,2108],{"class":473},[467,3270,448],{"class":477},[467,3272,2113],{"class":473},[467,3274,2116],{"class":645},[467,3276,2094],{"class":671},[467,3278,3279],{"class":473},".props.",[467,3281,2003],{"class":480},[467,3283,716],{"class":473},[467,3285,3286,3288],{"class":469,"line":609},[467,3287,2404],{"class":645},[467,3289,765],{"class":473},[467,3291,3292],{"class":469,"line":615},[467,3293,2449],{"class":645},[467,3295,3296,3298],{"class":469,"line":773},[467,3297,2084],{"class":480},[467,3299,666],{"class":473},[467,3301,3302,3304,3306,3308,3310,3312,3314,3316,3318,3320,3322,3324,3326],{"class":469,"line":778},[467,3303,1763],{"class":645},[467,3305,2091],{"class":645},[467,3307,2094],{"class":671},[467,3309,2556],{"class":473},[467,3311,2100],{"class":645},[467,3313,2103],{"class":473},[467,3315,448],{"class":477},[467,3317,2108],{"class":473},[467,3319,448],{"class":477},[467,3321,2113],{"class":473},[467,3323,2116],{"class":645},[467,3325,2119],{"class":671},[467,3327,2122],{"class":473},[467,3329,3330,3332,3334,3336,3338,3341],{"class":469,"line":794},[467,3331,2404],{"class":645},[467,3333,2091],{"class":645},[467,3335,2094],{"class":671},[467,3337,675],{"class":473},[467,3339,3340],{"class":480},"renderContent",[467,3342,716],{"class":473},[467,3344,3345],{"class":469,"line":806},[467,3346,765],{"class":473},[467,3348,3349],{"class":469,"line":905},[467,3350,770],{"class":473},[986,3352,3353],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":11,"searchDepth":12,"depth":12,"links":3355},[3356,3357,3358],{"id":2051,"depth":12,"text":2052},{"id":2338,"depth":12,"text":2339},{"id":2890,"depth":12,"text":2891},[998,227],"2017-12-14T12:28:51","Giving fast feedback to users has been improved by single page applications over the request response cycle. However,\\nthere is one serious downside with this approach. Elements are popping out of the wild on various sections everytime.\\nParticular data loading indicated by a waiting animation is affected with this phenomenon. In this blog I’d like to\\npresent you our solution of a UI component that takes care about delaying the rendering of the animation.","https://synyx.de/blog/implementing-a-waiting-component-with-user-experience-in-mind/",{},"/blog/implementing-a-waiting-component-with-user-experience-in-mind",{"title":1981,"description":1990},"blog/implementing-a-waiting-component-with-user-experience-in-mind",[],"Giving fast feedback to users has been improved by single page applications over the request response cycle. However, there is one serious downside with this approach. Elements are popping out of the wild on various sections everytime. Particular data loading indicated by a waiting animation is affected with this phenomenon. In this blog I’d like to present you our solution of a UI component that takes care about delaying the rendering of the animation.","McgKfmY5KyiZhLIHs5tLcAtXDXJuUH3jp9wYxZq7gns",{"id":3371,"title":3372,"author":3373,"body":3375,"category":3528,"date":3529,"description":11,"extension":18,"link":3530,"meta":3531,"navigation":29,"path":3532,"seo":3533,"slug":3534,"stem":3535,"tags":3536,"teaser":3539,"__hash__":3540},"blog/blog/von-profis-lernen-heisst-siegen-lernen.md","Von Profis lernen heißt siegen lernen",[33,3374],"ulrich",{"type":8,"value":3376,"toc":3519},[3377,3380,3402,3405,3418,3421,3424,3428,3431,3434,3438,3441,3444,3448,3451,3454,3457,3461,3464,3467,3471,3474,3480,3483,3487,3490,3493,3497,3500,3505,3508,3513,3516],[45,3378,3372],{"id":3379},"von-profis-lernen-heißt-siegen-lernen",[75,3381,3382,3387,3392,3397],{},[54,3383,3384],{},[316,3385,3386],{},"Der nächste Sprint steht an… Wir treffen uns am Dienstag Morgen um 10:00 Uhr zum Refinement. Es gilt noch einige\nThemen fachlich genauer zu hinterfragen. Auch technische Grundlagen müssen noch geklärt werden.",[54,3388,3389],{},[316,3390,3391],{},"Abends geht’s zum Fußballtraining. Der Trainer hält erst einmal einen 15 minütigen Vortrag über das letzte Spiel am\nSonntag. Dann fängt er auch noch an über das nächste Spiel zu reden wie wichtig das sei… Ich will doch nur kicken.\nVerteil endlich die Leibchen!",[54,3393,3394],{},[316,3395,3396],{},"Mittwoch morgen unter der Dusche gehe ich nochmal die technischen Themen des Refinements durch. Mir fällt auf, dass\nunser Ansatz wohl nur bedingt funktioniert und habe eine alternative Idee. Glück gehabt… Man wär das ärgerlich gewesen\ndas erst zu bemerken, nachdem ein Tag Entwicklung investiert wurde. Im Büro angekommen setze ich mir erst einmal eine\nTimebox von einer Stunde und evaluiere eine Bibliothek.",[54,3398,3399],{},[316,3400,3401],{},"Donnerstag dann wieder Fußballtraining. Der Trainer kommt später. Steckt noch im Stau bekomme ich in der Kabine\ngesagt. Wir krallen uns die Bälle, legen sie auf die Strafraumlinie und ballern das Runde ins Eckige. Als der Trainer\ndann kommt machen wir zum Aufwärmen wieder dieses Passspiel-Laufweg-Dingens. Als ob das was bringen würde.",[54,3403,3404],{},"“Von Sportlern lernen heißt siegen lernen”. Dieser Spruch ist mehr oder weniger jedem geläufig. Die einleitende\nAnekdote löst bei so mancher Person vielleicht noch weitere Erinnerungen aus. Aus diesem Winkel betrachtet, erfordert\nder eben erwähnte Spruch vermutlich eine Überholung. Viel treffender wäre an dieser Stelle: “Vom Berufsleben lernen\nheißt siegen lernen”.",[54,3406,3407,3408,3411,3412,3414,3415,3417],{},"Das ",[316,3409,3410],{},"ich"," ist froh nicht gleich in die Tastatur gehauen, sondern vorerst geplant zu haben. Das ",[316,3413,3410],{}," setzt sich\ndiszipliniert eine Timebox um neue Technologien zu evaluieren. Das selbe ",[316,3416,3410],{}," jedoch, will “einfach nur kicken”,\nanstatt das eigene Spiel und das des Teams mit spezifischen Trainingseinheiten zu verbessern.",[54,3419,3420],{},"Vergleicht man die Sportart Fussball mit dem Beruf der Softwareentwicklung, zeigen sich interessante Analogien auf. Im\nfolgenden werden einige dieser Analogien beleuchtet und regen hoffentlich zum Denken an. Ähnliches schon erlebt? Gibt es\nweitere Analogien? Stimmt gar nicht!",[54,3422,3423],{},"Falls nicht bekannt sein sollte, was wir im Berufsalltag so tun, helfen die Analogien vielleicht einen Eindruck zu\nbekommen.",[49,3425,3427],{"id":3426},"wenns-nicht-läuft-ist-immer-der-trainer-zuerst-am-pranger","Wenns nicht läuft ist immer der Trainer zuerst am Pranger",[54,3429,3430],{},"Der “Trainer” als Alibi Verantwortlicher. Es ist immer einfacher eine einzelne Person los zu werden, als das komplette\nTeam im Hintergrund.",[54,3432,3433],{},"In Firmen und Unternehmen geht es oftmals ähnlich von statten: Führungskräfte und Projektleiter werden ausgetauscht,\nwenn das Projektziel gefährdet ist. Unter neuer Führung geht es in die entgegengesetzte Richtung weiter, in der Hoffnung\nnun alles besser zu machen. Die bereits vorhandenen Probleme lassen sich auf diese Weise aber nicht zwangsläufig\nbeseitigen. Probleme können auch im Team, als auch im Umfeld verankert sein.",[49,3435,3437],{"id":3436},"ballbesitz-9010-und-keine-tore","Ballbesitz 90:10 und keine Tore",[54,3439,3440],{},"Kurzpassspiel, optimale Raumausnutzung, keine Fehlpässe. Für Taktik Liebhaber und Trainer sicherlich ein Leckerbissen.\nDoch nach 90 Minuten gewinnt die Mannschaft mit den meist erzielten Toren. Bei aller Leidenschaft und Liebe gewinnt am\nEnde das Ergebnis.",[54,3442,3443],{},"Auf ähnlich Art und Weise vertieft man sich auch gerne mal in ein Code Refactoring. Es soll ja keiner kommen und sagen\ndass hier könnte gegebenenfalls unter gewissen Umständen manchmal zu einem Bug oder einem Missverständnis führen. Es\nwerden so lange Codefragmente in andere Komponenten verschoben bis der eigene Zufriedensheitgrad erreicht wird. Am Ende\ndes Tages zählt jedoch der Mehrwert seitens des Anwenders.",[49,3445,3447],{"id":3446},"guardiola-vs-ancelotti-taktische-vorgaben-vs-laissez-faire","Guardiola vs Ancelotti (taktische Vorgaben vs Laissez-faire)",[54,3449,3450],{},"Guardiola und Ancelotti zählen aktuell beide zu den erfolgreichsten Trainern der Welt. Ihre Herangehensweisen und\nFührungsstile könnten jedoch kaum unterschiedlicher sein. Während Guardiola für millimetergenaue Laufwege und exakte\ntaktische Vorgaben bekannt ist, pflegt und befürwortet Ancelotti eher den Laissez-faire Stil.",[54,3452,3453],{},"In der Softwareentwicklung erinnert das z. B. an Besprechungen und deren Ablauf. Wir haben für das Refinement 60 Minuten\nangesetzt. Also sollten auch alle Themen innerhalb dieser Zeit besprochen sein. Zur Not fallen niedrig priorisierte\nThemen runter. Man hat schließlich noch andere Themen denen man sich zuwenden muss!",[54,3455,3456],{},"Oder vielleicht doch mal vom Fahrplan abweichen und das Refinement spontan verlängern, da gegen Ende eine interessante\nDiskussion entstanden ist?",[49,3458,3460],{"id":3459},"beidfüßig-und-alleskönner-oder-doch-einen-arjen-robben","Beidfüßig und Alleskönner oder doch einen Arjen Robben?",[54,3462,3463],{},"Arjen Robben ist im Fußball recht einzigartig. Er spielt offensiv auf der rechten Außenbahn, macht immer die gleiche\nFinte, ist linksfuß und hat den rechten Fuß nur, damit er beim laufen nicht umfällt. Auf anderen Positionen ist er lange\nnicht so effektiv. Dennoch ist er so wichtig für das Bayern Spiel, weil er Effizienz und das entscheidende Momentum mit\nbringt.",[54,3465,3466],{},"In der Softwareentwicklung kann man Robben das T-Modell gegenüber stellen. Dieses beschreibt spezialisiertes\nFachwissen (vertikaler Strich) und allgemeines Grundlagenwissen (horizontaler Strich). Es gibt in einem Team z. B.\njemanden der vorwiegend Interesse am Betrieb hat und jemanden der seinen Schwerpunkt in der Frontend Entwicklung hat.\nBeide können bei Abwesenheit des anderen aber auch dessen Arbeit erledigen. So hat man im Normalfall immer den Experten\ngriffbereit und der Experte darf auch mal Urlaub machen, ohne dass alles in seinem Bereich stehen bleibt.",[49,3468,3470],{"id":3469},"freistoß-spray-torlinientechnik-videobeweis","Freistoß-Spray, Torlinientechnik, Videobeweis, …",[54,3472,3473],{},"Das anfangs belächelte Freistoß-Spray verhindert das nachträgliche Verschieben des Balles. Die anfangs umstrittene\nTorlinientechnik verhindert Millionenverluste wegen menschlichen Fehlentscheidungen. Der immer noch umstrittene\nVideobeweis wird spielentscheidende Fehlentscheidungen zumindest dezimieren.",[54,3475,3476,3477,675],{},"In der Softwareentwicklung müssen wir genauso am Ball bleiben (sind wir nich alle ein bisschen Fußball? 🙂 ). Themen wie\nz. B. Betrieb mit Containern dürfen natürlich kritisch hinterfragt werden. Virtuelle Maschinen tuns ja auch, richtig?\nOder komplizierte Frontend Build Tools. Zum erstellen eines Bundles reicht doch\n",[372,3478,3479],{},"cat awesomeStuff-001.js >> app.bundle.js",[54,3481,3482],{},"Insbesondere in unserer Branche gilt es lernbeständig zu sein, stetig neue Methoden und Technologien zu evaluieren und\nauch mutig zu sein diese schrittweise zu etablieren.",[49,3484,3486],{"id":3485},"barcelona-ist-kein-barcelona-wenn-messi-nicht-spielt","Barcelona ist kein Barcelona wenn Messi nicht spielt",[54,3488,3489],{},"Messi ist unbestritten einer der besten Fußballprofis des Planeten. Seine Aktionen und seine Anwesenheit bringt dem\nSpiel seines Teams den entscheidenden Vorteil. In Zahlen ausgedrückt hat die Argentinische Nationalmannschaft in der\nQualifikation zu Weltmeisterschaft 2018 mit Messi 6 Spiele bestritten und 5 gewonnen, ohne Messi 7 Spiele bestritten und\nnur 1 gewonnen. Das entspricht einer Siegquote von 83% (mit Messi) zu 14% (ohne Messi).",[54,3491,3492],{},"Dieses Thema lässt sich auf Teamarbeit im allgemeinen abbilden. Bezieht man sich auf den Ausfall eines Teammitglieds\nspricht man gerne vom sogenannten Truck Faktor. Wie viele Leute können vom Truck ausgeknockt werden, ohne den\nProjekterfolg zu gefährden. Finde eigentlich nur ich das etwas makaber?!? Naja, auf jeden fall darf der Erfolg nicht von\neiner Person abhängen. Bei uns gilt z. B. die Regel: kein Projekt wird mehr alleine bestritten. So steht einem drei\nWochenurlaub im Normalfall nichts entgegen, da die Last auf die Kollegen verteilt werden kann.",[49,3494,3496],{"id":3495},"wieso-wechselt-der-denn-jetzt-den-besten-spieler-aus","“Wieso wechselt der denn jetzt den besten Spieler aus?!”",[54,3498,3499],{},"Steht man im Stadion kann man nicht immer alle Trainerentscheidungen nachvollziehen. “Wieso spielt der mit dreier\nKette?!”, “Wieso fängt der ausgerechnet mit dieser Startelf an?!”, “Wieso wechselt der den denn jetzt aus?!”. Nach dem\nSpiel stellt sich dann vielleicht heraus, dass jener bester Spieler eine leichte Verletzung hatte.",[54,3501,3502],{},[436,3503,3504],{},"Perspektivenwechsel / Empathie",[54,3506,3507],{},"Oft ist es hilfreich mal kurz durchzuatmen, einen Schritt zurück zu gehen und zu beobachten. Bei der Anforderungsanalyse\nz. B. hat es sich bewährt sogenannte Personas zu erstellen. Eine Persona ist die Beschreibung eines fiktiven Charakters\ndem die Software das Leben vereinfachen soll. Anhand der Persona kann das Team sich z. B. auf dessen Bedürfnisse\nfokussieren, statt mit noch mehr Vermutungen auch andere Möglichkeiten abzudecken.",[75,3509,3510],{},[54,3511,3512],{},"“Von Sportlern lernen heißt siegen lernen”wird zu“Von Profis lernen heißt siegen lernen”",[54,3514,3515],{},"Profis im Sport haben alle etwas gemeinsames. Das gewisse Etwas weshalb sie erfolgreich wurden und weiterhin bleiben.\nSei es im Teamsport oder auch im Einzelsport (Tennis, Leichtathletik, …). Wir können viel von Ihnen lernen, bestimmt.\nAber auch Sportler können von uns lernen. Professionellen Softwareentwicklern, Agile Coaches und generell Teams im\nBerufsleben. In beide Richtungen lassen sich Analogien bilden. Probiert es aus. Experimentiert.",[54,3517,3518],{},"Und bleibt am Ball 😉",{"title":11,"searchDepth":12,"depth":12,"links":3520},[3521,3522,3523,3524,3525,3526,3527],{"id":3426,"depth":12,"text":3427},{"id":3436,"depth":12,"text":3437},{"id":3446,"depth":12,"text":3447},{"id":3459,"depth":12,"text":3460},{"id":3469,"depth":12,"text":3470},{"id":3485,"depth":12,"text":3486},{"id":3495,"depth":12,"text":3496},[227],"2017-05-04T12:33:31","https://synyx.de/blog/von-profis-lernen-heisst-siegen-lernen/",{},"/blog/von-profis-lernen-heisst-siegen-lernen",{"title":3372,"description":11},"von-profis-lernen-heisst-siegen-lernen","blog/von-profis-lernen-heisst-siegen-lernen",[3537,1008,3538],"analogien","profisport","Der nächste Sprint steht an… Wir treffen uns am Dienstag Morgen um 10:00 Uhr zum Refinement. Es gilt noch einige Themen fachlich genauer zu hinterfragen. Auch technische Grundlagen müssen noch…","vic-ozc1OThUJSebUDBMCTXgtICWalJTj6v7retM5z0",{"id":3542,"title":3543,"author":3544,"body":3545,"category":5016,"date":5017,"description":5018,"extension":18,"link":5019,"meta":5020,"navigation":29,"path":5021,"seo":5022,"slug":3549,"stem":5024,"tags":5025,"teaser":5028,"__hash__":5029},"blog/blog/javascript-code-refactoring-automatisieren.md","JavaScript Code Refactoring automatisieren",[33],{"type":8,"value":3546,"toc":5007},[3547,3550,3558,3578,3584,3588,3597,3604,3607,3623,3627,3630,3635,3643,3648,3651,3656,3659,3662,3670,3673,3677,3680,3697,3700,3704,3707,3716,3810,3813,3840,3843,3897,3900,3903,3934,3937,3940,4037,4078,4081,4095,4098,4123,4129,4175,4187,4288,4308,4320,4327,4379,4382,4396,4399,4413,4416,4419,4422,4442,4452,4586,4594,4600,4727,4730,4752,4758,4859,4862,4865,4877,4880,4894,4897,4901,4910,4913,4930,4974,4978,4981,4996,5001,5004],[45,3548,3543],{"id":3549},"javascript-code-refactoring-automatisieren",[54,3551,3552,3553,3557],{},"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 ",[61,3554,3555],{"href":3555,"rel":3556},"http://jasmine.github.io/2.0/upgrading.html",[65]," wurde super\nbeschrieben was für Änderungen man genau machen muss beim Umstieg von Jasmine 1.x auf 2.x.",[54,3559,3560,3561,264,3564,3567,3568,3571,3572,3577],{},"In diesem Artikel möchte ich zeigen, wie ich die Transformation der ",[372,3562,3563],{},"runs",[372,3565,3566],{},"waitsFor"," Blöcke zum neuen ",[372,3569,3570],{},"done","\nCallback Muster mittels ",[61,3573,3576],{"href":3574,"rel":3575},"https://github.com/facebook/jscodeshift",[65],"jscodeshift"," automatisiert habe.",[54,3579,3580],{},[87,3581],{"alt":3582,"src":3583},"jasmine_async_vergleich","https://media.synyx.de/uploads//2016/08/jasmine_async_vergleich-1024x469.png",[49,3585,3587],{"id":3586},"jscodeshift-recast-esprima-codemods","jscodeshift / recast / esprima / codemods",[54,3589,3590,3591,3596],{},"Jscodeshift ist ein Werkzeug dass von Facebook gebaut wurde und ",[61,3592,3595],{"href":3593,"rel":3594},"https://github.com/benjamn/recast",[65],"recast"," erweitert.\nDieses wiederum arbeitet mit dem Esprima Parser. Dieser baut einen abstrakten Syntaxbaum (engl. abstract source tree,\nAST) auf, der traversiert werden kann.",[54,3598,3599,3600,3603],{},"Mit jscodeshift ist es z. B. möglich, alle anonyme Funktionen herauszuholen und mit einem Namen ",[316,3601,3602],{},"ichBinNichtMehrAnonym","\nzu ersetzen. Ein weiteres, nettes Feature wie ich finde, ist die Beibehaltung der originalen Code Formatierung (so weit\nmöglich).",[54,3605,3606],{},"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 🙂",[309,3608,3609,3616],{},[312,3610,3611],{},[61,3612,3615],{"href":3613,"rel":3614},"https://vramana.github.io/blog/2015/12/21/codemod-tutorial/",[65],"How to write a codemod",[312,3617,3618],{},[61,3619,3622],{"href":3620,"rel":3621},"https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb",[65],"Effective JavaScript Codemods",[49,3624,3626],{"id":3625},"automatisieren-von-code-refactorings","Automatisieren von Code Refactorings",[54,3628,3629],{},"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.",[54,3631,3632],{},[436,3633,3634],{},"Spaß",[54,3636,3637,3638,3642],{},"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 ",[3639,3640,3641],"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!",[54,3644,3645],{},[436,3646,3647],{},"Zuverlässigkeit",[54,3649,3650],{},"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.",[54,3652,3653],{},[436,3654,3655],{},"Effektivität",[54,3657,3658],{},"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.",[54,3660,3661],{},"Eine Regex zum",[309,3663,3664,3667],{},[312,3665,3666],{},"löschen von Abschnitten wenn Bedingung A zutrifft",[312,3668,3669],{},"verschieben von Code Blöcken",[54,3671,3672],{},"kann entweder einmal geschrieben und nie wieder verstanden werden, oder ist gar unmöglich zu schreiben. Hier kommt\njscodeshift mit codemods zur Rettung.",[49,3674,3676],{"id":3675},"codemods-zum-upgrade-von-jasmine-1x-auf-2x","Codemods zum Upgrade von Jasmine 1.x auf 2.x",[54,3678,3679],{},"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:",[309,3681,3682,3685,3688,3691,3694],{},[312,3683,3684],{},"spies",[312,3686,3687],{},"asynchrone Tests",[312,3689,3690],{},"expectations",[312,3692,3693],{},"custom matchers (falls vorhanden)",[312,3695,3696],{},"clock",[54,3698,3699],{},"Wir wenden uns folgend den asynchronen Tests zu.",[939,3701,3703],{"id":3702},"asynchrone-tests-transformieren","Asynchrone Tests transformieren",[54,3705,3706],{},"Das Projekt das es zu refactored galt hatte überwiegend sehr einfach geschriebene asynchrone Tests. Perfekt für den\nEinstieg in jscodeshift.",[54,3708,3709,3710,3712,3713,3715],{},"Eine Variable die initial ",[372,3711,2762],{}," ist und nach $Aktion auf ",[372,3714,2140],{}," gesetzt wird. Die Assertions werden dann erst\nausgeführt, wenn die Variable gesetzt wurde.",[459,3717,3721],{"className":3718,"code":3719,"language":3720,"meta":11,"style":11},"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",[372,3722,3723,3741,3749,3763,3768,3780,3785,3796,3802,3806],{"__ignoreMap":11},[467,3724,3725,3728,3730,3733,3735,3738],{"class":469,"line":470},[467,3726,3727],{"class":480},"it",[467,3729,681],{"class":473},[467,3731,3732],{"class":487},"\"tests something async\"",[467,3734,687],{"class":473},[467,3736,3737],{"class":645},"function",[467,3739,3740],{"class":473}," () {\n",[467,3742,3743,3746],{"class":469,"line":12},[467,3744,3745],{"class":645}," var",[467,3747,3748],{"class":473}," done;\n",[467,3750,3751,3754,3756,3758,3761],{"class":469,"line":529},[467,3752,3753],{"class":480}," doSomethingAsync",[467,3755,681],{"class":473},[467,3757,3737],{"class":645},[467,3759,3760],{"class":480}," callback",[467,3762,666],{"class":473},[467,3764,3765],{"class":469,"line":551},[467,3766,3767],{"class":1208}," // assertions\n",[467,3769,3770,3773,3775,3778],{"class":469,"line":583},[467,3771,3772],{"class":473}," done ",[467,3774,484],{"class":645},[467,3776,3777],{"class":671}," true",[467,3779,2122],{"class":473},[467,3781,3782],{"class":469,"line":589},[467,3783,3784],{"class":473}," });\n",[467,3786,3787,3790,3792,3794],{"class":469,"line":599},[467,3788,3789],{"class":480}," waitsFor",[467,3791,681],{"class":473},[467,3793,3737],{"class":645},[467,3795,3740],{"class":473},[467,3797,3798,3800],{"class":469,"line":609},[467,3799,2091],{"class":645},[467,3801,3748],{"class":473},[467,3803,3804],{"class":469,"line":615},[467,3805,3784],{"class":473},[467,3807,3808],{"class":469,"line":773},[467,3809,809],{"class":473},[54,3811,3812],{},"Für jasmine 2.x müssen wir also",[309,3814,3815,3824,3834],{},[312,3816,3817,3818,3820,3821,3823],{},"einmal der Funktion die dem ",[372,3819,3727],{}," übergeben wird einen Parameter ",[372,3822,3570],{}," hinzufügen",[312,3825,3826,3829,3830,3833],{},[372,3827,3828],{},"done = true;"," mit ",[372,3831,3832],{},"done();"," ersetzen",[312,3835,3836,3837,3839],{},"und den ",[372,3838,3566],{}," Block löschen",[54,3841,3842],{},"Nach dem Refactoring soll das Ganze also wie folgt aussehen:",[459,3844,3846],{"className":3718,"code":3845,"language":3720,"meta":11,"style":11},"it(\"tests something async\", function (done) {\n doSomethingAsync(function callback() {\n // assertions\n done();\n });\n});\n",[372,3847,3848,3866,3878,3882,3889,3893],{"__ignoreMap":11},[467,3849,3850,3852,3854,3856,3858,3860,3862,3864],{"class":469,"line":470},[467,3851,3727],{"class":480},[467,3853,681],{"class":473},[467,3855,3732],{"class":487},[467,3857,687],{"class":473},[467,3859,3737],{"class":645},[467,3861,693],{"class":473},[467,3863,3570],{"class":696},[467,3865,2415],{"class":473},[467,3867,3868,3870,3872,3874,3876],{"class":469,"line":12},[467,3869,3753],{"class":480},[467,3871,681],{"class":473},[467,3873,3737],{"class":645},[467,3875,3760],{"class":480},[467,3877,666],{"class":473},[467,3879,3880],{"class":469,"line":529},[467,3881,3767],{"class":1208},[467,3883,3884,3887],{"class":469,"line":551},[467,3885,3886],{"class":480}," done",[467,3888,716],{"class":473},[467,3890,3891],{"class":469,"line":583},[467,3892,3784],{"class":473},[467,3894,3895],{"class":469,"line":589},[467,3896,809],{"class":473},[3898,3899,3576],"h4",{"id":3576},[54,3901,3902],{},"Bevor wir loslegen können, müssen noch wenige Dinge erledigt werden.",[459,3904,3908],{"className":3905,"code":3906,"language":3907,"meta":11,"style":11},"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",[372,3909,3910,3914,3919,3924,3929],{"__ignoreMap":11},[467,3911,3912],{"class":469,"line":470},[467,3913,612],{"emptyLinePlaceholder":29},[467,3915,3916],{"class":469,"line":12},[467,3917,3918],{},"$> npm install -g jscodeshift\n",[467,3920,3921],{"class":469,"line":529},[467,3922,3923],{},"$> mkdir jasmineCodemods && cd jasmineCodemods\n",[467,3925,3926],{"class":469,"line":551},[467,3927,3928],{},"$> git init && git commit -m \"initial commit\" --allow-empty\n",[467,3930,3931],{"class":469,"line":583},[467,3932,3933],{},"$> touch jasmine-async.js\n",[54,3935,3936],{},"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!",[54,3938,3939],{},"Dann legen wir uns eine Datei für der/die/das erste codemod an und schreiben folgenden Inhalt:",[459,3941,3943],{"className":3718,"code":3942,"language":3720,"meta":11,"style":11},"// 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",[372,3944,3945,3950,3980,3993,4011,4025,4032],{"__ignoreMap":11},[467,3946,3947],{"class":469,"line":470},[467,3948,3949],{"class":1208},"// jasmine-async.js\n",[467,3951,3952,3955,3957,3960,3962,3965,3968,3970,3973,3975,3978],{"class":469,"line":12},[467,3953,3954],{"class":671},"module",[467,3956,675],{"class":473},[467,3958,3959],{"class":671},"exports",[467,3961,1573],{"class":645},[467,3963,3964],{"class":645}," function",[467,3966,3967],{"class":480}," transformer",[467,3969,681],{"class":473},[467,3971,3972],{"class":696},"file",[467,3974,687],{"class":473},[467,3976,3977],{"class":696},"api",[467,3979,2415],{"class":473},[467,3981,3982,3985,3988,3990],{"class":469,"line":529},[467,3983,3984],{"class":645}," const",[467,3986,3987],{"class":671}," j",[467,3989,1573],{"class":645},[467,3991,3992],{"class":473}," api.jscodeshift;\n",[467,3994,3995,3997,4000,4003,4006,4008],{"class":469,"line":551},[467,3996,3984],{"class":645},[467,3998,3999],{"class":473}," { ",[467,4001,4002],{"class":671},"statement",[467,4004,4005],{"class":473}," } ",[467,4007,484],{"class":645},[467,4009,4010],{"class":473}," j.template;\n",[467,4012,4013,4015,4018,4020,4022],{"class":469,"line":583},[467,4014,3984],{"class":645},[467,4016,4017],{"class":671}," root",[467,4019,1573],{"class":645},[467,4021,3987],{"class":480},[467,4023,4024],{"class":473},"(file.source);\n",[467,4026,4027,4029],{"class":469,"line":589},[467,4028,2910],{"class":645},[467,4030,4031],{"class":473}," root;\n",[467,4033,4034],{"class":469,"line":599},[467,4035,4036],{"class":473},"};\n",[54,4038,4039,4040,4043,4044,687,4047,687,4050,687,4053,4056,4057,4060,4061,91,4066,4071,4072,4077],{},"Auf ",[372,4041,4042],{},"root"," können wir jetzt jscodeshift Methoden aufrufen wie ",[372,4045,4046],{},"find",[372,4048,4049],{},"filter",[372,4051,4052],{},"forEach",[372,4054,4055],{},"replaceWith"," und zuletzt\n",[372,4058,4059],{},"toSource",". Die Methoden machen genau das was der Name sagt, selbsterklärend. Genaueres muss man sich leider selbst\nim ",[61,4062,4065],{"href":4063,"rel":4064},"https://github.com/facebook/jscodeshift/blob/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/Collection.js",[65],"Source",[61,4067,4070],{"href":4068,"rel":4069},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b/src/collections",[65],"Code","\nauf ",[61,4073,4076],{"href":4074,"rel":4075},"https://github.com/facebook/jscodeshift/tree/fe67b121d4c2519c5227a00be3f590e7f7c46d2b",[65],"Github"," zusammenkratzen.",[54,4079,4080],{},"Ausführen können wir das Skript später mit",[459,4082,4084],{"className":3905,"code":4083,"language":3907,"meta":11,"style":11},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n\n",[372,4085,4086,4090],{"__ignoreMap":11},[467,4087,4088],{"class":469,"line":470},[467,4089,612],{"emptyLinePlaceholder":29},[467,4091,4092],{"class":469,"line":12},[467,4093,4094],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei\n",[54,4096,4097],{},"Doch zuerst müssen Transformationen gecoded werden 😮",[54,4099,4100,4101,4103,4104,4106,4107,4110,4111,4113,4114,4116,4117,4122],{},"Wir wollen fürs erste alle ",[372,4102,3727],{}," Knoten finden und der übergebenen Funktion einen ",[372,4105,3570],{}," Parameter spendieren. Zum Suchen\nvon Ausdrücken verwenden wir die jscodeshift Methode ",[372,4108,4109],{},"root.find",". Diese traversiert den AST und gibt uns eine Collection\nvon passenden Knoten zurück. Als Argument müssen wird dem ",[372,4112,4046],{}," Aufruf eine AST Beschreibung des ",[372,4115,3727],{}," Knotens mitgeben.\nBeim Finden der Beschreibung hilft uns der geniale ",[61,4118,4121],{"href":4119,"rel":4120},"https://astexplorer.net",[65],"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!",[54,4124,4125],{},[87,4126],{"alt":4127,"src":4128},"astexplorer","https://media.synyx.de/uploads//2016/08/astexplorer-1024x631.png",[459,4130,4132],{"className":3718,"code":4131,"language":3720,"meta":11,"style":11},"// jasmine-async.js\nreturn root.find(j.CallExpression, {\n callee: {\n name: \"it\",\n },\n});\n",[372,4133,4134,4138,4151,4156,4166,4171],{"__ignoreMap":11},[467,4135,4136],{"class":469,"line":470},[467,4137,3949],{"class":1208},[467,4139,4140,4143,4146,4148],{"class":469,"line":12},[467,4141,4142],{"class":645},"return",[467,4144,4145],{"class":473}," root.",[467,4147,4046],{"class":480},[467,4149,4150],{"class":473},"(j.CallExpression, {\n",[467,4152,4153],{"class":469,"line":529},[467,4154,4155],{"class":473}," callee: {\n",[467,4157,4158,4161,4164],{"class":469,"line":551},[467,4159,4160],{"class":473}," name: ",[467,4162,4163],{"class":487},"\"it\"",[467,4165,803],{"class":473},[467,4167,4168],{"class":469,"line":583},[467,4169,4170],{"class":473}," },\n",[467,4172,4173],{"class":469,"line":589},[467,4174,809],{"class":473},[54,4176,4177,4178,4180,4181,4183,4184,4186],{},"Dann wollen wir für alle Knoten die gefunden werden etwas tun. Nämlich den ",[372,4179,3570],{}," Parameter hinzufügen zur eigentlichen\nTestfunktion. Mit ",[372,4182,4052],{}," können wir über die von ",[372,4185,4046],{}," zurückgegebene Collection iterieren und dies tun.",[459,4188,4190],{"className":3718,"code":4189,"language":3720,"meta":11,"style":11},"\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",[372,4191,4192,4196,4200,4207,4220,4235,4240,4259,4264,4283],{"__ignoreMap":11},[467,4193,4194],{"class":469,"line":470},[467,4195,612],{"emptyLinePlaceholder":29},[467,4197,4198],{"class":469,"line":12},[467,4199,3949],{"class":1208},[467,4201,4202,4204],{"class":469,"line":529},[467,4203,4142],{"class":645},[467,4205,4206],{"class":473}," root\n",[467,4208,4209,4212,4214,4216,4218],{"class":469,"line":551},[467,4210,4211],{"class":473}," .",[467,4213,4046],{"class":480},[467,4215,681],{"class":473},[467,4217,3216],{"class":645},[467,4219,1294],{"class":473},[467,4221,4222,4224,4226,4228,4230,4233],{"class":469,"line":583},[467,4223,4211],{"class":473},[467,4225,4052],{"class":480},[467,4227,681],{"class":473},[467,4229,54],{"class":696},[467,4231,4232],{"class":645}," =>",[467,4234,658],{"class":473},[467,4236,4237],{"class":469,"line":589},[467,4238,4239],{"class":1208}," // p.node.arguments[0] would be the spec description\n",[467,4241,4242,4245,4248,4250,4253,4256],{"class":469,"line":599},[467,4243,4244],{"class":645}," const",[467,4246,4247],{"class":671}," specCallee",[467,4249,1573],{"class":645},[467,4251,4252],{"class":473}," p.node.arguments[",[467,4254,4255],{"class":671},"1",[467,4257,4258],{"class":473},"];\n",[467,4260,4261],{"class":469,"line":609},[467,4262,4263],{"class":1208}," // add 'done' parameter\n",[467,4265,4266,4269,4272,4274,4277,4280],{"class":469,"line":615},[467,4267,4268],{"class":473}," specCallee.params.",[467,4270,4271],{"class":480},"push",[467,4273,681],{"class":473},[467,4275,4276],{"class":480},"statment",[467,4278,4279],{"class":487},"`done`",[467,4281,4282],{"class":473},");\n",[467,4284,4285],{"class":469,"line":773},[467,4286,4287],{"class":473}," })\n",[54,4289,4290,4291,4293,4294,4297,4298,4301,4302,4304,4305,4307],{},"Die Variable ",[372,4292,54],{}," ist der Pfad des gefundenen Knotens. Man könnte die Variable auch ",[372,4295,4296],{},"path"," benennen, würde sich aber\nbeißen mit dem node Modul ",[372,4299,4300],{},"const path = require('path');",". Das importieren dieses Moduls ist keine Seltenheit in\ncodemods denke ich. Und als Konvention nehmen wir einfach ",[372,4303,54],{}," statt ",[372,4306,4296],{},", immer!",[54,4309,4310,4311,4313,4314,4316,4317,4319],{},"Der ASTExplorer zeigt wie wir an die Funktion kommen der wir den ",[372,4312,3570],{}," Parameter hinzufügen möchten. Wir holen uns das\nzweite Element der CallExpression Argumente und fügen dessen Parameter Liste einfach das ",[372,4315,3570],{}," 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 ",[372,4318,4002],{}," 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?",[54,4321,4322,4323,4326],{},"Zum Abschluss müssen wir die Änderungen mit ",[372,4324,4325],{},"toSource()"," an jscodeshift zurückgeben um die Datei neu zu schreiben.",[459,4328,4330],{"className":3718,"code":4329,"language":3720,"meta":11,"style":11},"\n// jasmine-async.js\nreturn root\n .find(...)\n .forEach(...)\n .toSource()\n\n",[372,4331,4332,4336,4340,4346,4358,4370],{"__ignoreMap":11},[467,4333,4334],{"class":469,"line":470},[467,4335,612],{"emptyLinePlaceholder":29},[467,4337,4338],{"class":469,"line":12},[467,4339,3949],{"class":1208},[467,4341,4342,4344],{"class":469,"line":529},[467,4343,4142],{"class":645},[467,4345,4206],{"class":473},[467,4347,4348,4350,4352,4354,4356],{"class":469,"line":551},[467,4349,4211],{"class":473},[467,4351,4046],{"class":480},[467,4353,681],{"class":473},[467,4355,3216],{"class":645},[467,4357,1294],{"class":473},[467,4359,4360,4362,4364,4366,4368],{"class":469,"line":583},[467,4361,4211],{"class":473},[467,4363,4052],{"class":480},[467,4365,681],{"class":473},[467,4367,3216],{"class":645},[467,4369,1294],{"class":473},[467,4371,4372,4374,4376],{"class":469,"line":589},[467,4373,4211],{"class":473},[467,4375,4059],{"class":480},[467,4377,4378],{"class":473},"()\n",[54,4380,4381],{},"Zum schnellen Testen kann die Transformation auf der Konsole mit",[459,4383,4385],{"className":3905,"code":4384,"language":3907,"meta":11,"style":11},"\n$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n\n",[372,4386,4387,4391],{"__ignoreMap":11},[467,4388,4389],{"class":469,"line":470},[467,4390,612],{"emptyLinePlaceholder":29},[467,4392,4393],{"class":469,"line":12},[467,4394,4395],{},"$> jscodeshift -t ./jasmine-async.js pfad/zur/source/datei.js\n",[54,4397,4398],{},"ausgeführt werden. Nach dem ersten Staunen aber mit",[459,4400,4402],{"className":3905,"code":4401,"language":3907,"meta":11,"style":11},"\n$> git checkout HEAD -- pfad/zur/source/datei.js\n\n",[372,4403,4404,4408],{"__ignoreMap":11},[467,4405,4406],{"class":469,"line":470},[467,4407,612],{"emptyLinePlaceholder":29},[467,4409,4410],{"class":469,"line":12},[467,4411,4412],{},"$> git checkout HEAD -- pfad/zur/source/datei.js\n",[54,4414,4415],{},"zurück gesetzt werden.",[54,4417,4418],{},"Git o/",[54,4420,4421],{},"Der erste Punkt ist erledigt.",[309,4423,4424,4432,4438],{},[312,4425,4426],{},[3639,4427,3817,4428,3820,4430,3823],{},[372,4429,3727],{},[372,4431,3570],{},[312,4433,4434,3829,4436,3833],{},[372,4435,3828],{},[372,4437,3832],{},[312,4439,3836,4440,3839],{},[372,4441,3566],{},[54,4443,4444,4445,4447,4448,4451],{},"Ersetzen wir als nächstes ",[372,4446,3828],{}," mit dem ",[372,4449,4450],{},"done()"," Aufruf. Dazu klicken wir im ASTExplorer auf den entsprechenden\nAusdruck und schauen rechts im AST nach der Pfad Beschreibung die wir brauchen.",[459,4453,4455],{"className":3718,"code":4454,"language":3720,"meta":11,"style":11},"\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",[372,4456,4457,4461,4465,4471,4483,4497,4502,4507,4520,4525,4530,4535,4543,4548,4553,4574,4578],{"__ignoreMap":11},[467,4458,4459],{"class":469,"line":470},[467,4460,612],{"emptyLinePlaceholder":29},[467,4462,4463],{"class":469,"line":12},[467,4464,3949],{"class":1208},[467,4466,4467,4469],{"class":469,"line":529},[467,4468,4142],{"class":645},[467,4470,4206],{"class":473},[467,4472,4473,4475,4477,4479,4481],{"class":469,"line":551},[467,4474,4211],{"class":473},[467,4476,4046],{"class":480},[467,4478,681],{"class":473},[467,4480,3216],{"class":645},[467,4482,1294],{"class":473},[467,4484,4485,4487,4489,4491,4493,4495],{"class":469,"line":583},[467,4486,4211],{"class":473},[467,4488,4052],{"class":480},[467,4490,681],{"class":473},[467,4492,54],{"class":696},[467,4494,4232],{"class":645},[467,4496,658],{"class":473},[467,4498,4499],{"class":469,"line":589},[467,4500,4501],{"class":1208}," // ...\n",[467,4503,4504],{"class":469,"line":599},[467,4505,4506],{"class":1208}," // replace 'done = true' with done() invocation\n",[467,4508,4509,4512,4515,4517],{"class":469,"line":609},[467,4510,4511],{"class":480}," j",[467,4513,4514],{"class":473},"(p).",[467,4516,4046],{"class":480},[467,4518,4519],{"class":473},"(j.ExpressionStatement, {\n",[467,4521,4522],{"class":469,"line":615},[467,4523,4524],{"class":473}," expression: {\n",[467,4526,4527],{"class":469,"line":773},[467,4528,4529],{"class":473}," type: j.AssignmentExpression.name,\n",[467,4531,4532],{"class":469,"line":778},[467,4533,4534],{"class":473}," left: {\n",[467,4536,4537,4540],{"class":469,"line":794},[467,4538,4539],{"class":473}," name: ",[467,4541,4542],{"class":487},"'done'\n",[467,4544,4545],{"class":469,"line":806},[467,4546,4547],{"class":473}," }\n",[467,4549,4550],{"class":469,"line":905},[467,4551,4552],{"class":473}," }\n",[467,4554,4555,4558,4560,4562,4564,4566,4569,4572],{"class":469,"line":911},[467,4556,4557],{"class":473}," }).",[467,4559,4055],{"class":480},[467,4561,681],{"class":473},[467,4563,54],{"class":696},[467,4565,4232],{"class":645},[467,4567,4568],{"class":480}," statement",[467,4570,4571],{"class":487},"`done();`",[467,4573,4282],{"class":473},[467,4575,4576],{"class":469,"line":916},[467,4577,4287],{"class":473},[467,4579,4580,4582,4584],{"class":469,"line":922},[467,4581,4211],{"class":473},[467,4583,4059],{"class":480},[467,4585,4378],{"class":473},[54,4587,4588,4589,4591,4592,675],{},"Da wir wissen, dass ",[372,4590,3828],{}," nur einmal vorkommt, können wir der Collection direkt sagen bitte ersetzen mit dem\nStatement ",[372,4593,3832],{},[54,4595,4596,4597,4599],{},"Die ",[372,4598,3570],{}," 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:",[459,4601,4603],{"className":3718,"code":4602,"language":3720,"meta":11,"style":11},"\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",[372,4604,4605,4609,4613,4619,4631,4645,4649,4654,4665,4670,4675,4680,4685,4692,4697,4701,4706,4715,4719],{"__ignoreMap":11},[467,4606,4607],{"class":469,"line":470},[467,4608,612],{"emptyLinePlaceholder":29},[467,4610,4611],{"class":469,"line":12},[467,4612,3949],{"class":1208},[467,4614,4615,4617],{"class":469,"line":529},[467,4616,4142],{"class":645},[467,4618,4206],{"class":473},[467,4620,4621,4623,4625,4627,4629],{"class":469,"line":551},[467,4622,4211],{"class":473},[467,4624,4046],{"class":480},[467,4626,681],{"class":473},[467,4628,3216],{"class":645},[467,4630,1294],{"class":473},[467,4632,4633,4635,4637,4639,4641,4643],{"class":469,"line":583},[467,4634,4211],{"class":473},[467,4636,4052],{"class":480},[467,4638,681],{"class":473},[467,4640,54],{"class":696},[467,4642,4232],{"class":645},[467,4644,658],{"class":473},[467,4646,4647],{"class":469,"line":589},[467,4648,4501],{"class":1208},[467,4650,4651],{"class":469,"line":599},[467,4652,4653],{"class":1208}," // get rid of 'var done = false'\n",[467,4655,4656,4658,4660,4662],{"class":469,"line":609},[467,4657,4511],{"class":480},[467,4659,4514],{"class":473},[467,4661,4046],{"class":480},[467,4663,4664],{"class":473},"(j.VariableDeclaration, {\n",[467,4666,4667],{"class":469,"line":615},[467,4668,4669],{"class":473}," declarations: [\n",[467,4671,4672],{"class":469,"line":773},[467,4673,4674],{"class":473}," {\n",[467,4676,4677],{"class":469,"line":778},[467,4678,4679],{"class":473}," type: j.VariableDeclarator.name,\n",[467,4681,4682],{"class":469,"line":794},[467,4683,4684],{"class":473}," id: {\n",[467,4686,4687,4690],{"class":469,"line":806},[467,4688,4689],{"class":473}," name: ",[467,4691,4542],{"class":487},[467,4693,4694],{"class":469,"line":905},[467,4695,4696],{"class":473}," }\n",[467,4698,4699],{"class":469,"line":911},[467,4700,4547],{"class":473},[467,4702,4703],{"class":469,"line":916},[467,4704,4705],{"class":473}," ]\n",[467,4707,4708,4710,4713],{"class":469,"line":922},[467,4709,4557],{"class":473},[467,4711,4712],{"class":480},"remove",[467,4714,4378],{"class":473},[467,4716,4717],{"class":469,"line":2290},[467,4718,4287],{"class":473},[467,4720,4721,4723,4725],{"class":469,"line":2300},[467,4722,4211],{"class":473},[467,4724,4059],{"class":480},[467,4726,4378],{"class":473},[54,4728,4729],{},"Zweiter Punkt auch erledigt.",[309,4731,4732,4740,4748],{},[312,4733,4734],{},[3639,4735,3817,4736,3820,4738,3823],{},[372,4737,3727],{},[372,4739,3570],{},[312,4741,4742],{},[3639,4743,4744,3829,4746,3833],{},[372,4745,3828],{},[372,4747,3832],{},[312,4749,3836,4750,3839],{},[372,4751,3566],{},[54,4753,4754,4755,4757],{},"Fehlt nur noch das Entfernen des ",[372,4756,3566],{}," Blocks. Richtig geraten! Der ASTExplorer zeigt uns was wir suchen müssen.",[459,4759,4761],{"className":3718,"code":4760,"language":3720,"meta":11,"style":11},"\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",[372,4762,4763,4767,4771,4777,4789,4803,4807,4812,4822,4827,4835,4839,4847,4851],{"__ignoreMap":11},[467,4764,4765],{"class":469,"line":470},[467,4766,612],{"emptyLinePlaceholder":29},[467,4768,4769],{"class":469,"line":12},[467,4770,3949],{"class":1208},[467,4772,4773,4775],{"class":469,"line":529},[467,4774,4142],{"class":645},[467,4776,4206],{"class":473},[467,4778,4779,4781,4783,4785,4787],{"class":469,"line":551},[467,4780,4211],{"class":473},[467,4782,4046],{"class":480},[467,4784,681],{"class":473},[467,4786,3216],{"class":645},[467,4788,1294],{"class":473},[467,4790,4791,4793,4795,4797,4799,4801],{"class":469,"line":583},[467,4792,4211],{"class":473},[467,4794,4052],{"class":480},[467,4796,681],{"class":473},[467,4798,54],{"class":696},[467,4800,4232],{"class":645},[467,4802,658],{"class":473},[467,4804,4805],{"class":469,"line":589},[467,4806,4501],{"class":1208},[467,4808,4809],{"class":469,"line":599},[467,4810,4811],{"class":1208}," // get rid of obsolete waitsFor block\n",[467,4813,4814,4816,4818,4820],{"class":469,"line":609},[467,4815,4511],{"class":480},[467,4817,4514],{"class":473},[467,4819,4046],{"class":480},[467,4821,4150],{"class":473},[467,4823,4824],{"class":469,"line":615},[467,4825,4826],{"class":473}," callee: {\n",[467,4828,4829,4832],{"class":469,"line":773},[467,4830,4831],{"class":473}," name: ",[467,4833,4834],{"class":487},"'waitsFor'\n",[467,4836,4837],{"class":469,"line":778},[467,4838,4552],{"class":473},[467,4840,4841,4843,4845],{"class":469,"line":794},[467,4842,4557],{"class":473},[467,4844,4712],{"class":480},[467,4846,4378],{"class":473},[467,4848,4849],{"class":469,"line":806},[467,4850,4287],{"class":473},[467,4852,4853,4855,4857],{"class":469,"line":905},[467,4854,4211],{"class":473},[467,4856,4059],{"class":480},[467,4858,4378],{"class":473},[54,4860,4861],{},"Fertig!",[54,4863,4864],{},"Schnell noch testen obs auch wirklich tut:",[459,4866,4867],{"className":3905,"code":4384,"language":3907,"meta":11,"style":11},[372,4868,4869,4873],{"__ignoreMap":11},[467,4870,4871],{"class":469,"line":470},[467,4872,612],{"emptyLinePlaceholder":29},[467,4874,4875],{"class":469,"line":12},[467,4876,4395],{},[54,4878,4879],{},"Und ab damit ins Repo",[459,4881,4883],{"className":3905,"code":4882,"language":3907,"meta":11,"style":11},"\n$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n\n",[372,4884,4885,4889],{"__ignoreMap":11},[467,4886,4887],{"class":469,"line":470},[467,4888,612],{"emptyLinePlaceholder":29},[467,4890,4891],{"class":469,"line":12},[467,4892,4893],{},"$> git commit -am \"jasmine async test upgrade from 1.x to 2.x; automated 🎉\"\n",[54,4895,4896],{},"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.",[49,4898,4900],{"id":4899},"ausblick","Ausblick",[54,4902,4903,4904,4906,4907,4909],{},"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",[372,4905,3566],{}," 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 ",[372,4908,3566],{}," Blöcke hatten weil mehrere Klicks simuliert\nwurden.",[54,4911,4912],{},"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.",[54,4914,4915,4916,4921,4922,4925,4926,4929],{},"Werden codemods komplexer kann man über Unit Tests nachdenken. Hier gibt es Hilfe von jscodeshift. Als Beispiel\nhilft ",[61,4917,4920],{"href":4918,"rel":4919},"https://github.com/cpojer/js-codemod/blob/82af3089f22fa0687159f64177b73908b82d074f/transforms/__tests__/arrow-function-test.js",[65],"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 ‘",[436,4923,4924],{},"testfixtures","’ abgelegt. Der Test liegt im Verzeichnis\n‘",[436,4927,4928],{},"tests","’ und definiert mittels den TestUtils einfach nur den Test. Codemods testgetrieben entwickeln steht nichts\nmehr im Weg.",[459,4931,4933],{"className":3718,"code":4932,"language":3720,"meta":11,"style":11},"// jasmine-async.spec.js\nconst defineTest = require(\"jscodeshift/dist/testUtils\").defineTest;\ndefineTest(__dirname, \"jasmine-async\");\n",[372,4934,4935,4940,4961],{"__ignoreMap":11},[467,4936,4937],{"class":469,"line":470},[467,4938,4939],{"class":1208},"// jasmine-async.spec.js\n",[467,4941,4942,4945,4948,4950,4953,4955,4958],{"class":469,"line":12},[467,4943,4944],{"class":645},"const",[467,4946,4947],{"class":671}," defineTest",[467,4949,1573],{"class":645},[467,4951,4952],{"class":480}," require",[467,4954,681],{"class":473},[467,4956,4957],{"class":487},"\"jscodeshift/dist/testUtils\"",[467,4959,4960],{"class":473},").defineTest;\n",[467,4962,4963,4966,4969,4972],{"class":469,"line":529},[467,4964,4965],{"class":480},"defineTest",[467,4967,4968],{"class":473},"(__dirname, ",[467,4970,4971],{"class":487},"\"jasmine-async\"",[467,4973,4282],{"class":473},[49,4975,4977],{"id":4976},"fazit","Fazit",[54,4979,4980],{},"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.",[54,4982,4983,4984,4989,4990,4995],{},"Oft verwendet habe ich bisher die ",[61,4985,4988],{"href":4986,"rel":4987},"https://github.com/cpojer/js-codemod",[65],"js-codemods","\nvon ",[61,4991,4994],{"href":4992,"rel":4993},"https://twitter.com/cpojer",[65],"@cpojer"," zum transformieren zu neuen es2015 Sprachfeatures.",[54,4997,4998],{},[436,4999,5000],{},"Meine Empfehlung:",[54,5002,5003],{},"Einmal reinknien und machen! Vielleicht sogar für kleinere Projekte.",[986,5005,5006],{},"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":11,"searchDepth":12,"depth":12,"links":5008},[5009,5010,5011,5014,5015],{"id":3586,"depth":12,"text":3587},{"id":3625,"depth":12,"text":3626},{"id":3675,"depth":12,"text":3676,"children":5012},[5013],{"id":3702,"depth":529,"text":3703},{"id":4899,"depth":12,"text":4900},{"id":4976,"depth":12,"text":4977},[998],"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":3543,"description":5023},"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",[5026,638,5027],"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":5031,"title":5032,"author":5033,"body":5036,"category":5367,"date":5368,"description":5369,"extension":18,"link":5370,"meta":5371,"navigation":29,"path":5372,"seo":5373,"slug":5040,"stem":5375,"tags":5376,"teaser":5380,"__hash__":5381},"blog/blog/synyx-goto-amsterdam.md","synyx GOTO Amsterdam",[33,5034,5035],"hammann","klem",{"type":8,"value":5037,"toc":5362},[5038,5041,5055,5059,5062,5065,5068,5074,5077,5080,5083,5087,5090,5093,5096,5099,5102,5105,5108,5111,5117,5125,5128,5136,5139,5143,5154,5157,5172,5175,5178,5198,5210,5213,5228,5248,5254,5257,5264,5291,5306,5311,5314,5324,5344,5350,5353,5359],[45,5039,5032],{"id":5040},"synyx-goto-amsterdam",[54,5042,5043,5044,5049,5050,5054],{},"Vom 13. bis 15.06.2016 waren wir zu siebt in Amsterdam auf der ",[61,5045,5048],{"href":5046,"rel":5047},"http://gotocon.com/amsterdam-2016/",[65],"goto; Amsterdam",".\nZunächst gibt es einen kleinen Reisebericht zu lesen auf den dann ein paar Impressionen aus den einzelnen Sessions und\nTalks folgen. Wer direkt zu den inhaltlichen Schwerpunkten unserer Reise springen möchte bitte ",[61,5051,5053],{"href":5052},"#goto","hier"," entlang.",[49,5056,5058],{"id":5057},"die-ankunft","Die Ankunft",[54,5060,5061],{},"„Ich hol mir nen rohen Hering“, stellte Marc beim Ausstieg in Amsterdam klar. Dem wurde nicht widersprochen, allerdings\nwolle man zunächst das Gepäck zum Hostel bringen.",[54,5063,5064],{},"Dort angekommen, wurde der Plan beschlossen den Hotdog-Laden „Fat Dog“ aufzusuchen, der bereits in der Kochshow\n„Kitchen impossible“ lobend erwähnt wurde.",[54,5066,5067],{},"Nach einem mindestens zehn stündigen Fußmarsch kamen wir dann sogar dort an und hatten ordentlich Hunger mitgebracht.\nDer Großteil der Gruppe hatte sich extra für das bevorstehende Match: Deutschland – Ukraine passend mit einem mehr oder\nweniger aktuellen Deutschlandtrikot gekleidet. Dies wurde sofort bemerkt woraufhin wir auf das Tagesspecial aufmerksam\ngemacht wurden.",[54,5069,5070],{},[87,5071],{"alt":5072,"src":5073},"\"IMG-20160612-WA0006\"","https://media.synyx.de/uploads//2016/06/IMG-20160612-WA0006-1.jpg",[54,5075,5076],{},"Im Nachhinein betrachtet, hätten wir diesem Angebot mehr Beachtung schenken sollen. Denn die Hotdogs, die wir\nbestellten, reichten gerade dafür aus, die verbrauchte Energie für den 15 stündigen Fußmarsch wiederherzustellen. „Ich\nhätte noch Lust auf einen Hering“ stellte Marc erneut fest.",[54,5078,5079],{},"Allerdings rückte der Anpfiff näher und wir mussten noch eine geeignete Bar finden, um das Spiel zu verfolgen. Einige\nGehminuten später wurden wir auch fündig. Gut gelaunt verließen wir nach dem erfolgreichen 2:0 die Bar und machten uns\nauf den Wunsch von Honnel auf die Suche nach einem Krokettenautomaten (Ja richtig gelesen…). David übernahm die\nNavigation und führte uns zu besagtem Automaten. (…) Glücklich und zufrieden legten wir uns schlafen und sammelten\nEnergie für den nächsten Tag.",[54,5081,5082],{},"zZz …Hering…murmel murmel…zZzZ",[49,5084,5086],{"id":5085},"amsterdam-kompakt","Amsterdam kompakt",[54,5088,5089],{},"Den Montag wollten wir dazu nutzen uns ein wenig mit Amsterdam vertraut zu machen. Beim gemeinsamen Frühstück, wurde\nbesprochen, wie man den Tag am besten Nutzen könnte. Hierbei entstanden zwei Pläne:",[54,5091,5092],{},"• Mit dem Rad ans Meer",[54,5094,5095],{},"• Ab ins Rijksmuseum",[54,5097,5098],{},"Die Planung für die Radtour gestaltete sich etwas schwierig, da aus den Wetterberichten verschiedener Apps keine klare\nTendenz hervorging. Wir entschieden uns daher für die etwas kürzere Variante. Zunächst suchten wir einen Fiets verhuur (\nFahrradverleih) auf und bekamen drei Fahrräder. Zwei davon hatten sogar eine Klingelautomatik (War halt kaputt und hat\nbei jeder Bodenwelle gebimmelt…) integriert!",[54,5100,5101],{},"Ansonsten waren die Fahrräder allerdings super, also machten wir uns auf den Weg in Richtung Meer. David als Navigator\nvoraus und der dauerklingelnde Marc und ich hinterher.",[54,5103,5104],{},"Zielsicher navigierte uns David im Zick Zack durch Amsterdam.",[54,5106,5107],{},"Brücke…Links…Rechts…Brücke…Rechts… Oh hier geht’s nicht weiter… Aber da drüben!",[54,5109,5110],{},"Nach einigen Minuten hatten wir aus der Stadt herausgefunden und radelten geradewegs aufs Meer zu.",[54,5112,5113],{},[87,5114],{"alt":5115,"src":5116},"\"Radtour_Meer\"","https://media.synyx.de/uploads//2016/06/Radtour_Meer.jpg",[54,5118,5119,5120,5124],{},"Da das Wetter noch hielt und wir noch voller Energie waren, entschlossen wir noch etwas weiter zu fahren. Zum Glück!\nSonst hätten wir nie diese Ziege\nentdeckt!",[87,5121],{"alt":5122,"src":5123},"\"Radtour_Ziege\"","https://media.synyx.de/uploads//2016/06/Radtour_Ziege.jpg","\nSchließlich in Marken angekommen, legten wir eine kurze Pause ein bevor wir uns auf den Rückweg machten. Leider begann\nes nun etwas zu regnen. Marc und David zogen das Tempo an. Ich hechelte hinterher.",[54,5126,5127],{},"Die Ziege hatte mittlerweile Gebrauch von ihrer Hütte gemacht und sich vollständig zurückgezogen. Glücklicherweise hörte\nes nach einem kurzen Schauer dann auch wieder auf zu regnen und wir kamen halbwegs trocken und zufrieden wieder im\nHostel an und machten uns auf die Suche nach dem Rest unserer Gruppe.",[54,5129,5130,5131,5135],{},"In der Zwischenzeit bewunderte der Rest der synyx-Reisegruppe die kulturellen Schätze des Rijksmuseum, allen voran\nRembrandt, Van Gogh und andere große Meister. Auch die Abteilung der „Trinkspiele“, sowie das ein oder andere Werk\nbrachten einem zum\nSchmunzeln.",[87,5132],{"alt":5133,"src":5134},"\"Museum\"","https://media.synyx.de/uploads//2016/06/IMG-20160613-WA0014.jpg","\nAnbetracht des Wetters war das Museum sehr gut besucht, sodass man sich ab und an durch die Menschenmassen vor dem\nBildern kämpfen musste. Auch die Umleitungssituation im Museum führte doch zu einigen Verwirrungen. Nach dem\nanstrengenden und überaus anspruchsvollen Museumsbesuch hatten wir uns eine Stärkung verdient. Allerdings war auch zu\ndiesem Zeitpunkt kein Hering in Sicht sodass wir einen ordentlichen Burger und anschließende Stroopwafel mit Genuß\nverzehrten. Zu diesem Zeitpunkt stießen dann auch die wilden Radler (gerade nochmal rechtzeitig) dazu.",[54,5137,5138],{},"Zusammen ging es dann weiter bei einer klassischen Grachtenfahrt, die sich recht schnell zur einer drei-sprachigen\nGiebelführung durch Amsterdam entpuppte.",[49,5140,5142],{"id":5141},"die-konferenz-goto-amsterdam-2016","Die Konferenz: goto; Amsterdam 2016",[54,5144,5145,5149,5150,3006],{},[87,5146],{"alt":5147,"src":5148},"\"IMG_20160614_101157\"","https://media.synyx.de/uploads//2016/06/IMG_20160614_101157.jpg","\nNachdem wir den ersten Tag die Stadt unsicher gemacht hatten, ging es jetzt weiter mit dem eigentlichen Ziel der Reise,\ndie ",[61,5151,5153],{"href":5046,"rel":5152},[65],"goto; Amsterdam 2016",[54,5155,5156],{},"Die Konferenz bot mehrere Tracks in unterschiedlichen Bereichen der Softwareentwicklung (Data, Spring, Legacy to\nMicroservices, JavaScript und Security) aber auch für nicht- technische Themen wurden in eigenen Track wie Post-Agile\noder im Philosophy Track interessante Vorträge gehalten. Alles in allem ein sehr breit ausgestelltes Programm und für\njeden etwas dabei. Zusätzlich waren die Keynotes mit großen Persönlichkeiten aus unterschiedlichsten Bereichen besetz.",[54,5158,5159,5160,5165,5166,5171],{},"Den ",[61,5161,5164],{"href":5162,"rel":5163},"http://gotocon.com/amsterdam-2016/presentations/show_presentation.jsp?oid=7505",[65],"Anfang"," machte Erich Gamma der über\nEntwicklung von Visual Studio Code, eins der wohl größten JavaScript basierten Projekte, berichtete. Es wurde eine\nkomplette IDE auf Basis vom Webtechnologien geschaffen, welche mit Features einer herkömmlichen IDE ausgestattet ist und\nauf allen gängigen Betriebssystemen verfügbar ist aufgrund des auf ",[61,5167,5170],{"href":5168,"rel":5169},"http://electron.atom.io/",[65],"Electron"," basierten\nAufbaus. Ein spannendes Projekt was durchaus mit den richtigen Ansprüchen daher kommt Stichwort: “Eating your own dog\nfood” was den direkten Einsatz der IDE bei Mircosoft mit einschließt. Allerdings wurde man während des Vortrags das\nGefühl nicht los, dass der Vortrag vor allem dazu dient Entwickler für das Microsoft Universum zu gewinnen. Zusätzlich\nhätte Erich Gamma tatsächlich mal eine Runde Live-Demo Beratung bei Josh Long nehmen können. Aber dazu später mehr.",[54,5173,5174],{},"Weiter ging es in sechs nebenläufigen Tracks die immer wieder und zur Mittagszeit etwas länger mit Pausen und sehr\nhochwertigen Essen unterbrochen wurden.",[54,5176,5177],{},"Hier ein paar Berichte aus einzelnen Sessions die uns am meisten begeistert haben:",[54,5179,5180,5181,5186,5187,5192,5193,5197],{},"Der Data Track beschäftigte sich mit Themen wie der Verarbeitung von massenhaften Daten bspw. mit maschinellen\nLernverfahren. Aber auch das Verständnis für Daten und daraus resultierenden Annahmen wurde betrachtet. Dazu\nmachte",[61,5182,5185],{"href":5183,"rel":5184},"https://lukasvermeer.wordpress.com/",[65],"Lukas Vermeer","\nmit ",[61,5188,5191],{"href":5189,"rel":5190},"http://gotocon.com/amsterdam-2016/presentation/Data%20Science%20vs.%20Data%20Alchemy",[65],"Data Sciene vs Data Alchemy","\nden Anfang und zeigte die Fallstricke der Geschichte auf die Aufgrund von Daten oder besser gesagt Beobachtungen gemacht\nwurden und sich allerdings am Ende oft als Trugschluss herausstellten. Ein Beispiel wurde anhand von Sauerkraut auf den\nSeeschiffen des 17 und 18 Jahrhunderts gemacht, welches zwar für den Anwendungsfall der Skorbut Krankheit (Armut an\nVitamin C)\nausreichte. ",[87,5194],{"alt":5195,"src":5196},"\"Screenshot from 2016-06-21 09-19-58\"","https://media.synyx.de/uploads//2016/06/Screenshot-from-2016-06-21-09-19-58.png","\nAllerdings war diese Annahme beispielsweise für die Südpolexpeditionen nicht mehr richtig. So reichte die Vitaminmenge\nder verwendeten Sauerkrautvariante einfach nicht aus um die Versorgung der Expetitionsteilnehmer mit dem lebenswichtigen\nVitamin zu decken.",[75,5199,5200,5205],{},[54,5201,5202],{},[316,5203,5204],{},"“Science is limited by data",[54,5206,5207],{},[316,5208,5209],{},"Data is limited by engineering”",[54,5211,5212],{},"Diese Kernaussage wurde durch viele Beispiele aus der Geschichte unterstrichen und steht gerade im Hinblick auf\nService-Angeboten wie kaggle.com oder ähnlichen Möglichkeiten zur Datenanalyse im direkten Konflikt, da letztendlich\nquasi jede Aussage aus Daten generiert werden kann auf Basis gewisser Annahmen.",[54,5214,5215,5216,5221,5222,5227],{},"Auch mit vielen Daten und jede Menge Last ging es weiter im Vortrag\nvon ",[61,5217,5220],{"href":5218,"rel":5219},"https://twitter.com/rusmeshenberg",[65],"Ruslan Meshenberg","\nüber",[61,5223,5226],{"href":5224,"rel":5225},"http://gotocon.com/amsterdam-2016/presentation/Microservices%20at%20Netflix%20Scale%20-%20First%20Principles,%20Tradeoffs%20&%20Lessons%20Learned",[65],"Microservices at Netflix Scale",".\nEr berichtete aus der Umstellung der Netflix Architektur auf eine Microservice basierte Architektur und berief sich\ndabei auf einige Grundprinzipien als Quintessenz.",[309,5229,5230,5233,5242,5245],{},[312,5231,5232],{},"Buy vs Build: Zuerst Open Source Software unterstützen und nur im Ausnahmefall auf eigene Lösungen setzen",[312,5234,5235,5236,5241],{},"Services should be stateless: Zur Überprüfung steht\ndie ",[61,5237,5240],{"href":5238,"rel":5239},"http://techblog.netflix.com/2011/07/netflix-simian-army.html",[65],"simian army","bereit, siehe Bild 🙂",[312,5243,5244],{},"Scale out vs. scale up: Da vertikale Skalierung irgendwann an Hardwaregrenzen stößt (mindest schneller)",[312,5246,5247],{},"Automate destructive testing: Vor allem auch in der Produktivumgebung",[54,5249,5250],{},[87,5251],{"alt":5252,"src":5253},"\"png;base6475ef3cbae089d72b\"","https://media.synyx.de/uploads//2016/06/pngbase6475ef3cbae089d72b.png",[54,5255,5256],{},"Zum Letzen Punkt wurde noch eine Anmerkung gemacht, dass solche Test sinnvoller Weise nur während den Geschäftszeiten\ngemacht werden sollen. Der Grund ist einleuchtend:",[75,5258,5259],{},[54,5260,5261],{},[316,5262,5263],{},"“Its a bad thing if your services are failing and your team is sleeping or drunk or both”",[54,5265,5266,5267,5272,5273,5278,5279,5284,5285,5290],{},"Auch für die Container-Freunde gab es etwas zu hören. ",[61,5268,5271],{"href":5269,"rel":5270},"https://twitter.com/saturnism",[65],"Ray Tsang"," von Google\nund ",[61,5274,5277],{"href":5275,"rel":5276},"https://twitter.com/ArjenWassink",[65],"Arjen Wassink"," hielten einen\ngemeinsamen ",[61,5280,5283],{"href":5281,"rel":5282},"http://gotocon.com/amsterdam-2016/presentation/Java-Based%20Microservices,%20Containers,%20Kubernetes%20-%20How%20To",[65],"Vortrag","\nindem eine Java Anwendung auf einem Kubernetes-Cluster mit Docker-Containern live deployt\nwurde. ",[61,5286,5289],{"href":5287,"rel":5288},"http://kubernetes.io/",[65],"Kubernetes"," ist ein Tool von Google, dass zur Verwaltung und Skalierung von Containern\nentwickelt wurde. Ray Tsang zeigte wie flexibel und doch einfach benutzbar Kubernetes ist. Als besonderer Effekt wurde\nder Kubernes-Kluster auf einem eigens mitgebrachten Datencenter betrieben, dass sich aus 5 Raspberry Pi zusammensetzte.\nHiermit sollte demonstriert werden, dass Kubernetes unabhängig von der Umgebung benutzt werden kann. Ob es sich dabei um\nGoogles Cloud Platform oder einen Raspberry Pi-Kluster handelt spielt keine Rolle.",[54,5292,5293,5294,5299,5300,5305],{},"Ein weiterer beeindruckender Vortrag gab es von ",[61,5295,5298],{"href":5296,"rel":5297},"https://twitter.com/starbuxman",[65],"Josh Long"," der mit seinem\nVortrag ",[61,5301,5304],{"href":5302,"rel":5303},"http://gotocon.com/amsterdam-2016/presentation/Cloud%20Native%20Java",[65],"Cloud Native Java"," neue\nGeschwindigkeitsrekorde aufstellte (sowohl auf der Tonspur als auch beim Live Coding). Die vorgestellten Projekte aus\ndem Spring Ökosystem zur Orchestrierung von Microservice-basierten Anwendungen wurden live an der Tastatur erarbeitet.\nDabei wurde ein Einblick geliefert wie eine mögliche Architektur einer Microservice-basierten Anwendung aussieht.\nAußerdem stellte er die unter dem Dach von Spring Cloud zusammengefassten Module vor. Dabei sei allerdings erwähnt, dass\nviele der Arbeiten durch Netflix veröffentlicht wurden und Einzug in das Ökosystem von Spring gehalten haben. Trotz der\nhohen Geschwindigkeit war auch noch Zeit für Humor:",[75,5307,5308],{},[54,5309,5310],{},"“everytime you do field injection, a unit test dies.”",[54,5312,5313],{},"Neben vielen technischen Talks überzeugte die goto; Amsterdam Konferenz vor allem mit dem Philosophie Track.",[75,5315,5316,5321],{},[54,5317,5318],{},[316,5319,5320],{},"“Jeder ist besser als ich. Ich bin nicht so gut wie andere denken und bald werden sie es herausfinden.”",[54,5322,5323],{},"(Imposter Syndrom)",[54,5325,5326,5331,5332,5337,5338,5343],{},[61,5327,5330],{"href":5328,"rel":5329},"https://twitter.com/nativewired",[65],"Gitte Klitgaard’s"," Vortrag über\ndas ",[61,5333,5336],{"href":5334,"rel":5335},"https://de.wikipedia.org/wiki/Hochstapler-Syndrom",[65],"Imposter Syndrom"," gilt es wohl hervorzuheben. Sie berichtete\nüber negative Konsequenzen wie Burn Out als auch über Chancen und positive Aspekte wie Individualität und die stetige\nSelbstverbesserung. Auch eine Folie spendiert bekam\nder ",[61,5339,5342],{"href":5340,"rel":5341},"https://de.wikipedia.org/wiki/Dunning-Kruger-Effekt",[65],"Dunning-Kruger-Effekt",", welcher die Selbstüberschätzung von\nAnfängern beschreibt. Traf einmal ein ein extrovertierte, seblbstbewusster Anfänger auf einen Experten mit Imposter\nSyndrom…Muss man mal drüber nachgedacht haben.",[54,5345,5346],{},[87,5347],{"alt":5348,"src":5349},"dunning-kruger-effekt","https://web.archive.org/web/20160609113938if_/http://terrycolon.com/quotes/a-e/D-Keffect2.gif",[54,5351,5352],{},"Insgesamt eine runde Veranstaltung, welche durch viele spannende Vorträge in einer tollen Location und mit sehr gutem\nEssen glänzte.",[54,5354,5355],{},[87,5356],{"alt":5357,"src":5358},"\"RYJ-8J34F\"","https://media.synyx.de/uploads//2016/06/RYJ-8J34F.jpg",[54,5360,5361],{},"Eins ist gewiss wir kommen wieder! …und nicht bloß wegen dem verpassten Hering 😉",{"title":11,"searchDepth":12,"depth":12,"links":5363},[5364,5365,5366],{"id":5057,"depth":12,"text":5058},{"id":5085,"depth":12,"text":5086},{"id":5141,"depth":12,"text":5142},[998],"2016-06-23T09:37:39","Vom 13. bis 15.06.2016 waren wir zu siebt in Amsterdam auf der goto; Amsterdam.\\nZunächst gibt es einen kleinen Reisebericht zu lesen auf den dann ein paar Impressionen aus den einzelnen Sessions und\\nTalks folgen. Wer direkt zu den inhaltlichen Schwerpunkten unserer Reise springen möchte bitte hier entlang.","https://synyx.de/blog/synyx-goto-amsterdam/",{},"/blog/synyx-goto-amsterdam",{"title":5032,"description":5374},"Vom 13. bis 15.06.2016 waren wir zu siebt in Amsterdam auf der goto; Amsterdam.\nZunächst gibt es einen kleinen Reisebericht zu lesen auf den dann ein paar Impressionen aus den einzelnen Sessions und\nTalks folgen. Wer direkt zu den inhaltlichen Schwerpunkten unserer Reise springen möchte bitte hier entlang.","blog/synyx-goto-amsterdam",[5377,5378,5379],"amsterdam","goto","konferenz","Vom 13. bis 15.06.2016 waren wir zu siebt in Amsterdam auf der goto; Amsterdam. Zunächst gibt es einen kleinen Reisebericht zu lesen auf den dann ein paar Impressionen aus den…","0iW4jrfW1wl3Ta6EPCFAqG6w8AEHQsIlfG32M2tIsa4",{"id":5383,"title":5384,"author":5385,"body":5386,"category":5793,"date":5794,"description":5795,"extension":18,"link":5796,"meta":5797,"navigation":29,"path":5798,"seo":5799,"slug":5801,"stem":5802,"tags":5803,"teaser":5808,"__hash__":5809},"blog/blog/springboot-reactjs-progressive-enhancement-based-on-list-sorting.md","springboot & reactjs #2 | progressive enhancement based on list sorting",[33],{"type":8,"value":5387,"toc":5785},[5388,5391,5400,5403,5406,5410,5456,5458,5461,5464,5469,5476,5479,5482,5489,5511,5522,5525,5532,5539,5542,5549,5568,5574,5577,5580,5583,5597,5600,5623,5637,5640,5650,5656,5663,5666,5669,5672,5686,5705,5708,5717,5733,5737,5760,5766,5770,5777,5782],[45,5389,5384],{"id":5390},"springboot-reactjs-2-progressive-enhancement-based-on-list-sorting",[54,5392,5393,5394,5399],{},"This is the second article of a springboot & reactjs article series about server side rendering and progressive\nenhancement. In the ",[61,5395,5398],{"href":5396,"rel":5397},"https://synyx.de/2016/03/universal-webapp-development-with-spring-boot-react/",[65],"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…",[54,5401,5402],{},"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/",[5404,5405],"hr",{},[49,5407,5409],{"id":5408},"springboot-reactjs-article-series","springboot & reactjs article series",[5411,5412,5413,5421,5450,5453],"ol",{},[312,5414,5415,5420],{},[61,5416,5419],{"href":5417,"rel":5418},"https://synyx.de/2016/03/springboot-reactjs-server-side-rendering",[65],"server side rendering"," ✅",[312,5422,5423,5424],{},"progressive enhancement based on list sorting 🆕\n",[309,5425,5426,5432,5438,5444],{},[312,5427,5428],{},[61,5429,5431],{"href":5430},"#html-form-and-server-side-rendering","HTML form and server side rendering",[312,5433,5434],{},[61,5435,5437],{"href":5436},"#enhance-the-client","Enhance the client",[312,5439,5440],{},[61,5441,5443],{"href":5442},"#make-the-back-button-work-again","Make the back button work again",[312,5445,5446],{},[61,5447,5449],{"href":5448},"#what-do-we-have-learned-so-far","What do we have learned so far",[312,5451,5452],{},"improving developer experience",[312,5454,5455],{},"lessons learned",[5404,5457],{},[54,5459,5460],{},"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",[54,5462,5463],{},"But let’s take one step after another…",[54,5465,5466],{},[436,5467,5468],{},"tl;dr",[54,5470,5471],{},[61,5472,5475],{"href":5473,"rel":5474},"https://github.com/synyx/springboot-reactjs-demo",[65],"project source code is available on github",[49,5477,5431],{"id":5478},"html-form-and-server-side-rendering",[54,5480,5481],{},"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.",[54,5483,5484,5485,5488],{},"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 ",[372,5486,5487],{},"name=\"sort\""," and a label to\nincrease the clickable area.",[54,5490,5491,5492,5495,5496,5499,5500,5503,5504,5506,5507,5510],{},"In the previous blog the ",[436,5493,5494],{},"ProductList"," was the only component to render and therefore the entry in ",[436,5497,5498],{},"main.js",". But\nthe new ",[436,5501,5502],{},"ProductFilter"," is not part of the list. The ",[436,5505,5494],{}," reacts to the filter parameter set by the user.\nSo we have to create an ",[436,5508,5509],{},"App"," container that combines our awesome ProductList and ProductFilter components.",[54,5512,5513,5514,5517,5518,5521],{},"Since we have a container now to combine the ProductList and the ProductFilter components we also have to adjust the\n",[372,5515,5516],{},"global.renderServer"," function. First we add a second parameter ",[372,5519,5520],{},"sortBy"," to be able to render the selected radio button.\nThen we must render the new App container instead of the plain ProductList.",[54,5523,5524],{},"That’s it for the frontend part!",[54,5526,5527,5528,5531],{},"Next we need to extend the backend controller to process the ",[372,5529,5530],{},"sort"," request parameter defined in the ProductFilter form\nand use it to sort the product list.",[54,5533,5534,5535,5538],{},"Additionally the ",[372,5536,5537],{},"React#renderProducts"," method must be extended, too, of course.",[54,5540,5541],{},"And that’s it with the backend part as well!",[54,5543,5544,5545,5548],{},"Now build the frontend, start the spring boot app, open your browser on ",[372,5546,5547],{},"http://localhost:8080"," and start sorting the\nawesome product list 🙂",[459,5550,5552],{"className":3905,"code":5551,"language":3907,"meta":11,"style":11},"\n$ npm run build\n$ ./gradlew bootRun\n\n",[372,5553,5554,5558,5563],{"__ignoreMap":11},[467,5555,5556],{"class":469,"line":470},[467,5557,612],{"emptyLinePlaceholder":29},[467,5559,5560],{"class":469,"line":12},[467,5561,5562],{},"$ npm run build\n",[467,5564,5565],{"class":469,"line":529},[467,5566,5567],{},"$ ./gradlew bootRun\n",[54,5569,5570],{},[87,5571],{"alt":5572,"src":5573},"awesome_productlist_sorting","https://media.synyx.de/uploads//2016/04/awesome_productlist_sorting.gif",[49,5575,5437],{"id":5576},"enhance-the-client",[54,5578,5579],{},"So far our awesome product list is fully functional. Let’s recap what we can do now.",[54,5581,5582],{},"We are able to:",[309,5584,5585,5588,5591,5594],{},[312,5586,5587],{},"see the awesome product info",[312,5589,5590],{},"sort the awesome products by name or price",[312,5592,5593],{},"use the browser’s back and forward button (static site!)",[312,5595,5596],{},"bookmark every single view",[54,5598,5599],{},"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.",[54,5601,5602,5603,5607,5608,5612,5613,803,5616,687,5619,5622],{},"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](",[61,5604,5605],{"href":5605,"rel":5606},"http://git@gitlab-test.synyx.coffee",[65],":\nseber/SynyxBibliothek.git ",[61,5609,5610],{"href":5610,"rel":5611},"https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods",[65],") like ",[372,5614,5615],{},"onClick",[372,5617,5618],{},"onChange",[372,5620,5621],{},"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.",[54,5624,5625,5626,5628,5629,5631,5632,3006],{},"You may ask why we are subscribing our submit handler via ",[372,5627,5621],{}," on the form and not the ",[372,5630,5615],{}," 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 ",[61,5633,5636],{"href":5634,"rel":5635},"https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements",[65],"HTMLFormControlsCollection",[54,5638,5639],{},"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? 😉",[54,5641,5642,5643,5645,5646,5649],{},"So additionally to the ",[372,5644,5516],{}," function used by Nashorn we need a second function ",[372,5647,5648],{},"window.renderClient","\nthat we have to call on the client side (browser) as we will see later in this tutorial.",[54,5651,5652,5653,5655],{},"Next we have to add the initial rendering to the index.html template. Of course, we must call ",[372,5654,5648],{}," 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).",[54,5657,5658,5659,5662],{},"Back to the Java backend we have to inject the initial product list and the sortBy value into the server side model of\nthe ",[372,5660,5661],{},"ProductController.java"," class. Additionally we add a second endpoint to provide the sorted product list as json.",[54,5664,5665],{},"That’s it!",[54,5667,5668],{},"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.",[49,5670,5443],{"id":5671},"make-the-back-button-work-again",[54,5673,5674,5675,5680,5681,675],{},"Okay, at first we should face the browser url. With HTML5 we’ve gained\nthe ",[61,5676,5679],{"href":5677,"rel":5678},"https://developer.mozilla.org/en-US/docs/Web/API/History_API",[65],"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 ",[61,5682,5685],{"href":5683,"rel":5684},"https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries",[65],"window.history.pushState",[54,5687,5688,5689,5692,5693,5696,5697,5700,5701,5704],{},"Next we want to listen to the browsers back and forward buttons. This can be implemented with subscribing to the\n",[372,5690,5691],{},"popstate"," event. The subscription is done within ",[372,5694,5695],{},"componentDidMount"," since we only want to subscribe in the browser\nenvironment. ",[372,5698,5699],{},"Constructor"," and it’s counterpart ",[372,5702,5703],{},"componentWillMount"," are both called on server side creating the static\nhtml markup.",[54,5706,5707],{},"Finally we made it 🙂",[54,5709,5710,5711,5716],{},"Go on, build the frontend, start the spring boot app, open ",[316,5712,5713],{},[61,5714,5547],{"href":5547,"rel":5715},[65]," and admire our awesome progressively\nenhanced product list. Functional without JavaScript and even better with enabled JavaScript.",[459,5718,5719],{"className":3905,"code":5551,"language":3907,"meta":11,"style":11},[372,5720,5721,5725,5729],{"__ignoreMap":11},[467,5722,5723],{"class":469,"line":470},[467,5724,612],{"emptyLinePlaceholder":29},[467,5726,5727],{"class":469,"line":12},[467,5728,5562],{},[467,5730,5731],{"class":469,"line":529},[467,5732,5567],{},[49,5734,5736],{"id":5735},"what-do-we-have-learned-so-far","What do we have learned so far?",[309,5738,5739,5749,5757],{},[312,5740,5741,5742,5745,5746],{},"use plain HTML ",[372,5743,5744],{},"\u003Cform>"," element and enhance it with JavaScript and ",[372,5747,5748],{},"event.preventDefault",[312,5750,5751,5752,2884,5754,5756],{},"use ",[372,5753,5679],{},[372,5755,5691],{}," event to handle the browser back/forward button on the client",[312,5758,5759],{},"manually rebuilding and reloading the ReactJS app still sucks (autoreload would be cool, right)",[54,5761,5762],{},[87,5763],{"alt":5764,"src":5765},"progressive_js","https://media.synyx.de/uploads//2016/04/progressive_js.gif",[49,5767,5769],{"id":5768},"the-next-steps-will-be","The next steps will be",[309,5771,5772,5775],{},[312,5773,5774],{},"using webpack to enhance the developer experience",[312,5776,5455],{},[54,5778,5779],{},[436,5780,5781],{},"Stay tuned and keep learning!",[986,5783,5784],{},"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":11,"searchDepth":12,"depth":12,"links":5786},[5787,5788,5789,5790,5791,5792],{"id":5408,"depth":12,"text":5409},{"id":5478,"depth":12,"text":5431},{"id":5576,"depth":12,"text":5437},{"id":5671,"depth":12,"text":5443},{"id":5735,"depth":12,"text":5736},{"id":5768,"depth":12,"text":5769},[998],"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":5384,"description":5800},"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",[837,638,5804,5805,5806,5807],"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":5811,"title":5812,"author":5813,"body":5814,"category":6396,"date":6397,"description":6398,"extension":18,"link":6399,"meta":6400,"navigation":29,"path":6401,"seo":6402,"slug":6403,"stem":6404,"tags":6405,"teaser":6406,"__hash__":6407},"blog/blog/springboot-reactjs-server-side-rendering.md","springboot & reactjs #1 | server side rendering",[33],{"type":8,"value":5815,"toc":6389},[5816,5819,5822,5824,5826,5886,5888,5891,5894,5899,5902,5905,5908,5911,5914,5918,5923,5926,5929,5932,5940,5943,5946,5954,5960,5986,5989,5998,6004,6007,6010,6015,6022,6027,6042,6049,6060,6074,6078,6092,6096,6103,6118,6121,6124,6129,6144,6149,6156,6159,6183,6188,6198,6202,6223,6226,6228,6231,6244,6249,6252,6255,6258,6293,6304,6310,6315,6321,6324,6337,6343,6345,6367,6373,6375,6383,6387],[45,5817,5812],{"id":5818},"springboot-reactjs-1-server-side-rendering",[54,5820,5821],{},"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.",[5404,5823],{},[49,5825,5409],{"id":5408},[5411,5827,5828,5874,5882,5884],{},[312,5829,5830,5831],{},"server side rendering ✅\n",[309,5832,5833,5839,5845,5851,5857,5863,5869],{},[312,5834,5835],{},[61,5836,5838],{"href":5837},"#why-java","Why Java for the backend?",[312,5840,5841],{},[61,5842,5844],{"href":5843},"#why-reactjs","Why ReactJS for the client?",[312,5846,5847],{},[61,5848,5850],{"href":5849},"#springboot","Spring Boot Initializr",[312,5852,5853],{},[61,5854,5856],{"href":5855},"#backend","Backend",[312,5858,5859],{},[61,5860,5862],{"href":5861},"#frontend","Frontend",[312,5864,5865],{},[61,5866,5868],{"href":5867},"#running-the-app","Running the app",[312,5870,5871],{},[61,5872,5449],{"href":5873},"#what-we-have-learned-so-far",[312,5875,5876,5881],{},[61,5877,5880],{"href":5878,"rel":5879},"https://synyx.de/2016/04/springboot-reactjs-progressive-enhancement-based-on-list-sorting/",[65],"progressive enhancement based on list sorting","\n🆕",[312,5883,5452],{},[312,5885,5455],{},[5404,5887],{},[54,5889,5890],{},"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.",[54,5892,5893],{},"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.",[54,5895,5896],{},[436,5897,5898],{},"Benefits of universal applications",[54,5900,5901],{},"thanks to server side rendered markup",[54,5903,5904],{},"– the app is fully functional from the start",[54,5906,5907],{},"– the app is usable instantly",[54,5909,5910],{},"JavaScript just enhances the features",[54,5912,5913],{},"– ajax calls without full page reloading are super fast",[54,5915,5916],{},[436,5917,5468],{},[54,5919,5920],{},[61,5921,5475],{"href":5473,"rel":5922},[65],[49,5924,5838],{"id":5925},"why-java-for-the-backend",[54,5927,5928],{},"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.",[54,5930,5931],{},"So to answer the question",[309,5933,5934,5937],{},[312,5935,5936],{},"team knowledge (Spring, …)",[312,5938,5939],{},"battle tested solutions (Spring Security, Spring MVC, …)",[49,5941,5844],{"id":5942},"why-reactjs-for-the-client",[54,5944,5945],{},"Well, personally I am a fan of react and it’s ecosystem. That’s it! 😎",[54,5947,5948,5949,675],{},"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 ",[61,5950,5953],{"href":5951,"rel":5952},"https://web.archive.org/web/20160411023634/https://egghead.io/series/getting-started-with-redux",[65],"redux",[54,5955,5956,5957],{},"Furthermore it supports server side rendering quite well. ",[316,5958,5959],{},"(afaik Angular 2 and Ember could also be used, or cyclejs,\nor …)",[75,5961,5962,5967],{},[54,5963,5964],{},[436,5965,5966],{},"Disclaimer",[54,5968,5969,5970,5975,5976,5980,5981,5985],{},"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 ",[61,5971,5974],{"href":5972,"rel":5973},"http://javascriptplayground.com/blog/2016/02/the-react-webpack-tooling-problem",[65],"how to start with react"," (\nwithout\nusing webpack). Additionally I recommend to have a look at the official documentation\nfor ",[61,5977,5804],{"href":5978,"rel":5979},"https://facebook.github.io/react/docs/thinking-in-react.html",[65]," as well as ",[61,5982,5806],{"href":5983,"rel":5984},"https://spring.io/docs",[65],"\nof\ncourse.",[45,5987,5850],{"id":5988},"spring-boot-initializr",[54,5990,5991,5992,5997],{},"At first we’re going to generate a bootstrap project with the awesome ",[61,5993,5996],{"href":5994,"rel":5995},"https://start.spring.io",[65],"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.",[54,5999,6000],{},[87,6001],{"alt":6002,"src":6003},"springboot-initializr","https://media.synyx.de/uploads//2016/02/springboot-initializer.png",[45,6005,5856],{"id":6006},"backend",[54,6008,6009],{},"Starting with the backend we have to create the following files.",[54,6011,6012],{},[436,6013,6014],{},"index.html",[54,6016,6017,6018,6021],{},"The html template is as simple as it could be. We just need a div that acts as container for our ReactJS app.\n",[372,6019,6020],{},"th:utext=\"${content}\""," is thymeleaf specific and injects the content attribute of the view model as unescaped string.",[54,6023,6024],{},[436,6025,6026],{},"React.java",[54,6028,6029,6030,6033,6034,6037,6038,6041],{},"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 ",[372,6031,6032],{},"window"," object nor a\n",[372,6035,6036],{},"console"," for logging. But since the latter one is required by ReactJS we have to load a ",[372,6039,6040],{},"nashorn-polyfill.js"," file\nbefore anything else.",[54,6043,6044,6045,6048],{},"We are loading our JavaScript sources into Nashorn with ",[372,6046,6047],{},"nashornScriptEngine.eval (\"load ('...')\")",". This is the same as\nincluding a script tag in a html document.",[54,6050,6051,6052,6055,6056,6059],{},"However, we could also call ",[372,6053,6054],{},"nashorn.eval (new InputStreamReader (...))"," to load the JavaScript files instead of using\nthe Nashorn specific ",[316,6057,6058],{},"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 😉",[54,6061,6062,6063,6065,6066,6069,6070,6073],{},"Furthermore we have to implement a method ",[316,6064,5537],{}," which will invoke a global ",[372,6067,6068],{},"renderServer"," function\ndefined in ",[316,6071,6072],{},"app.bundle.js"," to create the rendered html string.",[54,6075,6076],{},[436,6077,6040],{},[54,6079,6080,6081,6084,6085,6087,6088,6091],{},"The polyfill for nashorn has to define a ",[436,6082,6083],{},"global"," variable (for reasons I will explain later) and the already\nmentioned ",[436,6086,6036],{},". ",[316,6089,6090],{},"print"," is a Nashorn function that logs on stdout.",[54,6093,6094],{},[436,6095,5661],{},[54,6097,6098,6099,6102],{},"The ProductController is responsible for getting the products and for setting the rendered html string as the ",[372,6100,6101],{},"content","\nattribute of the view model.",[54,6104,6105,6106,6111,6112,6117],{},"Additionally we need\na ",[61,6107,6110],{"href":6108,"rel":6109},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/Product.java",[65],"Product.java","\nPOJO and\na ",[61,6113,6116],{"href":6114,"rel":6115},"https://github.com/synyx/springboot-reactjs-demo/blob/031a52fee5cc49c91988227b6b29b9857e5fed86/src/main/java/de/synyx/tutorials/spring/reactjs/demo/product/ProductRepository.java",[65],"ProductRepository.java",".\nI think this is very straight forward and code snippets are obsolete here.",[45,6119,5862],{"id":6120},"frontend",[54,6122,6123],{},"With the backend part ready we can start with the frontend.",[54,6125,6126,6127,675],{},"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",[316,6128,6026],{},[459,6130,6132],{"className":3905,"code":6131,"language":3907,"meta":11,"style":11},"$ npm init\n$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n\n",[372,6133,6134,6139],{"__ignoreMap":11},[467,6135,6136],{"class":469,"line":470},[467,6137,6138],{},"$ npm init\n",[467,6140,6141],{"class":469,"line":12},[467,6142,6143],{},"$ npm i --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom\n",[54,6145,6146],{},[436,6147,6148],{},"webpack.config.js",[54,6150,6151,6152,6155],{},"Next we configure webpack to generate a bundle of our JavaScript files including the ReactJS library and our app\nbusiness logic. Please note ",[436,6153,6154],{},"output.filename"," which is the file loaded by React.java.",[54,6157,6158],{},"Webpack can then simply be used to create the bundle by a npm task.",[459,6160,6162],{"className":3905,"code":6161,"language":3907,"meta":11,"style":11},"// package.json\n\"scripts\": {\n \"build\": \"webpack\"\n}\n\n",[372,6163,6164,6169,6174,6179],{"__ignoreMap":11},[467,6165,6166],{"class":469,"line":470},[467,6167,6168],{},"// package.json\n",[467,6170,6171],{"class":469,"line":12},[467,6172,6173],{},"\"scripts\": {\n",[467,6175,6176],{"class":469,"line":529},[467,6177,6178],{}," \"build\": \"webpack\"\n",[467,6180,6181],{"class":469,"line":551},[467,6182,770],{},[54,6184,6185],{},[436,6186,6187],{},"ProducList.js",[54,6189,6190,6191,6193,6194,6197],{},"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 ",[316,6192,6110],{},"?). Therefore we\nimplement a function that takes our products and returns the representational markup. To avoid\n",[372,6195,6196],{},"\"Cannot read property 'map' of undefined\""," type errors we simply assign an empty array to the products by default.",[54,6199,6200],{},[436,6201,5498],{},[54,6203,6204,6205,6207,6208,6210,6211,6213,6214,6216,6217,6219,6220,6222],{},"Next we need the entry point of our ReactJS app to define the ",[436,6206,6068],{}," function invoked by Nashorn. Remember the\n",[436,6209,6083],{}," variable set in ",[316,6212,6040],{},"? We use this variable now to “export” our ",[316,6215,6068],{}," function. If\nyou are familiar with the NodeJS environment, you already know that the ",[316,6218,6083],{}," object is the equivalent to the\n",[316,6221,6032],{}," object available in the browser. And Nashorn is our equivalent of NodeJS 😉",[45,6224,5868],{"id":6225},"running-the-app",[54,6227,5665],{},[54,6229,6230],{},"Now we can run our first universal server side rendered springboot application to admire our graceful product list. Go\non, run",[459,6232,6234],{"className":3905,"code":6233,"language":3907,"meta":11,"style":11},"$ npm run build\n$ ./gradlew bootRun\n",[372,6235,6236,6240],{"__ignoreMap":11},[467,6237,6238],{"class":469,"line":470},[467,6239,5562],{},[467,6241,6242],{"class":469,"line":12},[467,6243,5567],{},[54,6245,6246,6247,675],{},"open your Browser and load ",[372,6248,5547],{},[54,6250,6251],{},"Just…",[54,6253,6254],{},"to see…",[54,6256,6257],{},"a wonderful stacktrace…",[459,6259,6261],{"className":3905,"code":6260,"language":3907,"meta":11,"style":11},"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",[372,6262,6263,6268,6273,6278,6283,6288],{"__ignoreMap":11},[467,6264,6265],{"class":469,"line":470},[467,6266,6267],{},"jdk.nashorn.internal.runtime.ECMAException: TypeError:\n",[467,6269,6270],{"class":469,"line":12},[467,6271,6272],{},"[de.synyx...Product@553287f8, Product@65ae29e6] has no such function \"map\"\n",[467,6274,6275],{"class":469,"line":529},[467,6276,6277],{}," at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:58) ~[nashorn.jar:na]\n",[467,6279,6280],{"class":469,"line":551},[467,6281,6282],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:214) ~[nashorn.jar:na]\n",[467,6284,6285],{"class":469,"line":583},[467,6286,6287],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:186) ~[nashorn.jar:na]\n",[467,6289,6290],{"class":469,"line":589},[467,6291,6292],{}," at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:173) ~[nashorn.jar:na]\n",[54,6294,6295,6296,6299,6300,6303],{},"The reason is that Nashorn interprets Java objects as, surprise, Java objects. As you remember, our ReactJS component\n",[372,6297,6298],{},"\u003CProductList />"," expects a list of products (actually a JavaScript array). But currently the type of products is a\n",[372,6301,6302],{},"java.util.List"," which doesn’t have the map method. Note the datatype of products in the image below.",[54,6305,6306],{},[87,6307],{"alt":6308,"src":6309},"nashorn-debugging","https://media.synyx.de/uploads//2016/03/nashorn-debugging.png",[75,6311,6312],{},[54,6313,6314],{},"“Given a Java array or Collection, this function returns a JavaScript array with a shallow copy of its contents”",[54,6316,6317,6318,6320],{},"So our renderServer function defined in ",[372,6319,5498],{}," must be extended to:",[54,6322,6323],{},"Now we’re ready to go 🙂",[54,6325,6326,6327,6330,6331,6336],{},"Rebuild the frontend with ",[372,6328,6329],{},"npm run build",", restart the Spring Boot application, reload ",[316,6332,6333],{},[61,6334,5547],{"href":5547,"rel":6335},[65]," and\nadmire our awesome product list.",[54,6338,6339],{},[87,6340],{"alt":6341,"src":6342},"awesome-product-list-001","https://media.synyx.de/uploads//2016/03/awesome-product-list-001.png",[49,6344,5736],{"id":5735},[309,6346,6347,6350,6356,6364],{},[312,6348,6349],{},"using Nashorn is no rocket science",[312,6351,6352,6353,6355],{},"load js files via ",[372,6354,6047],{}," to enable debugging (at least in IntelliJ)",[312,6357,6358,6360,6361],{},[316,6359,6302],{}," must be converted to JavaScript array with ",[372,6362,6363],{},"Java.from",[312,6365,6366],{},"manually rebuilding and reloading the ReactJS app sucks (autoreload would be cool, right)",[54,6368,6369],{},[87,6370],{"alt":6371,"src":6372},"js-webpack-nashorn","https://media.synyx.de/uploads//2016/03/js-webpack-nashorn.png",[49,6374,5769],{"id":5768},[309,6376,6377,6380],{},[312,6378,6379],{},"using webpack to enhance developer experience",[312,6381,6382],{},"implementing the sorting feature",[54,6384,6385],{},[436,6386,5781],{},[986,6388,5784],{},{"title":11,"searchDepth":12,"depth":12,"links":6390},[6391,6392,6393,6394,6395],{"id":5408,"depth":12,"text":5409},{"id":5925,"depth":12,"text":5838},{"id":5942,"depth":12,"text":5844},{"id":5735,"depth":12,"text":5736},{"id":5768,"depth":12,"text":5769},[998],"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":5812,"description":5821},"springboot-reactjs-server-side-rendering","blog/springboot-reactjs-server-side-rendering",[837,638,5804,5805,5806,5807],"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":6409,"title":6410,"author":6411,"body":6412,"category":7093,"date":7094,"description":6419,"extension":18,"link":7095,"meta":7096,"navigation":29,"path":7097,"seo":7098,"slug":6416,"stem":7099,"tags":7100,"teaser":7102,"__hash__":7103},"blog/blog/awesome-css-3-layouting.md","Awesome CSS 3 Layouting",[33],{"type":8,"value":6413,"toc":7088},[6414,6417,6420,6434,6449,6462,6465,6468,6474,6658,6684,6704,6707,6711,6725,6731,7037,7043,7054,7057,7060,7063,7074,7083,7086],[45,6415,6410],{"id":6416},"awesome-css-3-layouting",[54,6418,6419],{},"At first let me ask you a few questions about developing web applications:",[309,6421,6422,6425,6428,6431],{},[312,6423,6424],{},"How do you create multiple column layouts?",[312,6426,6427],{},"How do you make it flexible?",[312,6429,6430],{},"How do you solve the 100% height problem?",[312,6432,6433],{},"How do you make it responsiveness for Desktop vs Mobile?",[54,6435,6436,6437,6440,6441,6444,6445,6448],{},"If one of your answers contained ‘",[316,6438,6439],{},"absolute positioning","‘, ‘",[316,6442,6443],{},"floating","‘ or ‘",[316,6446,6447],{},"JavaScript","‘ you’re welcome for further\nreading about my favourite CSS 3 features. Wait, CSS 3? The thing that enables rounded corners and gradients? Yep,\nexactly, and despite the new trend of flat design CSS 3 is still useful since it has a bit more to offer than rounded\ncorners and gradients.",[54,6450,6451],{},[436,6452,6453,6456,6457,91,6460],{},[3639,6454,6455],{},"Unfortunately this is still bleeding edge and even Firefox (version 21.0 on ubuntu 13.04) doesn’t render the\nexamples."," Update: A few hours ago version 22 of Firefox was released with support for ",[372,6458,6459],{},"display: flex",[316,6461,2864],{},[54,6463,6464],{},"Feel free to add working examples in the comments below 🙂",[49,6466,6459],{"id":6467},"display-flex",[54,6469,6470,6471,6473],{},"My most favourite feature is the new display property ",[372,6472,6459],{},". This solves the first three questions including\nthe most painful 100% height problem. Remember the ugly JavaScript workarounds to set the height equally to to highest\ndiv? Or the abuse of the border attribute and absolute positioned divs? Well, forget that. All you gonna need in the\nfuture are a few lines of css code.",[459,6475,6477],{"className":3905,"code":6476,"language":3907,"meta":11,"style":11},".container {\n display: -webkit-flex;\n display: flex;\n}\n.menu {\n overflow: hidden;\n background-color: #D8E47F;\n -webkit-flex: 1;\n flex: 1;\n}\n.content {\n -webkit-flex: 3;\n flex: 3;\n}\n.sidenote {\n padding: 1em;\n background-color: #D8E47F;\n -webkit-flex: 2;\n flex: 2;\n}\n.menu > ul {\n margin: 1em;\n list-style-type: none;\n white-space: nowrap;\n}\n.menu a {\n color: black;\n}\n.content article {\n margin: 1em;\n}\n* {\n padding: 0;\n margin: 0;\n}\n\n",[372,6478,6479,6484,6489,6494,6498,6503,6508,6513,6518,6523,6527,6532,6537,6542,6546,6551,6556,6560,6565,6570,6574,6579,6585,6591,6597,6602,6608,6614,6619,6625,6630,6635,6641,6647,6653],{"__ignoreMap":11},[467,6480,6481],{"class":469,"line":470},[467,6482,6483],{},".container {\n",[467,6485,6486],{"class":469,"line":12},[467,6487,6488],{}," display: -webkit-flex;\n",[467,6490,6491],{"class":469,"line":529},[467,6492,6493],{}," display: flex;\n",[467,6495,6496],{"class":469,"line":551},[467,6497,770],{},[467,6499,6500],{"class":469,"line":583},[467,6501,6502],{},".menu {\n",[467,6504,6505],{"class":469,"line":589},[467,6506,6507],{}," overflow: hidden;\n",[467,6509,6510],{"class":469,"line":599},[467,6511,6512],{}," background-color: #D8E47F;\n",[467,6514,6515],{"class":469,"line":609},[467,6516,6517],{}," -webkit-flex: 1;\n",[467,6519,6520],{"class":469,"line":615},[467,6521,6522],{}," flex: 1;\n",[467,6524,6525],{"class":469,"line":773},[467,6526,770],{},[467,6528,6529],{"class":469,"line":778},[467,6530,6531],{},".content {\n",[467,6533,6534],{"class":469,"line":794},[467,6535,6536],{}," -webkit-flex: 3;\n",[467,6538,6539],{"class":469,"line":806},[467,6540,6541],{}," flex: 3;\n",[467,6543,6544],{"class":469,"line":905},[467,6545,770],{},[467,6547,6548],{"class":469,"line":911},[467,6549,6550],{},".sidenote {\n",[467,6552,6553],{"class":469,"line":916},[467,6554,6555],{}," padding: 1em;\n",[467,6557,6558],{"class":469,"line":922},[467,6559,6512],{},[467,6561,6562],{"class":469,"line":2290},[467,6563,6564],{}," -webkit-flex: 2;\n",[467,6566,6567],{"class":469,"line":2300},[467,6568,6569],{}," flex: 2;\n",[467,6571,6572],{"class":469,"line":2306},[467,6573,770],{},[467,6575,6576],{"class":469,"line":2311},[467,6577,6578],{},".menu > ul {\n",[467,6580,6582],{"class":469,"line":6581},22,[467,6583,6584],{}," margin: 1em;\n",[467,6586,6588],{"class":469,"line":6587},23,[467,6589,6590],{}," list-style-type: none;\n",[467,6592,6594],{"class":469,"line":6593},24,[467,6595,6596],{}," white-space: nowrap;\n",[467,6598,6600],{"class":469,"line":6599},25,[467,6601,770],{},[467,6603,6605],{"class":469,"line":6604},26,[467,6606,6607],{},".menu a {\n",[467,6609,6611],{"class":469,"line":6610},27,[467,6612,6613],{}," color: black;\n",[467,6615,6617],{"class":469,"line":6616},28,[467,6618,770],{},[467,6620,6622],{"class":469,"line":6621},29,[467,6623,6624],{},".content article {\n",[467,6626,6628],{"class":469,"line":6627},30,[467,6629,6584],{},[467,6631,6633],{"class":469,"line":6632},31,[467,6634,770],{},[467,6636,6638],{"class":469,"line":6637},32,[467,6639,6640],{},"* {\n",[467,6642,6644],{"class":469,"line":6643},33,[467,6645,6646],{}," padding: 0;\n",[467,6648,6650],{"class":469,"line":6649},34,[467,6651,6652],{}," margin: 0;\n",[467,6654,6656],{"class":469,"line":6655},35,[467,6657,770],{},[54,6659,6660,6661,6664,6665,6668,6669,6672,6673,6676,6677,6679,6680,6683],{},"The value of the ",[372,6662,6663],{},"flex"," property tells the browser how much space the section should fill of the available place. In our\nexample the ",[316,6666,6667],{},".main-nav"," is the smallest, followed by ",[316,6670,6671],{},".side-note"," which is twice as big and by ",[316,6674,6675],{},".content"," which is\nthree times as big as the ",[316,6678,6667],{},". The children of ",[316,6681,6682],{},".wrapper"," are flexible (as the name flex tells us), in other\nwords these sections will adjust their width relatively to the parent element. Feel free to play around with the code\npen above! Open it in a new window and change the browser size and see how the other columns adapt their height\nautomatically to the ‘master’ column. Awesome, isn’t it? No more JavaScript calculations or ugly CSS workarounds for\nthis trivial use case.",[54,6685,6686,6687,6690,6691,6694,6695,6697,6698,675],{},"Now what if you want a static width for the ",[316,6688,6689],{},".main-menu",". Well, just remove ",[372,6692,6693],{},"flex: 1"," or replace it by a width\ndeclaration, that’s it. The other columns will take the remaining place that is left, of course relative to it’s set\n",[372,6696,6663],{}," value. If you’re interested to dive deaper into the amazing flexbox layout I recommend the article\non ",[61,6699,6703],{"href":6700,"rel":6701,"title":6702},"http://css-tricks.com/snippets/css/a-guide-to-flexbox/",[65],"css-tricks.com | flexbox","css-tricks.com",[54,6705,6706],{},"Impressed so far? We’re just getting started with CSS 3!",[49,6708,6710],{"id":6709},"media","@media",[54,6712,6713,6714,6717,6718,6724],{},"Nowadays we developers ",[3639,6715,6716],{},"hopefully"," want to support various devices and resolutions. How often do I curse webpages\nwhile surfing on it with my smartphone. Most smartphones uses Webkit as browser platform, so maybe it’s worth to take a\nlook\nat ",[61,6719,6723],{"href":6720,"rel":6721,"title":6722},"https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Media_queries",[65],"MDN | Media Queries","CSS 3 Media Queries","\neven now.",[54,6726,6727,6728,6730],{},"In the codepen below the ",[372,6729,6710],{}," is at the bottom since it must override the default css values. In this example the\nsidenote will be hidden if the display is too small. Furthermore the menu on the left will be positioned at the top when\ndecreasing the display size even more.",[459,6732,6734],{"className":3905,"code":6733,"language":3907,"meta":11,"style":11},".container {\n display: -webkit-flex;\n display: flex;\n}\n.menu {\n overflow: hidden;\n background-color: #D8E47F;\n -webkit-flex: 1;\n flex: 1;\n -webkit-transition: -webkit-flex 1s;\n transition: flex 1s;\n}\n.content {\n -webkit-flex: 3;\n flex: 3;\n}\n.sidenote {\n padding: 1em;\n background-color: #D8E47F;\n -webkit-flex: 2;\n flex: 2;\n}\n.menu > ul {\n margin: 1em;\n list-style-type: none;\n white-space: nowrap;\n}\n.menu a {\n color: black;\n}\n.content article {\n margin: 1em;\n}\n@media (max-width: 800px) {\n .sidenote {\n display: none;\n }\n}\n@media (max-width: 400px) {\n .menu {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n }\n .menu > ul {\n display: -webkit-flex;\n display: -moz-box;\n display: flex;\n }\n .menu > ul > li {\n padding: 0 1em;\n -webkit-flex: 1;\n flex: 1;\n }\n .content {\n padding-top: 3em\n }\n}\n* {\n padding: 0;\n margin: 0;\n}\n\n",[372,6735,6736,6740,6744,6748,6752,6756,6760,6764,6768,6772,6777,6782,6786,6790,6794,6798,6802,6806,6810,6814,6818,6822,6826,6830,6834,6838,6842,6846,6850,6854,6858,6862,6866,6870,6875,6880,6886,6891,6896,6902,6908,6914,6920,6926,6932,6937,6943,6949,6955,6961,6966,6972,6978,6984,6990,6995,7001,7007,7012,7017,7022,7027,7032],{"__ignoreMap":11},[467,6737,6738],{"class":469,"line":470},[467,6739,6483],{},[467,6741,6742],{"class":469,"line":12},[467,6743,6488],{},[467,6745,6746],{"class":469,"line":529},[467,6747,6493],{},[467,6749,6750],{"class":469,"line":551},[467,6751,770],{},[467,6753,6754],{"class":469,"line":583},[467,6755,6502],{},[467,6757,6758],{"class":469,"line":589},[467,6759,6507],{},[467,6761,6762],{"class":469,"line":599},[467,6763,6512],{},[467,6765,6766],{"class":469,"line":609},[467,6767,6517],{},[467,6769,6770],{"class":469,"line":615},[467,6771,6522],{},[467,6773,6774],{"class":469,"line":773},[467,6775,6776],{}," -webkit-transition: -webkit-flex 1s;\n",[467,6778,6779],{"class":469,"line":778},[467,6780,6781],{}," transition: flex 1s;\n",[467,6783,6784],{"class":469,"line":794},[467,6785,770],{},[467,6787,6788],{"class":469,"line":806},[467,6789,6531],{},[467,6791,6792],{"class":469,"line":905},[467,6793,6536],{},[467,6795,6796],{"class":469,"line":911},[467,6797,6541],{},[467,6799,6800],{"class":469,"line":916},[467,6801,770],{},[467,6803,6804],{"class":469,"line":922},[467,6805,6550],{},[467,6807,6808],{"class":469,"line":2290},[467,6809,6555],{},[467,6811,6812],{"class":469,"line":2300},[467,6813,6512],{},[467,6815,6816],{"class":469,"line":2306},[467,6817,6564],{},[467,6819,6820],{"class":469,"line":2311},[467,6821,6569],{},[467,6823,6824],{"class":469,"line":6581},[467,6825,770],{},[467,6827,6828],{"class":469,"line":6587},[467,6829,6578],{},[467,6831,6832],{"class":469,"line":6593},[467,6833,6584],{},[467,6835,6836],{"class":469,"line":6599},[467,6837,6590],{},[467,6839,6840],{"class":469,"line":6604},[467,6841,6596],{},[467,6843,6844],{"class":469,"line":6610},[467,6845,770],{},[467,6847,6848],{"class":469,"line":6616},[467,6849,6607],{},[467,6851,6852],{"class":469,"line":6621},[467,6853,6613],{},[467,6855,6856],{"class":469,"line":6627},[467,6857,770],{},[467,6859,6860],{"class":469,"line":6632},[467,6861,6624],{},[467,6863,6864],{"class":469,"line":6637},[467,6865,6584],{},[467,6867,6868],{"class":469,"line":6643},[467,6869,770],{},[467,6871,6872],{"class":469,"line":6649},[467,6873,6874],{},"@media (max-width: 800px) {\n",[467,6876,6877],{"class":469,"line":6655},[467,6878,6879],{}," .sidenote {\n",[467,6881,6883],{"class":469,"line":6882},36,[467,6884,6885],{}," display: none;\n",[467,6887,6889],{"class":469,"line":6888},37,[467,6890,765],{},[467,6892,6894],{"class":469,"line":6893},38,[467,6895,770],{},[467,6897,6899],{"class":469,"line":6898},39,[467,6900,6901],{},"@media (max-width: 400px) {\n",[467,6903,6905],{"class":469,"line":6904},40,[467,6906,6907],{}," .menu {\n",[467,6909,6911],{"class":469,"line":6910},41,[467,6912,6913],{}," position: fixed;\n",[467,6915,6917],{"class":469,"line":6916},42,[467,6918,6919],{}," top: 0;\n",[467,6921,6923],{"class":469,"line":6922},43,[467,6924,6925],{}," left: 0;\n",[467,6927,6929],{"class":469,"line":6928},44,[467,6930,6931],{}," right: 0;\n",[467,6933,6935],{"class":469,"line":6934},45,[467,6936,765],{},[467,6938,6940],{"class":469,"line":6939},46,[467,6941,6942],{}," .menu > ul {\n",[467,6944,6946],{"class":469,"line":6945},47,[467,6947,6948],{}," display: -webkit-flex;\n",[467,6950,6952],{"class":469,"line":6951},48,[467,6953,6954],{}," display: -moz-box;\n",[467,6956,6958],{"class":469,"line":6957},49,[467,6959,6960],{}," display: flex;\n",[467,6962,6964],{"class":469,"line":6963},50,[467,6965,765],{},[467,6967,6969],{"class":469,"line":6968},51,[467,6970,6971],{}," .menu > ul > li {\n",[467,6973,6975],{"class":469,"line":6974},52,[467,6976,6977],{}," padding: 0 1em;\n",[467,6979,6981],{"class":469,"line":6980},53,[467,6982,6983],{}," -webkit-flex: 1;\n",[467,6985,6987],{"class":469,"line":6986},54,[467,6988,6989],{}," flex: 1;\n",[467,6991,6993],{"class":469,"line":6992},55,[467,6994,765],{},[467,6996,6998],{"class":469,"line":6997},56,[467,6999,7000],{}," .content {\n",[467,7002,7004],{"class":469,"line":7003},57,[467,7005,7006],{}," padding-top: 3em\n",[467,7008,7010],{"class":469,"line":7009},58,[467,7011,765],{},[467,7013,7015],{"class":469,"line":7014},59,[467,7016,770],{},[467,7018,7020],{"class":469,"line":7019},60,[467,7021,6640],{},[467,7023,7025],{"class":469,"line":7024},61,[467,7026,6646],{},[467,7028,7030],{"class":469,"line":7029},62,[467,7031,6652],{},[467,7033,7035],{"class":469,"line":7034},63,[467,7036,770],{},[54,7038,7039,7040,7042],{},"A more complex use case could be the alignment of the navigation dependent of the screen size. With ",[372,7041,6710],{}," we can\nsimply place it on the top if the screen is small like on a smartphone or place it on the right if the page is visited\nby a desktop browser or even by a mobile one in landscape mode. And all magic is done with CSS only! Again, feel free to\nplay around with the given codepen.",[54,7044,7045,7046,7048,7049,675],{},"There are a lot more ",[372,7047,6710],{}," properties and possibilities that can be best read\non ",[61,7050,6703],{"href":7051,"rel":7052,"title":7053},"http://css-tricks.com/css-media-queries/",[65],"css-tricks | Media Queries",[49,7055,7056],{"id":7056},"transitions",[54,7058,7059],{},"Last but not least let me introduce css transitions if you never heard of it so far. As well as you won’t need\nJavaScript for 100% height calculations anymore you won’t need it for simple animations.",[54,7061,7062],{},"Let’s imagine that we have too much content and want to use every pixel the display gives us, but we cannot hide the\nmenu on the left because it’s too important!!1!. Why don’t we let the user decide what is important or not? And with\nsome extra animation he will love our application even more!",[54,7064,7065,7066,7069,7070,7073],{},"Due to simple adding or removing the class ",[316,7067,7068],{},".hidden"," we can change the width of the menu container. The ",[372,7071,7072],{},"transition","\nproperty takes all the magic for us and animates the width change. Try to increase the duration and click ‘hide’ and\n‘show’ in the codepen before the animation time is over. Note how the animation stops immediately and returns to the\nprevious state as soon as you click again. Did you ever implemented something like this with JavaScript? Luckily I\ndidn’t.",[54,7075,7076,7077,7082],{},"As well as the above mentioned CSS 3 features, transitions are much more powerful\nand ",[61,7078,6703],{"href":7079,"rel":7080,"title":7081},"http://css-tricks.com/search-results/?q=transition",[65],"css-tricks.com | transition"," is a nice source\nto learn more and to play with advanced examples.",[54,7084,7085],{},"Thanks for reading! And hopefully we can enjoy the full power of CSS 3 as soon as possible throughout all modern\nbrowsers (even IE…).",[986,7087,5784],{},{"title":11,"searchDepth":12,"depth":12,"links":7089},[7090,7091,7092],{"id":6467,"depth":12,"text":6459},{"id":6709,"depth":12,"text":6710},{"id":7056,"depth":12,"text":7056},[998],"2013-06-26T11:51:48","https://synyx.de/blog/awesome-css-3-layouting/",{},"/blog/awesome-css-3-layouting",{"title":6410,"description":6419},"blog/awesome-css-3-layouting",[7101],"css3","At first let me ask you a few questions about developing web applications: How do you create multiple column layouts? How do you make it flexible? How do you solve…","Y2DiBD_KnuEqdChUKF_H_3Qaxz2XX93qcAd5uc647dY",{"id":7105,"title":7106,"author":7107,"body":7108,"category":7537,"date":7538,"description":7539,"extension":18,"link":7540,"meta":7541,"navigation":29,"path":7542,"seo":7543,"slug":7112,"stem":7545,"tags":7546,"teaser":7547,"__hash__":7548},"blog/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar-part-2.md","Visualize JavaScript code quality and code coverage with Sonar – part 2",[33],{"type":8,"value":7109,"toc":7533},[7110,7113,7123,7126,7132,7136,7139,7148,7151,7160,7163,7218,7221,7224,7227,7379,7382,7417,7420,7424,7427,7531],[45,7111,7106],{"id":7112},"visualize-javascript-code-quality-and-code-coverage-with-sonar-part-2",[54,7114,7115,7116,7122],{},"In\nmy ",[61,7117,7121],{"href":7118,"rel":7119,"title":7120},"http://blog.synyx.de/2012/08/visualize-javascript-code-quality-and-code-coverage-with-sonar/",[65],"Visualize JavaScript code quality and code coverage with Sonar","previous post","\nI wrote about the Sonar JavaScript-Plugin, JsTestDriver, jstd-maven-plugin and some problems with the configuration.\nMeanwhile we’ve got a working setup which I want to explain in this blog.",[54,7124,7125],{},"For the impatient ones among us, there is a sample project available on github:",[54,7127,7128],{},[61,7129,7130],{"href":7130,"rel":7131},"https://github.com/synyx/JavaJsSonarDemo",[65],[49,7133,7135],{"id":7134},"run-javascript-tests-wit-jstestdriver","Run JavaScript-Tests wit JsTestDriver",[54,7137,7138],{},"The first problem was, that the opened browser-tab by the jstd runner was not closed anymore. In the previous setup\njstd was configured to start the test server during the maven test goal. Now we have a jstd server instance running on a\nremote computer with already captured browsers. Therefore you just have to execute",[459,7140,7142],{"className":3905,"code":7141,"language":3907,"meta":11,"style":11},"java -jar JsTestDriver.jar --port 9876\n",[372,7143,7144],{"__ignoreMap":11},[467,7145,7146],{"class":469,"line":470},[467,7147,7141],{},[54,7149,7150],{},"in the terminal, start the browsers of your choice and load the url",[459,7152,7154],{"className":3905,"code":7153,"language":3907,"meta":11,"style":11},"http://localhost:9876/capture\n",[372,7155,7156],{"__ignoreMap":11},[467,7157,7158],{"class":469,"line":470},[467,7159,7153],{},[54,7161,7162],{},"Now the browsers will listen to the jstd server to run the tests. No more tabs will be opened which we would have to\nclose manually. This works pretty well, as long as you use the JsTestDriver.jar to run your tests from the terminal. But\nif you use jstd-maven-plugin you will get following error:",[459,7164,7166],{"className":3905,"code":7165,"language":3907,"meta":11,"style":11},"java.lang.RuntimeException: Connection error on : sun.net.www.protocol.http.HttpURLConnection:http://10.0.15.24:9876/fileSet\n at com.google.jstestdriver.HttpServer.post(HttpServer.java:96)\n at com.google.jstestdriver.FileUploader.determineServerFileSet(FileUploader.java:174)\n at com.google.jstestdriver.action.UploadAction.run(UploadAction.java:38)\n at com.google.jstestdriver.ActionRunner.runActions(ActionRunner.java:64)\n at com.google.jstestdriver.JsTestDriver.main(JsTestDriver.java:86)\nCaused by: java.io.IOException: Server returned HTTP response code: 405 for URL: http://10.0.15.24:9876/fileSet at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1403)\n at com.google.jstestdriver.HttpServer.post(HttpServer.java:92)\n ... 4 more\nUnexpected Runner Condition: Connection error on: sun.net.www.protocol.http.HttpURLConnection:http://10.0.15.24:9876/fileSet\n",[372,7167,7168,7173,7178,7183,7188,7193,7198,7203,7208,7213],{"__ignoreMap":11},[467,7169,7170],{"class":469,"line":470},[467,7171,7172],{},"java.lang.RuntimeException: Connection error on : sun.net.www.protocol.http.HttpURLConnection:http://10.0.15.24:9876/fileSet\n",[467,7174,7175],{"class":469,"line":12},[467,7176,7177],{}," at com.google.jstestdriver.HttpServer.post(HttpServer.java:96)\n",[467,7179,7180],{"class":469,"line":529},[467,7181,7182],{}," at com.google.jstestdriver.FileUploader.determineServerFileSet(FileUploader.java:174)\n",[467,7184,7185],{"class":469,"line":551},[467,7186,7187],{}," at com.google.jstestdriver.action.UploadAction.run(UploadAction.java:38)\n",[467,7189,7190],{"class":469,"line":583},[467,7191,7192],{}," at com.google.jstestdriver.ActionRunner.runActions(ActionRunner.java:64)\n",[467,7194,7195],{"class":469,"line":589},[467,7196,7197],{}," at com.google.jstestdriver.JsTestDriver.main(JsTestDriver.java:86)\n",[467,7199,7200],{"class":469,"line":599},[467,7201,7202],{},"Caused by: java.io.IOException: Server returned HTTP response code: 405 for URL: http://10.0.15.24:9876/fileSet at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1403)\n",[467,7204,7205],{"class":469,"line":609},[467,7206,7207],{}," at com.google.jstestdriver.HttpServer.post(HttpServer.java:92)\n",[467,7209,7210],{"class":469,"line":615},[467,7211,7212],{}," ... 4 more\n",[467,7214,7215],{"class":469,"line":773},[467,7216,7217],{},"Unexpected Runner Condition: Connection error on: sun.net.www.protocol.http.HttpURLConnection:http://10.0.15.24:9876/fileSet\n",[54,7219,7220],{},"The problem is that jstd-maven-plugin uses an older version of jstd. With the current version 1.3.5 everything works\nas expected. But how can we tell jstd-maven-plugin to use the new version of jstd?",[54,7222,7223],{},"Well, you can simply add a property \u003Cjstd.jar> to your pom.xml properties and point to the jar file.",[54,7225,7226],{},"Since jstd-maven-plugin has a dependeny to jstd 1.3.2 this old jar will still be in our classpath. So we have to\ntell jstd-maven-plugin to use jstd 1.3.5 instead of the outdated one.",[459,7228,7230],{"className":3905,"code":7229,"language":3907,"meta":11,"style":11},"\u003Cdependencies>\n ...\n \u003Cdependency>\n \u003CgroupId>com.google.jstestdriver\u003C/groupId>\n \u003CartifactId>jstestdriver\u003C/artifactId>\n \u003Cversion>1.3.5\u003C/version>\n \u003Cscope>test\u003C/scope>\n \u003C/dependency>\n ...\n\u003C/dependencies>\n\u003Cbuild>\n ...\n \u003CpluginManagement>\n \u003Cplugins>\n \u003Cplugin>\n \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n \u003Cversion>1.3.2.5\u003C/version>\n \u003Cdependencies>\n \u003Cdependency>\n \u003CgroupId>com.google.jstestdriver\u003C/groupId>\n \u003CartifactId>jstestdriver\u003C/artifactId>\n \u003Cversion>1.3.5\u003C/version>\n \u003C/dependency>\n \u003C/dependencies>\n \u003C/plugin>\n \u003C/plugins>\n \u003C/pluginManagement>\n ...\n\u003C/build>\n",[372,7231,7232,7237,7242,7247,7252,7257,7262,7267,7272,7276,7281,7286,7290,7295,7300,7305,7310,7315,7320,7325,7330,7335,7340,7345,7350,7355,7360,7365,7370,7374],{"__ignoreMap":11},[467,7233,7234],{"class":469,"line":470},[467,7235,7236],{},"\u003Cdependencies>\n",[467,7238,7239],{"class":469,"line":12},[467,7240,7241],{}," ...\n",[467,7243,7244],{"class":469,"line":529},[467,7245,7246],{}," \u003Cdependency>\n",[467,7248,7249],{"class":469,"line":551},[467,7250,7251],{}," \u003CgroupId>com.google.jstestdriver\u003C/groupId>\n",[467,7253,7254],{"class":469,"line":583},[467,7255,7256],{}," \u003CartifactId>jstestdriver\u003C/artifactId>\n",[467,7258,7259],{"class":469,"line":589},[467,7260,7261],{}," \u003Cversion>1.3.5\u003C/version>\n",[467,7263,7264],{"class":469,"line":599},[467,7265,7266],{}," \u003Cscope>test\u003C/scope>\n",[467,7268,7269],{"class":469,"line":609},[467,7270,7271],{}," \u003C/dependency>\n",[467,7273,7274],{"class":469,"line":615},[467,7275,7241],{},[467,7277,7278],{"class":469,"line":773},[467,7279,7280],{},"\u003C/dependencies>\n",[467,7282,7283],{"class":469,"line":778},[467,7284,7285],{},"\u003Cbuild>\n",[467,7287,7288],{"class":469,"line":794},[467,7289,7241],{},[467,7291,7292],{"class":469,"line":806},[467,7293,7294],{}," \u003CpluginManagement>\n",[467,7296,7297],{"class":469,"line":905},[467,7298,7299],{}," \u003Cplugins>\n",[467,7301,7302],{"class":469,"line":911},[467,7303,7304],{}," \u003Cplugin>\n",[467,7306,7307],{"class":469,"line":916},[467,7308,7309],{}," \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n",[467,7311,7312],{"class":469,"line":922},[467,7313,7314],{}," \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n",[467,7316,7317],{"class":469,"line":2290},[467,7318,7319],{}," \u003Cversion>1.3.2.5\u003C/version>\n",[467,7321,7322],{"class":469,"line":2300},[467,7323,7324],{}," \u003Cdependencies>\n",[467,7326,7327],{"class":469,"line":2306},[467,7328,7329],{}," \u003Cdependency>\n",[467,7331,7332],{"class":469,"line":2311},[467,7333,7334],{}," \u003CgroupId>com.google.jstestdriver\u003C/groupId>\n",[467,7336,7337],{"class":469,"line":6581},[467,7338,7339],{}," \u003CartifactId>jstestdriver\u003C/artifactId>\n",[467,7341,7342],{"class":469,"line":6587},[467,7343,7344],{}," \u003Cversion>1.3.5\u003C/version>\n",[467,7346,7347],{"class":469,"line":6593},[467,7348,7349],{}," \u003C/dependency>\n",[467,7351,7352],{"class":469,"line":6599},[467,7353,7354],{}," \u003C/dependencies>\n",[467,7356,7357],{"class":469,"line":6604},[467,7358,7359],{}," \u003C/plugin>\n",[467,7361,7362],{"class":469,"line":6610},[467,7363,7364],{}," \u003C/plugins>\n",[467,7366,7367],{"class":469,"line":6616},[467,7368,7369],{}," \u003C/pluginManagement>\n",[467,7371,7372],{"class":469,"line":6621},[467,7373,7241],{},[467,7375,7376],{"class":469,"line":6627},[467,7377,7378],{},"\u003C/build>\n",[54,7380,7381],{},"If you try to build the maven project now, you will notice that jstd 1.3.5 can’t be found in the maven central\nrepository, unfortunately. So you have to add the jar to your local maven repo or to your nexus. To add it to your local\nrepo execute this command in the terminal:",[459,7383,7385],{"className":3905,"code":7384,"language":3907,"meta":11,"style":11},"mvn install:install-file \\\n -Dfile=\u003Cpath.to.jstd.jar> \\\n -DgroupId=com.google.jstestdriver \\\n -DartifactId=jstestdriver \\\n -Dversion=1.3.5 \\\n -Dpackaging=jar\n",[372,7386,7387,7392,7397,7402,7407,7412],{"__ignoreMap":11},[467,7388,7389],{"class":469,"line":470},[467,7390,7391],{},"mvn install:install-file \\\n",[467,7393,7394],{"class":469,"line":12},[467,7395,7396],{}," -Dfile=\u003Cpath.to.jstd.jar> \\\n",[467,7398,7399],{"class":469,"line":529},[467,7400,7401],{}," -DgroupId=com.google.jstestdriver \\\n",[467,7403,7404],{"class":469,"line":551},[467,7405,7406],{}," -DartifactId=jstestdriver \\\n",[467,7408,7409],{"class":469,"line":583},[467,7410,7411],{}," -Dversion=1.3.5 \\\n",[467,7413,7414],{"class":469,"line":589},[467,7415,7416],{}," -Dpackaging=jar\n",[54,7418,7419],{},"Now the JavaScript-Tests are automatically run by JsTestDriver during the maven build.",[49,7421,7423],{"id":7422},"analyse-java-and-javascript-sources-with-sonar","Analyse Java and JavaScript sources with Sonar",[54,7425,7426],{},"The second problem I mentioned in my previous post was the specification of the sourceDirectory for Sonar to be able to\nfetch the JavaScript sources. Hereby the java source directory will be overridden, of course, and the maven build will\nfail. I solved this with a maven profile which sets the source directory, the language that sonar should analyse and a\nbranch name for the JavaScript analysis (otherwise the Java analysis will be overridden). Furthermore you have to tell\nmaven to skip the java tests since the sources cannot be found anymore due the sourceDirectory change.",[459,7428,7430],{"className":3905,"code":7429,"language":3907,"meta":11,"style":11},"\u003Cproperties>\n ...\n \u003CsrcDir>src/main/java\u003C/srcDir>\n ...\n\u003C/properties>\n\u003Cbuild>\n ...\n \u003CsourceDirectory>${srcDir}\u003C/sourceDirectory>\n ...\n\u003C/build>\n\u003Cprofiles>\n \u003Cprofile>\n \u003Cid>sonarJsEnabled\u003C/id>\n \u003Cproperties>\n \u003CsrcDir>src/main/webapp/js\u003C/srcDir>\n \u003Cmaven.test.skip>true\u003C/maven.test.skip>\n \u003Csonar.language>js\u003C/sonar.language>\n \u003Csonar.branch>js\u003C/sonar.branch>\n \u003C/properties>\n \u003C/profile>\n\u003C/profiles>\n",[372,7431,7432,7437,7441,7446,7450,7455,7459,7463,7468,7472,7476,7481,7486,7491,7496,7501,7506,7511,7516,7521,7526],{"__ignoreMap":11},[467,7433,7434],{"class":469,"line":470},[467,7435,7436],{},"\u003Cproperties>\n",[467,7438,7439],{"class":469,"line":12},[467,7440,7241],{},[467,7442,7443],{"class":469,"line":529},[467,7444,7445],{}," \u003CsrcDir>src/main/java\u003C/srcDir>\n",[467,7447,7448],{"class":469,"line":551},[467,7449,7241],{},[467,7451,7452],{"class":469,"line":583},[467,7453,7454],{},"\u003C/properties>\n",[467,7456,7457],{"class":469,"line":589},[467,7458,7285],{},[467,7460,7461],{"class":469,"line":599},[467,7462,7241],{},[467,7464,7465],{"class":469,"line":609},[467,7466,7467],{}," \u003CsourceDirectory>${srcDir}\u003C/sourceDirectory>\n",[467,7469,7470],{"class":469,"line":615},[467,7471,7241],{},[467,7473,7474],{"class":469,"line":773},[467,7475,7378],{},[467,7477,7478],{"class":469,"line":778},[467,7479,7480],{},"\u003Cprofiles>\n",[467,7482,7483],{"class":469,"line":794},[467,7484,7485],{}," \u003Cprofile>\n",[467,7487,7488],{"class":469,"line":806},[467,7489,7490],{}," \u003Cid>sonarJsEnabled\u003C/id>\n",[467,7492,7493],{"class":469,"line":905},[467,7494,7495],{}," \u003Cproperties>\n",[467,7497,7498],{"class":469,"line":911},[467,7499,7500],{}," \u003CsrcDir>src/main/webapp/js\u003C/srcDir>\n",[467,7502,7503],{"class":469,"line":916},[467,7504,7505],{}," \u003Cmaven.test.skip>true\u003C/maven.test.skip>\n",[467,7507,7508],{"class":469,"line":922},[467,7509,7510],{}," \u003Csonar.language>js\u003C/sonar.language>\n",[467,7512,7513],{"class":469,"line":2290},[467,7514,7515],{}," \u003Csonar.branch>js\u003C/sonar.branch>\n",[467,7517,7518],{"class":469,"line":2300},[467,7519,7520],{}," \u003C/properties>\n",[467,7522,7523],{"class":469,"line":2306},[467,7524,7525],{}," \u003C/profile>\n",[467,7527,7528],{"class":469,"line":2311},[467,7529,7530],{},"\u003C/profiles>\n",[986,7532,5784],{},{"title":11,"searchDepth":12,"depth":12,"links":7534},[7535,7536],{"id":7134,"depth":12,"text":7135},{"id":7422,"depth":12,"text":7423},[998],"2012-11-22T17:00:57","In\\nmy previous post\\nI wrote about the Sonar JavaScript-Plugin, JsTestDriver, jstd-maven-plugin and some problems with the configuration.\\nMeanwhile we’ve got a working setup which I want to explain in this blog.","https://synyx.de/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar-part-2/",{},"/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar-part-2",{"title":7106,"description":7544},"In\nmy previous post\nI wrote about the Sonar JavaScript-Plugin, JsTestDriver, jstd-maven-plugin and some problems with the configuration.\nMeanwhile we’ve got a working setup which I want to explain in this blog.","blog/visualize-javascript-code-quality-and-code-coverage-with-sonar-part-2",[],"In my previous post I wrote about the Sonar JavaScript-Plugin, JsTestDriver, jstd-maven-plugin and some problems with the configuration. Meanwhile we’ve got a working setup which I want to explain in…","dZPmT4RqM5AjLvjCHcsD4nNol7MHvdL4Dy92jpUsG_g",{"id":7550,"title":7120,"author":7551,"body":7552,"category":8187,"date":8190,"description":8191,"extension":18,"link":8192,"meta":8193,"navigation":29,"path":8194,"seo":8195,"slug":7556,"stem":8196,"tags":8197,"teaser":8198,"__hash__":8199},"blog/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar.md",[33],{"type":8,"value":7553,"toc":8178},[7554,7557,7560,7566,7569,7575,7589,7592,7596,7645,7648,7662,7665,7679,7690,7693,7778,7781,7784,7786,7789,7792,7795,7798,7808,7810,7813,7816,7859,7862,7896,7899,7991,8000,8003,8006,8009,8014,8018,8025,8029,8036,8059,8063,8066,8075,8078,8087,8098,8107,8111,8114,8117,8125,8135,8139,8144,8150,8155,8160,8176],[45,7555,7120],{"id":7556},"visualize-javascript-code-quality-and-code-coverage-with-sonar",[54,7558,7559],{},"It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant\nscript language. In the past we have used JavaScript mostly for eye-candy and form validation. Recently we have been\nasked more often to implement complex user interfaces with trees, sortable tables and things like that. So we decided to\nrely more on JavaScript to improve the feedback of the website to user actions.",[54,7561,7562,7563],{},"The first question that came up was: ",[436,7564,7565],{},"How to develop test driven with JavaScript?",[54,7567,7568],{},"We decided to use Jasmine, a behaviour driven development framework which tests can be run headless in a Maven build for\nexample.",[54,7570,7571,7572],{},"The second question was: ",[436,7573,7574],{},"How to visualise code coverage and code quality?",[54,7576,7577,7578,7584,7585,7588],{},"The tool Sonar has been proven to be useful in our Java projects in the past. So the first I was searching for was the\nJavaScript Plugin for Sonar. It can be\ndownloaded ",[61,7579,7583],{"href":7580,"rel":7581,"title":7582},"https://web.archive.org/web/20150530172810/http://docs.codehaus.org/display/SONAR/JavaScript+Plugin",[65],"Sonar JavaScript Plugin","here","\nand was luckily updated to version 1.0 recently with a bunch of new metrics like “",[316,7586,7587],{},"avoid usage of == and !=","”.",[54,7590,7591],{},"Unfortunately this plugin only supports JsTestDriver for code coverage and other test metrics. However, Jasmine support\nis on the Roadmap and I’m looking forward to see the next release of the JavaScript Plugin. But at the moment I had to\nintegrate our Jasmine and jasmine-jquery tests with JsTestDriver, the JavaScript Plugin of Sonar and an automated maven\nbuild.",[49,7593,7595],{"id":7594},"used-technologies","Used technologies",[309,7597,7598,7605,7612,7619,7626,7633,7640],{},[312,7599,7600],{},[61,7601,7604],{"href":7602,"rel":7603,"title":7604},"https://maven.apache.org/",[65],"Maven",[312,7606,7607],{},[61,7608,7611],{"href":7609,"rel":7610,"title":7611},"https://github.com/jasmine/jasmine",[65],"Jasmine",[312,7613,7614],{},[61,7615,7618],{"href":7616,"rel":7617,"title":7618},"https://github.com/ibolmo/jasmine-jstd-adapter",[65],"jasmine-jstd-adapter",[312,7620,7621],{},[61,7622,7625],{"href":7623,"rel":7624,"title":7625},"https://code.google.com/p/js-test-driver/",[65],"JsTestDriver",[312,7627,7628],{},[61,7629,7632],{"href":7630,"rel":7631,"title":7632},"https://code.google.com/p/jstd-maven-plugin/",[65],"jstd-maven-plugin",[312,7634,7635],{},[61,7636,7639],{"href":7637,"rel":7638,"title":7639},"https://sonarsource.com/",[65],"Sonar",[312,7641,7642],{},[61,7643,7582],{"href":7580,"rel":7644,"title":7582},[65],[49,7646,7625],{"id":7647},"jstestdriver",[54,7649,7650,7651,7655,7656,675],{},"JsTestDriver is a test runner designed by Google and can be\ndownloaded ",[61,7652,7583],{"href":7653,"rel":7654,"title":7625},"http://code.google.com/p/js-test-driver/downloads/",[65],". If you don’t know JSTestDriver\nyet, you maybe want to run over\nit’s ",[61,7657,7661],{"href":7658,"rel":7659,"title":7660},"http://code.google.com/p/js-test-driver/w/list",[65],"JsTestDriver Documentation","documentation",[54,7663,7664],{},"Some advantages of JsTestDriver:",[309,7666,7667,7670,7673,7676],{},[312,7668,7669],{},"Eclipse and IntelliJ integration",[312,7671,7672],{},"Maven Plugin",[312,7674,7675],{},"parallel test executions across browsers (local or remote)",[312,7677,7678],{},"code coverage",[54,7680,7681,7682,7685,7686,7689],{},"In order to run JsTestDriver you have to create a config file. By default you have to name it ",[316,7683,7684],{},"jsTestDriver.conf"," and\nyou have to save it in ",[316,7687,7688],{},"src/test/resources",". If you want to choose another filename or path remember to define these in\nthe Maven plugin later.",[54,7691,7692],{},"In the config file you have to define following flags (Be sure that there are no whitespaces in front of the keywords):",[459,7694,7696],{"className":3905,"code":7695,"language":3907,"meta":11,"style":11},"server: http://localhost:9876\nload:\n # jasmine dependency\n - lib/jasmine.js\n # to make our jasmine tests work within jstd\n # (must be included after jasmine.js)\n - lib/jasmineAdapter.js\n # models, views and other stuff\n - src/main/js/*.js\ntest:\n # load all test files\n - src/test/js/*Test.js\nplugin:\n - name: \"coverage\"\n jar: \"lib/coverage-1.3.4.b.jar\"\n module: \"com.google.jstestdriver.coverage.CoverageModule\"\n",[372,7697,7698,7703,7708,7713,7718,7723,7728,7733,7738,7743,7748,7753,7758,7763,7768,7773],{"__ignoreMap":11},[467,7699,7700],{"class":469,"line":470},[467,7701,7702],{},"server: http://localhost:9876\n",[467,7704,7705],{"class":469,"line":12},[467,7706,7707],{},"load:\n",[467,7709,7710],{"class":469,"line":529},[467,7711,7712],{}," # jasmine dependency\n",[467,7714,7715],{"class":469,"line":551},[467,7716,7717],{}," - lib/jasmine.js\n",[467,7719,7720],{"class":469,"line":583},[467,7721,7722],{}," # to make our jasmine tests work within jstd\n",[467,7724,7725],{"class":469,"line":589},[467,7726,7727],{}," # (must be included after jasmine.js)\n",[467,7729,7730],{"class":469,"line":599},[467,7731,7732],{}," - lib/jasmineAdapter.js\n",[467,7734,7735],{"class":469,"line":609},[467,7736,7737],{}," # models, views and other stuff\n",[467,7739,7740],{"class":469,"line":615},[467,7741,7742],{}," - src/main/js/*.js\n",[467,7744,7745],{"class":469,"line":773},[467,7746,7747],{},"test:\n",[467,7749,7750],{"class":469,"line":778},[467,7751,7752],{}," # load all test files\n",[467,7754,7755],{"class":469,"line":794},[467,7756,7757],{}," - src/test/js/*Test.js\n",[467,7759,7760],{"class":469,"line":806},[467,7761,7762],{},"plugin:\n",[467,7764,7765],{"class":469,"line":905},[467,7766,7767],{}," - name: \"coverage\"\n",[467,7769,7770],{"class":469,"line":911},[467,7771,7772],{}," jar: \"lib/coverage-1.3.4.b.jar\"\n",[467,7774,7775],{"class":469,"line":916},[467,7776,7777],{}," module: \"com.google.jstestdriver.coverage.CoverageModule\"\n",[3898,7779,7780],{"id":7780},"server",[54,7782,7783],{},"JsTestDriver starts it’s own server and generates a HTML page that can be captured by various browsers.",[3898,7785,6058],{"id":6058},[54,7787,7788],{},"Define all needed JavaScript files like your models and views and so on. You can use a wildcard * to include all files\nwithin the specified directory. Note that it could be relevant to load your files in an correct order since a normal\nHTML page will be created and some dependencies have to be considered. The load sequence will be alphabetically if the\nwildcard is used.",[3898,7790,7791],{"id":7791},"test",[54,7793,7794],{},"simply attaches the test files to the created HTML page.",[3898,7796,7797],{"id":7797},"plugin",[54,7799,7800,7801,7807],{},"to be able to see the code coverage report in Sonar you have to download\nthe ",[61,7802,7806],{"href":7803,"rel":7804,"title":7805},"http://code.google.com/p/js-test-driver/downloads/list",[65],"jstd coverage plugin","coverage plugin"," and save it\nsomewhere as you wish. Just remember to add the path to the plugin jar flag as shown in the listing above.",[49,7809,7632],{"id":7632},[54,7811,7812],{},"Before we can see the Sonar report about the code quality and code coverage we have to configure maven to run jstd.",[54,7814,7815],{},"Unfortunately, the jstd-maven-plugin is not available at the Maven Central Repository. Therefore we have to add a new\nrepository and pluginRepository to our pom.xml:",[459,7817,7819],{"className":3905,"code":7818,"language":3907,"meta":11,"style":11},"\u003Crepository>\n \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n\u003C/repository>\n\u003CpluginRepository>\n \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n\u003C/pluginRepository>\n",[372,7820,7821,7826,7831,7836,7841,7846,7850,7854],{"__ignoreMap":11},[467,7822,7823],{"class":469,"line":470},[467,7824,7825],{},"\u003Crepository>\n",[467,7827,7828],{"class":469,"line":12},[467,7829,7830],{}," \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n",[467,7832,7833],{"class":469,"line":529},[467,7834,7835],{}," \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n",[467,7837,7838],{"class":469,"line":551},[467,7839,7840],{},"\u003C/repository>\n",[467,7842,7843],{"class":469,"line":583},[467,7844,7845],{},"\u003CpluginRepository>\n",[467,7847,7848],{"class":469,"line":589},[467,7849,7830],{},[467,7851,7852],{"class":469,"line":599},[467,7853,7835],{},[467,7855,7856],{"class":469,"line":609},[467,7857,7858],{},"\u003C/pluginRepository>\n",[54,7860,7861],{},"After that maven should be able to fetch the jstd-maven-plugin artifact:",[459,7863,7865],{"className":3905,"code":7864,"language":3907,"meta":11,"style":11},"\u003Cdependency>\n \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n \u003Cversion>1.3.2.5\u003C/version>\n \u003Cscope>test\u003C/scope>\n\u003C/dependency>\n",[372,7866,7867,7872,7877,7882,7887,7891],{"__ignoreMap":11},[467,7868,7869],{"class":469,"line":470},[467,7870,7871],{},"\u003Cdependency>\n",[467,7873,7874],{"class":469,"line":12},[467,7875,7876],{}," \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n",[467,7878,7879],{"class":469,"line":529},[467,7880,7881],{}," \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n",[467,7883,7884],{"class":469,"line":551},[467,7885,7886],{}," \u003Cversion>1.3.2.5\u003C/version>\n",[467,7888,7889],{"class":469,"line":583},[467,7890,7266],{},[467,7892,7893],{"class":469,"line":589},[467,7894,7895],{},"\u003C/dependency>\n",[54,7897,7898],{},"To run our tests with a maven build we need the jstd-maven-plugin as a Maven plugin:",[459,7900,7902],{"className":3905,"code":7901,"language":3907,"meta":11,"style":11},"\u003Cplugin>\n \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n \u003Cversion>1.3.2.5\u003C/version>\n \u003Cconfiguration>\n \u003Cbrowser>firefox\u003C/browser>\n \u003Cport>9876\u003C/port>\n \u003CtestOutput>target/jstestdriver\u003C/testOutput>\n \u003C/configuration>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cid>run-tests\u003C/id>\n \u003Cgoals>\n \u003Cgoal>test\u003C/goal>\n \u003C/goals>\n \u003C/execution>\n \u003C/executions>\n\u003C/plugin>\n",[372,7903,7904,7909,7913,7917,7921,7926,7931,7936,7941,7946,7951,7956,7961,7966,7971,7976,7981,7986],{"__ignoreMap":11},[467,7905,7906],{"class":469,"line":470},[467,7907,7908],{},"\u003Cplugin>\n",[467,7910,7911],{"class":469,"line":12},[467,7912,7876],{},[467,7914,7915],{"class":469,"line":529},[467,7916,7881],{},[467,7918,7919],{"class":469,"line":551},[467,7920,7886],{},[467,7922,7923],{"class":469,"line":583},[467,7924,7925],{}," \u003Cconfiguration>\n",[467,7927,7928],{"class":469,"line":589},[467,7929,7930],{}," \u003Cbrowser>firefox\u003C/browser>\n",[467,7932,7933],{"class":469,"line":599},[467,7934,7935],{}," \u003Cport>9876\u003C/port>\n",[467,7937,7938],{"class":469,"line":609},[467,7939,7940],{}," \u003CtestOutput>target/jstestdriver\u003C/testOutput>\n",[467,7942,7943],{"class":469,"line":615},[467,7944,7945],{}," \u003C/configuration>\n",[467,7947,7948],{"class":469,"line":773},[467,7949,7950],{}," \u003Cexecutions>\n",[467,7952,7953],{"class":469,"line":778},[467,7954,7955],{}," \u003Cexecution>\n",[467,7957,7958],{"class":469,"line":794},[467,7959,7960],{}," \u003Cid>run-tests\u003C/id>\n",[467,7962,7963],{"class":469,"line":806},[467,7964,7965],{}," \u003Cgoals>\n",[467,7967,7968],{"class":469,"line":905},[467,7969,7970],{}," \u003Cgoal>test\u003C/goal>\n",[467,7972,7973],{"class":469,"line":911},[467,7974,7975],{}," \u003C/goals>\n",[467,7977,7978],{"class":469,"line":916},[467,7979,7980],{}," \u003C/execution>\n",[467,7982,7983],{"class":469,"line":922},[467,7984,7985],{}," \u003C/executions>\n",[467,7987,7988],{"class":469,"line":2290},[467,7989,7990],{},"\u003C/plugin>\n",[54,7992,7993,7994,7999],{},"Three configuration flags are mandatory. More command line flags can be found in\nthe ",[61,7995,7661],{"href":7996,"rel":7997,"title":7998},"http://code.google.com/p/js-test-driver/wiki/CommandLineFlags",[65],"jstd-maven-plugin documentation"," of\njstd.",[3898,8001,8002],{"id":8002},"browser",[54,8004,8005],{},"a comma separated list of browsers (more exactly the path to the specific browser) that should be used for the tests",[3898,8007,8008],{"id":8008},"port",[54,8010,8011,8012],{},"the port that is set in ",[316,8013,7684],{},[3898,8015,8017],{"id":8016},"testoutput","testOutput",[54,8019,8020,8021,8024],{},"This specifies the directory where the code coverage reports (needed for Sonar) will be saved. The default directory for\nsonar is ",[316,8022,8023],{},"target/jstestdriver",", so remember to configure sonar accordingly, if you choose another directory.",[49,8026,8028],{"id":8027},"set-the-sourcedirectory-in-the-pomxml","Set the sourceDirectory in the pom.xml",[54,8030,8031,8032,8035],{},"In order for Sonar to be able to analyze the JavaScript code and to visualize the reports, we must add the path to the\nsource code which is ",[316,8033,8034],{},"src/main/js"," in our case.",[459,8037,8039],{"className":3905,"code":8038,"language":3907,"meta":11,"style":11},"\u003Cbuild>\n \u003CsourceDirectory>src/main/js\u003C/sourceDirectory>\n \u003C!-- ... -->\n\u003C/build>\n",[372,8040,8041,8045,8050,8055],{"__ignoreMap":11},[467,8042,8043],{"class":469,"line":470},[467,8044,7285],{},[467,8046,8047],{"class":469,"line":12},[467,8048,8049],{}," \u003CsourceDirectory>src/main/js\u003C/sourceDirectory>\n",[467,8051,8052],{"class":469,"line":529},[467,8053,8054],{}," \u003C!-- ... -->\n",[467,8056,8057],{"class":469,"line":551},[467,8058,7378],{},[49,8060,8062],{"id":8061},"run-the-tests-and-the-analysis","Run the tests and the analysis",[54,8064,8065],{},"Everything should be configured correctly now. So just start the maven build:",[459,8067,8069],{"className":3905,"code":8068,"language":3907,"meta":11,"style":11},"mvn jstd:test\n",[372,8070,8071],{"__ignoreMap":11},[467,8072,8073],{"class":469,"line":470},[467,8074,8068],{},[54,8076,8077],{},"JsTestDriver opens the defined browsers, runs all tests and generates the code coverage report. After that we have to\nstart the sonar build:",[459,8079,8081],{"className":3905,"code":8080,"language":3907,"meta":11,"style":11},"mvn sonar:sonar -Dsonar.language=js -Dsonar.branch=js\n",[372,8082,8083],{"__ignoreMap":11},[467,8084,8085],{"class":469,"line":470},[467,8086,8080],{},[54,8088,8089,8090,8093,8094,8097],{},"To tell sonar to analyze a JavaScript project the ",[316,8091,8092],{},"sonar.language"," property is essential. If the same project should be\nanalyzed as a Java project you may want to add a branch with the property ",[316,8095,8096],{},"sonar.branch",". Otherwise the previous values\nwill be overridden with this JavaScript analysis.",[54,8099,8100,91,8104],{},[87,8101],{"alt":8102,"src":8103},"\"JavaScript Plugin - Sonar\"","https://media.synyx.de/uploads//2012/08/js_sonar_01.png",[87,8105],{"alt":8102,"src":8106},"https://media.synyx.de/uploads//2012/08/js_sonar_02.png",[49,8108,8110],{"id":8109},"problems","Problems",[54,8112,8113],{},"An annoying problem is running the tests with real browsers like Firefox and Chrome. The maven build automatically\nstarts the browser and also closes it after the tests are finished. But Firefox is not correctly closed by jstd somehow…\nso the next test run fails because Firefox opens a dialog which must be closed manually. The maven build is deadlocked\nand you have to abort and rerun it…",[54,8115,8116],{},"So maybe a running Firefox process would be a workaround, I thought. Well, it kinda worked but the opened tab was not\nclosed anymore (tested on Linux and Windows). Each new test run opened a new tab and after a handful testruns the tests\nfailed because of some strange error. Closing tabs manually solved this, however. Same problem occurred with Chrome (\nVersion 22.0.1201.0 dev).",[54,8118,8119,8120,8124],{},"On one hand it is really nice to run the tests in all desired browsers, on the other hand closing tabs/browsers manually\nmakes it impossible to automate this process. So I’m really looking forward to Jasmine support of the sonar JavaScript\nPlugin to run headless tests as a maven build, just like jasmine-maven-plugin. A quick google search links to this\nproject: ",[61,8121,8122],{"href":8122,"rel":8123},"https://github.com/jwark/jstd-standalone-headless",[65]," Maybe this could be a solution… Any information is welcome,\nso if you have a working setup, please let me know.",[54,8126,8127,8128,8131,8132,8134],{},"Another problem surely is the mandatory specification of the ",[316,8129,8130],{},"sourceDirectory"," to be able to see the metrics in Sonar.\nUsually you will have a Java project with some JavaScript code. Therefore you certainly can’t pinpoint to ",[316,8133,8034],{},"\nas source directory of the project, for example. Further information is appreciated, again 🙂",[49,8136,8138],{"id":8137},"todo","Todo",[54,8140,8141],{},[436,8142,8143],{},"automate the analysis within a Jenkins build process",[54,8145,8146,8147],{},"— ",[316,8148,8149],{},"maybe jstd tests can be run headless?",[54,8151,8146,8152],{},[316,8153,8154],{},"maybe maven profiles could be used to prevent the sourceDirectory declaration?",[54,8156,8157],{},[436,8158,8159],{},"require.js integration in jstd-unit-tests",[54,8161,8146,8162],{},[316,8163,8164,8170,8171,8175],{},[61,8165,8169],{"href":8166,"rel":8167,"title":8168},"http://requirejs.org/",[65],"require.js","RequireJS"," is a JavaScript file and module loader\nand ",[61,8172,747],{"href":8173,"rel":8174},"https://github.com/podefr/jasmine-reqjs-jstd/wiki/how-to-setup-requirejs---jasmine---jsTestDriver",[65]," should be\na good starting point.",[986,8177,5784],{},{"title":11,"searchDepth":12,"depth":12,"links":8179},[8180,8181,8182,8183,8184,8185,8186],{"id":7594,"depth":12,"text":7595},{"id":7647,"depth":12,"text":7625},{"id":7632,"depth":12,"text":7632},{"id":8027,"depth":12,"text":8028},{"id":8061,"depth":12,"text":8062},{"id":8109,"depth":12,"text":8110},{"id":8137,"depth":12,"text":8138},[998,8188,8189],"open-source-blog","tutorial","2012-08-08T13:43:42","It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant\\nscript language. In the past we have used JavaScript mostly for eye-candy and form validation. Recently we have been\\nasked more often to implement complex user interfaces with trees, sortable tables and things like that. So we decided to\\nrely more on JavaScript to improve the feedback of the website to user actions.","https://synyx.de/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar/",{},"/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar",{"title":7120,"description":7559},"blog/visualize-javascript-code-quality-and-code-coverage-with-sonar",[],"It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant script language. In the past we have used JavaScript…","mrqNn4n0IH3ZbUdfxIC3kySm1Vw9wOmXI1qcURunod0",["Reactive",8201],{"$scookieConsent":8202,"$ssite-config":8204},{"functional":8203,"analytics":8203},false,{"_priority":8205,"env":8209,"name":8210,"url":8211},{"name":8206,"env":8207,"url":8208},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",8214],{"author-seber":-1,"roughlyFilteredArticles":-1},"/blog/author/seber"]