<?xml version="1.0" encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="https://raibledesigns.com/roller-ui/styles/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom">
    <title type="html">Raible Designs</title>
    <subtitle type="html">Raible Designs is an Enterprise Open Source Consulting company. We specialize in UI and Full Stack Architectures using HTML5, CSS, JavaScript and Java. We love HTML5, Angular, Bootstrap, Spring Boot, and especially JHipster.</subtitle>
    <id>https://raibledesigns.com/rd/feed/entries/atom</id>
            <link rel="self" type="application/atom+xml" href="https://raibledesigns.com/rd/feed/entries/atom?tags=angularjs" />
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/" />
        <updated>2026-03-30T03:31:45-06:00</updated>
    <generator uri="http://roller.apache.org" version="5.0.3 (1388864191739:dave)">Apache Roller (incubating)</generator>
        <entry>
        <id>https://raibledesigns.com/rd/entry/life_as_an_open_source</id>
        <title type="html">Life as an Open Source Developer</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/life_as_an_open_source"/>
        <published>2016-11-03T16:29:01-06:00</published>
        <updated>2016-11-03T22:30:05-06:00</updated> 
        <category term="/Open Source" label="Open Source" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angular2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="opensource" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="stormpath" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springboot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="github" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;
It&apos;s been a little over a month since I started my &lt;a href=&quot;https://raibledesigns.com/rd/entry/life_update_a_summer_to&quot;&gt;
new gig at Stormpath&lt;/a&gt;. I gotta say, life is great as an open source developer! Yes, I did start working for them as a consultant in April, so it&apos;s not a huge change for me.
However, I only recently realized I haven&apos;t written a &lt;em&gt;single line&lt;/em&gt; of proprietary code the entire time.
My &lt;a href=&quot;https://github.com/mraible&quot;&gt;GitHub contributions&lt;/a&gt; look pretty good this year. They&apos;re nothing like &lt;a href=&quot;https://github.com/mojavelinux&quot;&gt;@mojavelinux&lt;/a&gt;, 
or &lt;a href=&quot;https://github.com/dsyer&quot;&gt;@dsyer&lt;/a&gt;, but I&apos;ll get there. &lt;img src=&quot;//raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;)&quot; title=&quot;;)&quot;&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://github.com/mraible&quot; title=&quot;GitHub Profile - November 3, 2016&quot;&gt;
    &lt;img src=&quot;https://c7.staticflickr.com/6/5703/30128917414_8e7c7a8e57_z.jpg&quot; width=&quot;640&quot; alt=&quot;GitHub Profile - November 3, 2016&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;It&apos;s also been a bit more stress than I&apos;m used to. I think this comes from a couple things: 1) turning my hobby into my job and 2)
    I&apos;ve set a lot of high expectations for myself. As a developer evangelist, I get to create my own job. That means I can
    speak at the conferences I want to, write the code I want to, create the blog posts I want to, and everything else in between.
&lt;/p&gt;
&lt;p&gt;At the end of September, I finished &lt;a href=&quot;http://www.jhipster-book.com/#!/news/entry/book-updated-for-jhipster-3-and-jhipster-gets-dirty&quot;&gt;updating the 
JHipster Mini-Book for JHipster 3.x&lt;/a&gt;. It&apos;s gone through tech editing and it&apos;s being copy-edited right now. I hope to release it within a week. 
&lt;/p&gt;
&lt;p&gt;In early October, I said I&apos;d commit to writing one blog post per week, develop a JHipster module for Stormpath, and help get their 
    Angular 2 support good enough for an alpha release. I&apos;m happy to report I&apos;ve been able to accomplish most of these and I hope to show off
    our Angular 2 support soon.&lt;/p&gt;
&lt;p&gt;
I then channeled my efforts into integrating Stormpath&apos;s Java SDK with their AngularJS directives. You can read about how I did that in
&lt;a href=&quot;https://stormpath.com/blog/angularjs-spring-boot-tutorial&quot;&gt;Get Started with AngularJS, Spring Boot, and Stormpath&lt;/a&gt;.
Unlike &lt;a href=&quot;https://raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;my previous AngularJS tutorial&lt;/a&gt;, this one connects to a 
backend and shows how to communicate with Spring Boot cross-domain.
&lt;/p&gt;
&lt;p&gt;If you like to read code more than words, you can look at the &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example&quot;&gt;example project&apos;s commits
on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an AngularJS UI: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/652ee29d9a002f5d437d356481809fe74114fe7e&quot;&gt;search&lt;/a&gt; and &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/9a06e9071d5db9710c3a8555c0dfe81c752f2242&quot;&gt;edit&lt;/a&gt; features&lt;/li&gt;
&lt;li&gt;Create a Spring Boot app with Stormpath: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/740ed84ccb16c94bfb6451c453c325b7f86fa870&quot;&gt;app from start.stormpath.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Develop an API to CRUD people with Spring Data REST: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/f223f26dba108e864cec271b32b856423bc12d74&quot;&gt;/api/people&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Integrate AngularJS and Spring Boot apps: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/88f43da9fc14bb59e6d1b7f36f658730029b4bd7&quot;&gt;cross-domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Integrate Stormpath into AngularJS for login, registration and forgot password: &lt;a href=&quot;https://github.com/stormpath/angularjs-spring-boot-stormpath-example/commit/2eee2b677237f793bf4ff25b6705d9c72efc984d&quot;&gt;Stormpath Angular SDK&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Last week, I released a &lt;a href=&quot;https://jhipster.github.io/modules/marketplace/#/details/generator-jhipster-stormpath&quot;&gt;JHipster module
 that integrates Stormpath&lt;/a&gt;. This exercise was good because I was able to identify some gaps in Stormpath&apos;s SDKs &lt;em&gt;and&lt;/em&gt; fix them.
Getting something to work made me feel good; having the ability to improve the developer experience
was even better! Of course, &lt;a href=&quot;https://stormpath.com/blog/stormpath-jhipster-application&quot;&gt;I blogged about what I learned&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
This week, I edited and code reviewed some posts from Karl Penzhorn on &lt;a href=&quot;https://stormpath.com/blog/crud-application-react-spring-boot-user-authentication&quot;&gt;
React with Spring Boot&lt;/a&gt; and using &lt;a href=&quot;https://stormpath.com/blog/optimize-react-webpack&quot;&gt;webpack with React&lt;/a&gt;. I also got to &lt;a href=&quot;https://github.com/mattlewis92/generator-angular-library/issues/14&quot;&gt;
bang my head against the wall&lt;/a&gt; writing Angular 2 tests. If you&apos;re writing a module for Angular 2, &lt;a href=&quot;https://www.npmjs.com/package/generator-angular2-module&quot;&gt;
generator-angular2-module&lt;/a&gt; provides a nice starting point.&lt;/p&gt;
&lt;p&gt;Last, but certainly not least, I&apos;ll be speaking at a few events about Microservices, JHipster, Angular 2 and Stormpath in the near feature. &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://nofluffjuststuff.com/conference/denver/2016/11/session?id=38028&quot;&gt;Rocky Mountain Software Symposium&lt;/a&gt;, November 19&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://therichwebexperience.com/conference/clearwater/2016/12/speakers/matt_raible&quot;&gt;The Rich Web Experience&lt;/a&gt;, December 9&lt;/li&gt;
    &lt;li&gt;A joint talk at &lt;a href=&quot;http://www.meetup.com/DenverJavaUsersGroup/events/231602438/&quot;&gt;Denver JUG&lt;/a&gt; with the infamous &lt;a href=&quot;https://twitter.com/starbuxman&quot;&gt;Josh Long&lt;/a&gt;, December 14&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions about developer evangelism, the technologies I mentioned in this post, or Stormpath, please let me know. Otherwise, I hope to see you on the road soon!
    &lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/devoxx_france_2016_springtime_in</id>
        <title type="html">Devoxx France 2016: Springtime in Paris</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/devoxx_france_2016_springtime_in"/>
        <published>2016-04-26T07:13:18-06:00</published>
        <updated>2016-04-26T13:13:18-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="infoq" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="devoxxfr" scheme="http://roller.apache.org/ns/tags/" />
        <category term="paris" scheme="http://roller.apache.org/ns/tags/" />
        <category term="asciidoctor" scheme="http://roller.apache.org/ns/tags/" />
        <category term="devoxx" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="travel" scheme="http://roller.apache.org/ns/tags/" />
        <category term="france" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angular2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;I had the good fortune to visit Paris last week for &lt;a href=&quot;http://www.devoxx.fr/&quot;&gt;Devoxx France&lt;/a&gt;. When traveling
    to conferences in exotic locations,
    I like to bring a travel partner. This time, I asked my daughter, Abbie, to join me. She gladly accepted. Springtime
    in Paris can be a beautiful event. The grass is green, the flowers are blooming and the sun&apos;s rays blanket the city.
&lt;/p&gt;
&lt;p&gt;We arrived in Paris on Tuesday, April 19 and quickly found our way to our &lt;a href=&quot;http://www.lemeridienetoile.com/&quot;&gt;hotel&lt;/a&gt;.
    Its location was ideal: across the street from Le Palais des Congr&#232;s de Paris convention center and mall. Since the
    conference
    was at the convention center, it made logistics for my talks very convenient. We grabbed a quick bite after settling
    in,
    then took a 15-minute stroll to the Arc de Triomphe.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_c.jpg&quot; title=&quot;Obligatory Arc de Triomphe selfie&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377054130/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_m.jpg&quot; width=&quot;240&quot;
        alt=&quot;Obligatory Arc de Triomphe selfie&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_c.jpg&quot; title=&quot;Abbie and Eiffel Tower&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377063160/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_m.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Eiffel Tower&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we joined Ippon developers and friends at a
    &lt;a href=&quot;http://blog.ippon.fr/2016/04/07/le-before-du-devoxx-avec-matt-raible/&quot;&gt;special event for Java Hipsters&lt;/a&gt;.
    Their
    &lt;a href=&quot;http://rooftop-work.paris/&quot;&gt;rooftop location&lt;/a&gt; had great views, cold &quot;Java&quot; beer and I met a lot of
    enthusiastic
    developers. I especially enjoyed talking with the original Java Hipster and founder of
    &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;, &lt;a href=&quot;http://www.julien-dubois.com/&quot;&gt;Julien Dubois&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_c.jpg&quot; title=&quot;Java Beer!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046785153/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_q.jpg&quot; width=&quot;150&quot; alt=&quot;Java Beer!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_c.jpg&quot;
       title=&quot;The original Java Hipster, Julien Dubious&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046789653/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The original Java Hipster, Julien Dubious&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_c.jpg&quot; title=&quot;Fun event!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046794363/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Fun event!&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The sunset over Paris provided a splendid backdrop for the festivities.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be_c.jpg&quot; title=&quot;Sunset over Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046797633/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be.jpg&quot; width=&quot;500&quot; alt=&quot;Sunset over Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;I had the good fortune to visit Paris last week for &lt;a href=&quot;http://www.devoxx.fr/&quot;&gt;Devoxx France&lt;/a&gt;. When traveling
    to conferences in exotic locations,
    I like to bring a travel partner. This time, I asked my daughter, Abbie, to join me. She gladly accepted. Springtime
    in Paris can be a beautiful event. The grass is green, the flowers are blooming and the sun&apos;s rays blanket the city.
&lt;/p&gt;
&lt;p&gt;We arrived in Paris on Tuesday, April 19 and quickly found our way to our &lt;a href=&quot;http://www.lemeridienetoile.com/&quot;&gt;hotel&lt;/a&gt;.
    Its location was ideal: across the street from Le Palais des Congr&#232;s de Paris convention center and mall. Since the
    conference
    was at the convention center, it made logistics for my talks very convenient. We grabbed a quick bite after settling
    in,
    then took a 15-minute stroll to the Arc de Triomphe.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_c.jpg&quot; title=&quot;Obligatory Arc de Triomphe selfie&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377054130/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1650/26377054130_d1d6561024_m.jpg&quot; width=&quot;240&quot;
        alt=&quot;Obligatory Arc de Triomphe selfie&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_c.jpg&quot; title=&quot;Abbie and Eiffel Tower&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377063160/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1548/26377063160_2cc22299cf_m.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Eiffel Tower&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1634/26046780663_83b2de9696_c.jpg&quot; title=&quot;The Arc is massive!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046780663/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1634/26046780663_83b2de9696.jpg&quot; width=&quot;500&quot; alt=&quot;The Arc is massive!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we joined Ippon developers and friends at a
    &lt;a href=&quot;http://blog.ippon.fr/2016/04/07/le-before-du-devoxx-avec-matt-raible/&quot;&gt;special event for Java Hipsters&lt;/a&gt;.
    Their
    &lt;a href=&quot;http://rooftop-work.paris/&quot;&gt;rooftop location&lt;/a&gt; had great views, cold &quot;Java&quot; beer and I met a lot of
    enthusiastic
    developers. I especially enjoyed talking with the original Java Hipster and founder of
    &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;, &lt;a href=&quot;http://www.julien-dubois.com/&quot;&gt;Julien Dubois&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_c.jpg&quot; title=&quot;Java Beer!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046785153/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1697/26046785153_7fdd931724_q.jpg&quot; width=&quot;150&quot; alt=&quot;Java Beer!&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_c.jpg&quot;
       title=&quot;The original Java Hipster, Julien Dubious&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046789653/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1718/26046789653_ac527f73ec_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The original Java Hipster, Julien Dubious&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_c.jpg&quot; title=&quot;Fun event!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046794363/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26046794363_3a057b8e6e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Fun event!&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The sunset over Paris provided a splendid backdrop for the festivities.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be_c.jpg&quot; title=&quot;Sunset over Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046797633/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1591/26046797633_60beba62be.jpg&quot; width=&quot;500&quot; alt=&quot;Sunset over Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;On Wednesday, Abbie and I got up early and headed to Versailles. We toured Ch&#226;teau de Versailles, the Gardens and
    &lt;a href=&quot;http://en.chateauversailles.fr/marie-antoinettes-estate&quot;&gt;Marie-Antoinette&apos;s estate&lt;/a&gt;. I&apos;d never visited
    this
    area of Versailles and never realized what I was missing. We rented a boat and practiced rowing on the Grand Canal
    to get
    ready for rafting season.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1592/26046801963_95dafdd5b7_c.jpg&quot; title=&quot;Abbie and Louis&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26046801963/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1592/26046801963_95dafdd5b7_n.jpg&quot; width=&quot;240&quot; alt=&quot;Abbie and Louis&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1717/26584955241_4b69591dea_c.jpg&quot; title=&quot;Lots of gold at Versailles!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26584955241/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1717/26584955241_4b69591dea_n.jpg&quot; width=&quot;240&quot;
        alt=&quot;Lots of gold at Versailles!&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1583/26584961021_29d12506dd_c.jpg&quot; title=&quot;The Gardens&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26584961021/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1583/26584961021_29d12506dd.jpg&quot; width=&quot;500&quot; alt=&quot;The Gardens&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1490/26044647774_97f6749313_c.jpg&quot; title=&quot;Spring in Paris is beautiful!&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26044647774/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1490/26044647774_97f6749313.jpg&quot; width=&quot;500&quot;
        alt=&quot;Spring in Paris is beautiful!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1531/26377129570_b72406b68e_c.jpg&quot; title=&quot;Hameau de la Reine&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377129570/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1531/26377129570_b72406b68e_q.jpg&quot; width=&quot;150&quot; alt=&quot;Hameau de la Reine&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1441/26377132120_90c70026c8_c.jpg&quot;
       title=&quot;The Queen&apos;s house and billiard room&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26377132120/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1441/26377132120_90c70026c8_q.jpg&quot; width=&quot;150&quot;
        alt=&quot;The Queen&apos;s house and billiard room&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1644/26624011386_06b02e4cee_c.jpg&quot; title=&quot;The Apollo Fountain&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26624011386/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1644/26624011386_06b02e4cee_q.jpg&quot; width=&quot;150&quot; alt=&quot;The Apollo Fountain&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we stopped by the &lt;a href=&quot;http://uk.le-sud-restaurant.com/&quot;&gt;Restaurant Le Sud&lt;/a&gt; for the speaker&apos;s
    dinner. It was
    fun seeing familiar faces and meeting new folks.
&lt;/p&gt;
&lt;p&gt;Thursday was my first talk, but we had the morning free to explore. We headed for the Eiffel Tower and rode its north
    elevator
    straight to the top. The views where spectacular and Abbie got goosebumps from the gentle sway.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1445/26650599775_547452b330_c.jpg&quot; title=&quot;Great view from the top&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650599775/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1445/26650599775_547452b330_m.jpg&quot; width=&quot;240&quot; alt=&quot;Great view from the top&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1588/26650603845_9df85f8e47_c.jpg&quot; title=&quot;It&apos;s a long way down&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650603845/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1588/26650603845_9df85f8e47_m.jpg&quot; width=&quot;240&quot; alt=&quot;It&apos;s a long way down&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1441/26650605665_a3a0a2e5a9_c.jpg&quot; title=&quot;Happiness in Paris&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26650605665/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1441/26650605665_a3a0a2e5a9_m.jpg&quot; width=&quot;240&quot; alt=&quot;Happiness in Paris&quot;
        style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1710/26624042686_d3004caf1d_c.jpg&quot; title=&quot;Tour Eiffel&quot;
       rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26624042686/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1710/26624042686_d3004caf1d_m.jpg&quot; width=&quot;240&quot; alt=&quot;Tour Eiffel&quot;
        style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    I transformed from an old-fashioned, whiskey-drinking Java developer to a Java Hipster
    &lt;a href=&quot;https://cfp.devoxx.fr/2016/talk/OJD-3590/Get_Hip_with_JHipster:_Spring_Boot_+_AngularJS_+_Bootstrap&quot;&gt;a few
        hours later&lt;/a&gt;. You can see
    the slides from my &quot;Get Hip with JHipster&quot; presentation below, or &lt;a
    href=&quot;http://www.slideshare.net/mraible/get-hip-with-jhipster-spring-boot-angularjs-bootstrap-devoxx-france-2016&quot;&gt;on
    SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/DICuqemFX1Sjx7&quot; width=&quot;595&quot; height=&quot;373&quot; frameborder=&quot;0&quot;
        marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
        style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;We wanted to see the &lt;a href=&quot;https://en.wikipedia.org/wiki/Catacombs_of_Paris&quot;&gt;Catacombs of Paris&lt;/a&gt; that night,
    and made it just minutes before it closed. Seeing the remains of millions of people&apos;s bones stacked on top of each other
    frightened Abbie more than standing on the glass floor in the Eiffel Tower. I experienced more heebie jeebies from
    the floor.
    We popped out of the Catacombs near the excellent
    &lt;a href=&quot;https://www.tripadvisor.com/Restaurant_Review-g187147-d5562763-Reviews-Thai_paragon-Paris_Ile_de_France.html&quot;&gt;Thai
        Paragon&lt;/a&gt; and stopped for a delicious meal.
    Abbie tried duck for the first time and loved it.
&lt;/p&gt;
&lt;p&gt;
    I had two talks on Friday, a quickie on &lt;a
    href=&quot;https://cfp.devoxx.fr/2016/talk/PGF-2414/Writing_an_InfoQ_Mini_Book_with_Asciidoctor&quot;&gt;how to write an InfoQ
    Mini-Book with Asciidoctor&lt;/a&gt;
    and a 45-minute session on &lt;a href=&quot;https://cfp.devoxx.fr/2016/talk/LUI-4351/The_Art_of_Angular_in_2016&quot;&gt;The Art of
    Angular in 2016&lt;/a&gt;.
    I wrote the InfoQ Mini-Book presentation using &lt;a href=&quot;https://github.com/opendevise/bespoke-emulating-shower&quot;&gt;Asciidoctor&apos;s
    Bespoke support&lt;/a&gt; and really enjoyed
    the experience. Thanks to &lt;a href=&quot;https://twitter.com/mojavelinux&quot;&gt;Dan Allen&lt;/a&gt; for assembling this easy to use
    starter template!
    Dan was also a great help in getting the JHipster Book printed for the first time and I was pumped to have a copy
    with me to show off.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1485/26046771893_34ea82fccc_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26046771893/in/album-72157667022214770/&quot; title=&quot;JHipster Book in print!&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1485/26046771893_34ea82fccc_n.jpg&quot; width=&quot;240&quot; alt=&quot;JHipster Book in print!&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1584/26046773243_b3c3a8f4b2_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26046773243/in/album-72157667022214770/&quot; title=&quot;heroku deploy:jar&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1584/26046773243_b3c3a8f4b2_n.jpg&quot; width=&quot;240&quot; alt=&quot;heroku deploy:jar&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    You can &lt;a href=&quot;http://mraible.github.io/infoq-mini-book-presentation/&quot;&gt;view the presentation online&lt;/a&gt; and
    checkout
    &lt;a href=&quot;https://github.com/mraible/infoq-mini-book-presentation&quot;&gt;its repository on GitHub&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;For my Angular presentation, I invited Abbie to kick things off, so she could experience what it&apos;s like to speak at a
    conference.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1619/26044723184_ee99bd81d9_c.jpg&quot;
       title=&quot;Moments before Abbie and I spoke about the Art of #Angular in 2016.&quot; rel=&quot;lightbox[devoxxfr2016]&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/26044723184/in/album-72157667022214770/&quot;&gt;&lt;img
        src=&quot;https://farm2.staticflickr.com/1619/26044723184_ee99bd81d9.jpg&quot; width=&quot;500&quot;
        alt=&quot;Moments before Abbie and I spoke about the Art of #Angular in 2016.&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;She did great and I followed her intro with my presentation on working with Angular 2. You can see my presentation
    below or &lt;a href=&quot;http://www.slideshare.net/mraible/the-art-of-angular-in-2016-devoxx-france-2016&quot;&gt;check it out on
        SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/f4qsdZ0gkbnbKN&quot; width=&quot;595&quot; height=&quot;373&quot; frameborder=&quot;0&quot;
        marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
        style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    At the end of the conference, we attended the &lt;a href=&quot;https://lescastcodeurs.com/&quot;&gt;Les Cast Codeurs Podcast&lt;/a&gt;. It
    was all in French, but you
    could tell everyone was having a good time from the smiles and laughter in the audience. During the session, the
    Devoxx Crew surprised me
    with a &lt;a href=&quot;https://java-champions.java.net/&quot;&gt;Java Champion&lt;/a&gt; award. I was very &lt;a href=&quot;https://twitter.com/mraible/status/723565855821443072&quot;&gt;surprised and humbled to
    receive this recognition&lt;/a&gt;. It
    was pretty cool having Abbie with me for such an honor.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1495/26044726404_91272a2bae_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26044726404/in/album-72157667022214770/&quot; title=&quot;Les Cast Codeurs&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1495/26044726404_91272a2bae_m.jpg&quot; width=&quot;240&quot; alt=&quot;Les Cast Codeurs&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm2.staticflickr.com/1465/26044729304_080e658df0_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/26044729304/in/album-72157667022214770/&quot; title=&quot;I&amp;#x27;m a Java Champion! :)&quot; rel=&quot;lightbox[devoxxfr2016]&quot;&gt;
        &lt;img src=&quot;https://farm2.staticflickr.com/1465/26044729304_080e658df0_m.jpg&quot; width=&quot;240&quot; alt=&quot;I&amp;#x27;m a Java Champion! :)&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div style=&quot;margin: 0 auto; text-align: right; margin-top: -10px; max-width: 500px; font-size: .9em&quot;&gt;
    More on Flickr &amp;rarr; &lt;a href=&quot;https://www.flickr.com/photos/mraible/albums/72157667022214770&quot;&gt;Devoxx France 2016&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;
    Thanks to Devoxx France and Ippon Technologies for providing us with the opportunity for such a fun adventure. We
    had a blast!&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/how_to_implement_a_smart</id>
        <title type="html">How to Implement a Smart Chunking Bootstrap Carousel with AngularJS</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/how_to_implement_a_smart"/>
        <published>2016-03-15T09:47:30-06:00</published>
        <updated>2016-03-15T15:48:49-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="ui-bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="carousel" scheme="http://roller.apache.org/ns/tags/" />
        <category term="css" scheme="http://roller.apache.org/ns/tags/" />
        <category term="responsive" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
    I&apos;ve been helping a client develop a project management application for the last several months. One of the features
    I implemented uses &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;UI Bootstrap&apos;s carousel directive&lt;/a&gt; to display a list of project templates to choose from when creating a new project. Rather than displaying
    one at a time, we wanted to display as many as the user&apos;s screen would allow. That is, if they were on a large monitor,
    we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a
    &lt;em&gt;smart chunking carousel&lt;/em&gt;.
&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
    I&apos;ve been helping a client develop a project management application for the last several months. One of the features
    I implemented uses &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;UI Bootstrap&apos;s carousel directive&lt;/a&gt; to display a list of project templates to choose from when creating a new project. Rather than displaying
    one at a time, we wanted to display as many as the user&apos;s screen would allow. That is, if they were on a large monitor,
    we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a
    &lt;em&gt;smart chunking carousel&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    To begin, I made it possible to show groups of items in the carousel using &lt;a href=&quot;http://stackoverflow.com/a/21653981/65681&quot;&gt;array chunking&lt;/a&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
  function chunk(arr, size) {
    var newArr = [];
    var arrayLength = arr.length;
    for (var i = 0; i &lt; arrayLength; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
&lt;/pre&gt;
&lt;p&gt;Using UI Bootstrap&apos;s &lt;a href=&quot;https://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;example code&lt;/a&gt;, I created &lt;code&gt;$scope.chunkedSlides&lt;/code&gt;
from &lt;code&gt;$scope.slides&lt;/code&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.chunkSize = 5;

// chunk slides so there&apos;s two per chunk by default
$scope.chunkedSlides = chunk($scope.slides, $scope.chunkSize);
&lt;/pre&gt;
&lt;p&gt;Next, I changed the HTML template to read the grouped slides, and show each one.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;uib-carousel active=&quot;active&quot; interval=&quot;0&quot; no-wrap=&quot;true&quot;&amp;gt;
  &amp;lt;uib-slide ng-repeat=&quot;row in chunkedSlides&quot;&amp;gt;
    &amp;lt;div class=&quot;row&quot;&amp;gt;
      &amp;lt;div ng-repeat=&quot;slide in row track by $index&quot; class=&quot;slide&quot;&amp;gt;
        &amp;lt;img ng-src=&quot;{{slide.image}}&quot;&amp;gt;
        &amp;lt;div class=&quot;carousel-caption&quot;&amp;gt;
          &amp;lt;h4&amp;gt;Slide {{slide.id}}&amp;lt;/h4&amp;gt;
          &amp;lt;p&amp;gt;{{slide.text}}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/uib-slide&amp;gt;
&amp;lt;/uib-carousel&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This was enough to get five squares on a large monitor. 
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;img src=&quot;https://farm2.staticflickr.com/1704/25673191306_39ee95e5a0_z.jpg&quot; width=&quot;640&quot; alt=&quot;Carousel Diagram&quot;&gt;
&lt;/p&gt;
&lt;p&gt;
However, I wanted to go further and reduce the number per group on smaller monitors. I created a &lt;code&gt;SmartChunking&lt;/code&gt;
    service that defined how many per group for each possible width.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;app&apos;).service(&apos;SmartChunking&apos;, function() {
  var large = 1600;
  var medium = 1200;
  var small = 1024;
  var xsmall = 800;

  this.getChunkSize = function(width) {
    var chunkSize;
    if (width &gt;= large) {
      chunkSize = 5;
    } else if (width &gt;= medium) {
      chunkSize = 4;
    } else if (width &gt;= small) {
      chunkSize = 3;
    } else if (width &gt;= xsmall) {
      chunkSize = 2;
    } else {
      chunkSize = 1;
    }
    return chunkSize;
  }
});
&lt;/pre&gt;
&lt;p&gt;
    I wrote a &lt;code&gt;smart-chunking&lt;/code&gt; directive to fire an event with the chunk size.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;app&apos;).directive(&apos;smartChunking&apos;, function($window, SmartChunking) {
  return {
    restrict: &apos;A&apos;,
    link: function($scope) {
      var w = angular.element($window);

      // window.outerWidth works on desktop, screen.height on iPad (width returns 768)
      var width = ($window.outerWidth &gt; 0) ? $window.outerWidth : screen.height;
      var chunkSize = SmartChunking.getChunkSize(width);
      if (chunkSize !== 5) {
        $scope.$emit(&apos;change-chunk-size&apos;, chunkSize);
      }

      $scope.getWidth = function() {
        return ($window.outerWidth &gt; 0) ? $window.outerWidth : screen.width;
      };

      $scope.$watch($scope.getWidth, function(newValue, oldValue) {
        if (newValue !== oldValue) {
          var chunkSize = SmartChunking.getChunkSize(newValue);
          $scope.$emit(&apos;change-chunk-size&apos;, chunkSize);
        }
      });

      w.bind(&apos;resize&apos;, function() {
        $scope.$apply();
      });
    }
  }
});
&lt;/pre&gt;
&lt;p&gt;Then I added a listener for this in the controller that populated the carousel.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.$on(&apos;change-chunk-size&apos;, function(event, data) {
  if (data !== $scope.chunkSize) {
    $scope.chunkedSlides = chunk($scope.slides, data);
    $scope.chunkSize = data;
  }
});
&lt;/pre&gt;
&lt;p&gt;
    The final step was adding the &lt;code&gt;smark-chunking&lt;/code&gt; directive to each slide and dynamically determining its &lt;code&gt;col-sm-*&lt;/code&gt; class.
&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;div ng-repeat=&quot;slide in row track by $index&quot; class=&quot;slide&quot; ng-class=&quot;getSlideClass(chunkSize)&quot; smart-chunking&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The controller contains a map of classes that map to chunk sizes:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var classMap = {
  5: &apos;col-sm-2&apos;,
  4: &apos;col-sm-3&apos;,
  3: &apos;col-sm-4&apos;,
  2: &apos;col-sm-5&apos;,
};

$scope.getSlideClass = function(chunkSize) {
  if (classMap[chunkSize]) {
    return classMap[chunkSize];
  } else {
    return &apos;col-sm-10&apos;;
  }
}
&lt;/pre&gt;
&lt;p&gt;I did find that adding some CSS made things look quite a bit better.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.carousel-caption {
  padding-bottom: 0;
}

.carousel-control.left,
.carousel-control.right {
  background-image: none;
}

.carousel-indicators {
  display: none;
}

.carousel-inner {
  padding-left: 10%;
  overflow: visible;
}

.carousel-control .glyphicon-chevron-left,
.carousel-control .glyphicon-chevron-right {
  font-size: 100px;
  margin-top: -60px;
  font-style: normal;
  font-weight: 100;
}

.carousel-control .glyphicon-chevron-left {
  margin-left: -100px;
}

.carousel-control .glyphicon-chevron-right {
  margin-right: -40px;
}

/* make slide widths more responsive */
@media only screen and (min-width: 1600px) {
  .col-sm-2 {
    width: 18%;
  }
}

@media only screen and (min-width: 1200px) {
  .col-sm-3 {
    width: 22%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 1200px) {
  .col-sm-4 {
    width: 29%;
  }
  .carousel-control .glyphicon-chevron-left {
    margin-left: -70px;
  }
  .carousel-control .glyphicon-chevron-right {
    margin-right: -20px;
  }
}

@media only screen and (max-width: 800px) {
  .col-sm-10 {
    width: 90%;
  }
}
&lt;/pre&gt;
&lt;p&gt;I hope this tip helps you if you need to implement a similar feature. I&apos;ve published a &lt;a href=&quot;https://plnkr.co/edit/BQkTiIUTbiLuOQ03QV8z?p=preview&quot;&gt;demo on Plunkr&lt;/a&gt; (best experienced in embedded view).&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm2.staticflickr.com/1504/25663696696_2517b5f33d_b.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/25663696696/in/datetaken-public/&quot; title=&quot;Smark Chunking Carousel&quot; rel=&quot;lightbox[smart-chunking-carousel]&quot;&gt;&lt;img src=&quot;https://farm2.staticflickr.com/1504/25663696696_2517b5f33d_z.jpg&quot; width=&quot;640&quot;  alt=&quot;Smark Chunking Carousel&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/devoxx_2015_a_java_hipster</id>
        <title type="html">Devoxx 2015: A Java Hipster Visits Belgium</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/devoxx_2015_a_java_hipster"/>
        <published>2015-11-17T00:09:43-07:00</published>
        <updated>2015-11-17T06:21:03-07:00</updated> 
        <category term="/Java" label="Java" />
        <category term="mcginityphoto" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springboot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="devoxx" scheme="http://roller.apache.org/ns/tags/" />
        <category term="beer" scheme="http://roller.apache.org/ns/tags/" />
        <category term="belgium" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
    I&apos;ve been excited to show people &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt; and what it can do ever since I
    &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_jhipster_on&quot;&gt;started
        using it in September 2014&lt;/a&gt;. I&apos;ve been using its core frameworks (AngularJS,
    Bootstrap and Spring Boot) for a few years and believe they do a great job to
    simplify web development. Especially for Java developers.
&lt;/p&gt;
&lt;p&gt;
    When my JHipster talk was accepted for &lt;a href=&quot;http://www.devoxx.be/&quot;&gt;Devoxx Belgium&lt;/a&gt;, I told Trish we were
    headed back to Belgium. She smiled from ear-to-ear. Belgium is one of our favorite countries
    to visit. In an effort to live healthier prior to Devoxx, I stopped drinking beer a month beforehand. I mentioned
    this to friends the week prior.
&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;One month ago, I stopped drinking beer. I hoped it&apos;d help me with &lt;a href=&quot;http://www.21-points.com&quot;&gt;www.21-points.com&lt;/a&gt;
        and weight loss. Unfortunately, it did not.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;
        I told myself I&apos;d start drinking beer again when 1) The Bus was finished or 2) Trish and I arrived in Belgium
        for Devoxx. Looks like #2 will win (we land on Tuesday).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We arrived in Brussels late Tuesday morning and hopped aboard a train to Antwerp. After
    arriving, we were hungry so we stopped at &lt;a href=&quot;http://www.biercentral.eu/&quot;&gt;Bier Central&lt;/a&gt; for lunch. The
    mussels and
    beer were splendid.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5814/23079344391_a2c964d0df_c.jpg&quot; title=&quot;First beer in over a month, so good!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/23079344391/&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5814/23079344391_a2c964d0df.jpg&quot; width=&quot;500&quot; alt=&quot;First beer in over a month, so good!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
    I&apos;ve been excited to show people &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt; and what it can do ever since I
    &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_jhipster_on&quot;&gt;started
        using it in September 2014&lt;/a&gt;. I&apos;ve been using its core frameworks (AngularJS,
    Bootstrap and Spring Boot) for a few years and believe they do a great job to
    simplify web development. Especially for Java developers.
&lt;/p&gt;
&lt;p&gt;
    When my JHipster talk was accepted for &lt;a href=&quot;http://www.devoxx.be/&quot;&gt;Devoxx Belgium&lt;/a&gt;, I told Trish we were
    headed back to Belgium. She smiled from ear-to-ear. Belgium is one of our favorite countries
    to visit. In an effort to live healthier prior to Devoxx, I stopped drinking beer a month beforehand. I mentioned
    this to friends the week prior.
&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;One month ago, I stopped drinking beer. I hoped it&apos;d help me with &lt;a href=&quot;http://www.21-points.com&quot;&gt;www.21-points.com&lt;/a&gt;
        and weight loss. Unfortunately, it did not.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;
        I told myself I&apos;d start drinking beer again when 1) The Bus was finished or 2) Trish and I arrived in Belgium
        for Devoxx. Looks like #2 will win (we land on Tuesday).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We arrived in Brussels late Tuesday morning and hopped aboard a train to Antwerp. After
    arriving, we were hungry so we stopped at &lt;a href=&quot;http://www.biercentral.eu/&quot;&gt;Bier Central&lt;/a&gt; for lunch. The
    mussels and
    beer were splendid.&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5814/23079344391_a2c964d0df_c.jpg&quot; title=&quot;First beer in over a month, so good!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/23079344391/&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5814/23079344391_a2c964d0df.jpg&quot; width=&quot;500&quot; alt=&quot;First beer in over a month, so good!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5809/22446966053_48f252f787_c.jpg&quot; title=&quot;Breakfast at Bernardin&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22446966053/in/dateposted-public/&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5809/22446966053_48f252f787_t.jpg&quot; width=&quot;100&quot; class=&quot;picture&quot; alt=&quot;Breakfast at Bernardin&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    We walked to our accommodations afterward, the &lt;a href=&quot;http://www.bernardin-antwerpen.be/&quot;&gt;Gernardin Guesthouse&lt;/a&gt;.
    We loved the small space, steep stairs and the nice use of space for the restroom in the upstairs closet. The
    breakfast was delightful too.
&lt;/p&gt;

&lt;p&gt;Wednesday afternoon we found ourselves strolling on a city walk around Antwerp. It was overcast, but not chilly
    and we had a fabulous lunch at &lt;a href=&quot;http://www.monantwerp.com/&quot;&gt;M&#243;n&lt;/a&gt; after taking some pictures from the top
    of the &lt;a href=&quot;http://www.mas.be/&quot; title=&quot;Museum aan de Stroom&quot;&gt;MAS&lt;/a&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5720/23054582742_393f044782_c.jpg&quot; title=&quot;Groenplaats, Anterp, Belgium&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/23054582742&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5720/23054582742_393f044782_q.jpg&quot; width=&quot;150&quot; alt=&quot;Groenplaats, Anterp, Belgium&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/681/22446999753_d096a6c46e_c.jpg&quot; title=&quot;From the top of Mas Museum Aan De Stroom&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22446999753&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/681/22446999753_d096a6c46e_q.jpg&quot; width=&quot;150&quot; alt=&quot;From the top of Mas Museum Aan De Stroom&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/742/22447021683_d2e0c2a5dc_c.jpg&quot; title=&quot;Lunch at M&#243;n&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22447021683&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/742/22447021683_d2e0c2a5dc_q.jpg&quot; width=&quot;150&quot; alt=&quot;Lunch at M&#243;n&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Wednesday evening, we journeyed to the Devoxx venue to deliver my presentation. The performance went well and I heard
    lots of positive feedback almost immediately. This is one of the things I love about Devoxx: the audience tweets
    like mad and
    feedback is immediate. I also like that the presentation displays are like developers monitors; &lt;em&gt;huge!&lt;/em&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/580/23056246016_087f330f4f_c.jpg&quot; title=&quot;Scotch&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/23056246016&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/580/23056246016_087f330f4f_m.jpg&quot; width=&quot;240&quot; alt=&quot;Scotch&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;https://farm1.staticflickr.com/589/23082331485_995b0a29de_c.jpg&quot; title=&quot;An Old Fashioned Java Developer&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/23082331485&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/589/23082331485_995b0a29de_m.jpg&quot; width=&quot;240&quot; alt=&quot;DSC_7788.jpg&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/679/22663956718_a82441bd93_c.jpg&quot; title=&quot;JHipster!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/22663956718&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/679/22663956718_a82441bd93_m.jpg&quot; width=&quot;240&quot; alt=&quot;JHipster!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/744/23093573791_07d2e3a9ac_c.jpg&quot; title=&quot;Immediate Feedback&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/23093573791&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/744/23093573791_07d2e3a9ac_m.jpg&quot; width=&quot;240&quot; alt=&quot;Immediate Feedback&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Devoxx made an excellent move this year: they uploaded recordings of talks to the &lt;a href=&quot;https://www.youtube.com/channel/UCCBVCTuk6uJrN3iFV_3vurg&quot;&gt;Devoxx 2015 channel on YouTube&lt;/a&gt;. Amazingly, they
    did it
    within hours for each talk! Because of this modern miracle, you can see &lt;a href=&quot;https://www.youtube.com/watch?v=baVOGuFIe9M&quot;&gt;Get Hip with
        JHipster on YouTube&lt;/a&gt; or watch it below.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/baVOGuFIe9M&quot; frameborder=&quot;0&quot; style=&quot;border: 1px solid black&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://www.slideshare.net/mraible/get-hip-with-jhipster-spring-boot-angularjs-bootstrap-devoxx-2015&quot;&gt;SlideShare&lt;/a&gt;
    |
    &lt;a href=&quot;http://static.raibledesigns.com/repository/presentations/Get_Hip_with_JHipster_Devoxx2015.pdf&quot;&gt;Download
        PDF&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    Near the end of my presentation, I announced &lt;a href=&quot;https://twitter.com/mraible/status/664498478920388608&quot;&gt;
    the source code for 21-Points Health is available on GitHub&lt;/a&gt;. I&apos;ve had quite a few people ask for it as part of
    the &lt;a href=&quot;http://www.infoq.com/minibooks/jhipster-mini-book&quot;&gt;JHipster Mini-Book&lt;/a&gt; and it seemed like the right
    thing to do. We celebrated that night with &lt;a href=&quot;https://twitter.com/starbuxman&quot;&gt;Josh Long&lt;/a&gt; and other new friends at Bier Central.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a data-flickr-embed=&quot;true&quot; href=&quot;https://farm1.staticflickr.com/595/22445469834_21210403fb_c.jpg&quot; title=&quot;Java Hipsters!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22445469834&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/595/22445469834_21210403fb_t.jpg&quot; width=&quot;100&quot; alt=&quot;Java Hipsters!&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;

    &lt;a data-flickr-embed=&quot;true&quot; href=&quot;https://farm1.staticflickr.com/579/23042227126_7e05081502_c.jpg&quot; title=&quot;More Cowbell!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/23042227126&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/579/23042227126_7e05081502_t.jpg&quot; width=&quot;100&quot; alt=&quot;More Cowbell!&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;

    &lt;a data-flickr-embed=&quot;true&quot; href=&quot;https://farm6.staticflickr.com/5748/22445489504_d700abc8a8_c.jpg&quot; title=&quot;New Friends at Bier Central&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22445489504&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5748/22445489504_d700abc8a8_t.jpg&quot; width=&quot;100&quot; alt=&quot;New Friends at Bier Central&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;

    &lt;a data-flickr-embed=&quot;true&quot; href=&quot;https://farm1.staticflickr.com/757/22675994129_aa9b7cfb79_c.jpg&quot; title=&quot;Cheers!&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/22675994129&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/757/22675994129_aa9b7cfb79_t.jpg&quot; width=&quot;100&quot; alt=&quot;Cheers!&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    Thursday we visited Bruges and had a wonderful time strolling around &lt;a href=&quot;https://bezoekers.Bruges.be/en/minnewaterpark&quot;&gt;Minnewater&lt;/a&gt;,
    marveling at the buildings near the main square and taking a clip-clop tour through
    town. We barely made it to the &lt;a href=&quot;http://www.brugesbeermuseum.com/&quot;&gt;Bruges Beer Museum&lt;/a&gt; before it
    closed and had a delicious
    meal at &lt;a href=&quot;http://www.cambrinus.eu/&quot;&gt;Cambrinus&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm1.staticflickr.com/730/22461168593_40d5063088_c.jpg&quot; title=&quot;Minnewater Brugge Belgium&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/22461168593&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/730/22461168593_40d5063088.jpg&quot; width=&quot;500&quot; alt=&quot;Minnewater Brugge Belgium&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5725/22459600664_cf3df2638f_c.jpg&quot; title=&quot;Sint-Janshospitaal Brugge West-Vlaanderen&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/22459600664&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5725/22459600664_cf3df2638f_m.jpg&quot; width=&quot;240&quot; alt=&quot;Sint-Janshospitaal Brugge West-Vlaanderen&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;https://farm1.staticflickr.com/688/22690129029_e3da2e47a0_c.jpg&quot; title=&quot;View from Mariastraat Brugge Belgium&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/22690129029&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/688/22690129029_e3da2e47a0_m.jpg&quot; width=&quot;240&quot; alt=&quot;View from Mariastraat Brugge Belgium&quot; style=&quot;border: 1px solid black; margin-left: 15px;&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm6.staticflickr.com/5752/22690142739_24b443fc6a_c.jpg&quot; title=&quot;Stadhuis Brugge Belgium&quot; rel=&quot;lightbox[devoxx2015]&quot; data-href=&quot;https://www.flickr.com/photos/mcginityphoto/22690142739&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5752/22690142739_24b443fc6a.jpg&quot; width=&quot;500&quot; alt=&quot;Stadhuis Brugge Belgium&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div style=&quot;margin: 0 auto; text-align: right; margin-top: -10px; max-width: 500px; font-size: .9em&quot;&gt;
    More on Flickr &amp;rarr; My &lt;a href=&quot;https://www.flickr.com/photos/mraible/albums/72157660511719478&quot;&gt;Devoxx 2015
    Album&lt;/a&gt; and Trish&apos;s &lt;a href=&quot;https://www.flickr.com/photos/mcginityphoto/albums/72157661317992265&quot;&gt;Belgium
    November 2015 Album&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;
    Thanks to the Devoxx crew for a fun conference and great venue. Thanks to Belgium:
    for being so beautiful, for making savory chocolate, brewing delicious beer and
    for your wonderful people. And to the Java community: thanks
    for being so enthusiastic and fun to talk to. We love creating lasting memories
    with you! &amp;#127867;&amp;#128522;
&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/angular_summit_2015</id>
        <title type="html">Angular Summit 2015</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/angular_summit_2015"/>
        <published>2015-10-01T10:29:31-06:00</published>
        <updated>2015-10-08T21:32:22-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="aurelia" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angular2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="spring-boot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="meteor" scheme="http://roller.apache.org/ns/tags/" />
        <category term="es6" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;
  I was in Boston this week, speaking and attending the very first &lt;a href=&quot;http://angularsummit.com&quot;&gt;Angular Summit&lt;/a&gt;. I had the privilege of delivering the opening keynote on Monday. I spoke about the Art of Angular
  and used a slide deck similar to &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs_in&quot;&gt;last time&lt;/a&gt;. I did
  update the presentation to show the astronomical growth of AngularJS in terms of candidate skills (on LinkedIn) and job opportunities (on Dice.com)&lt;sup&gt;1&lt;/sup&gt;.
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm1.staticflickr.com/691/21198424124_e9b9b37afb_c.jpg&quot; title=&quot;LinkedIn Skills Growth for JavaScript MVC Frameworks&quot; rel=&quot;lightbox[angularsummit2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/21198424124/in/dateposted-public/&quot;&gt;&lt;img src=&quot;https://farm1.staticflickr.com/691/21198424124_e9b9b37afb_c.jpg&quot; width=&quot;300&quot; alt=&quot;LinkedIn Skills Growth for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://farm6.staticflickr.com/5808/21633143850_9aef93d361_c.jpg&quot; title=&quot;Dice.com Job Growth for JavaScript MVC Frameworks&quot; rel=&quot;lightbox[angularsummit2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/21633143850/in/dateposted-public/&quot;&gt;&lt;img src=&quot;https://farm6.staticflickr.com/5808/21633143850_9aef93d361_c.jpg&quot; width=&quot;300&quot; alt=&quot;Dice.com Job Growth for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I mentioned the recently announced &lt;a href=&quot;http://angularjs.blogspot.com/2015/08/angular-1-and-angular-2-coexistence.html&quot;&gt;good news for Angular 2&lt;/a&gt;:
  &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;We&apos;re enabling mixing of Angular 1 and Angular 2 in the same application.&lt;/li&gt;
  &lt;li&gt;You can mix Angular 1 and Angular 2 components in the same view.&lt;/li&gt;
  &lt;li&gt;Angular 1 and Angular 2 can inject services across frameworks.&lt;/li&gt;
  &lt;li&gt;Data binding works across frameworks.&lt;/li&gt;
  &lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In related news, &lt;a href=&quot;https://twitter.com/cdoremus&quot;&gt;Craig Doremus&lt;/a&gt; recently posted a &lt;a href=&quot;https://github.com/cdoremus/state-geo-angular&quot;&gt;state-geo-angular&lt;/a&gt; project
  that shows how you can develop an Angular 1.x application that will be easy to upgrade to Angular 2.x.
  Thanks Craig!
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/vaTKWLA8oVDr8z&quot; width=&quot;600&quot; height=&quot;377&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;div style=&quot;text-align: right; max-width: 600px; margin: -20px auto 10px auto&quot;&gt;
    &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;Download&lt;/a&gt; | &lt;a href=&quot;//www.slideshare.net/mraible/the-art-of-angularjs-in-2015-angular-summit-2015&quot;&gt;SlideShare&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;After my keynote, I attended &lt;a href=&quot;https://twitter.com/prpatel&quot;&gt;Pratik Patel&lt;/a&gt;&apos;s session on &lt;a href=&quot;https://angularsummit.com/conference/boston/2015/09/session?id=34208&quot;&gt;High Performance JavaScript Web Apps&lt;/a&gt;.
  Pratik pointed out &lt;a href=&quot;http://mobitest.akamai.com&quot;&gt;mobitest.akamai.com&lt;/a&gt; for testing an app&apos;s performance and seeing its blocking resources. He also mentioned
  &lt;a href=&quot;http://speedgun.io/&quot;&gt;speedgun.io&lt;/a&gt; (currently unavailable) for capturing performance numbers as part of a continuous integration process. Finally,
  he recommended &lt;a href=&quot;http://addyosmani.com/blog/video-javascript-memory-management-masterclass/&quot;&gt;Addy Somani&apos;s JavaScript Memory Management Masterclass&lt;/a&gt;.
&lt;p&gt;
My second presentation was about &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;. Near the end of the presentation,
I mentioned that I hope to finish the &lt;a href=&quot;http://www.jhipster-book.com/&quot;&gt;JHipster Book&lt;/a&gt; this month. Writing presentations for
&lt;a href=&quot;//raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations&quot;&gt;SpringOne 2GX&lt;/a&gt; and the Angular Summit occupied a lot of my free time in September. Now that it&apos;s October, I&apos;ll be dedicating my free time to finishing the book. In fact, I think I can finish the rough draft this week!
&lt;/p&gt;&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/769Ne9avDiEeWl&quot; width=&quot;600&quot; height=&quot;377&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;div style=&quot;text-align: right; max-width: 600px; margin: -20px auto 10px auto&quot;&gt;
&lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;Download&lt;/a&gt; | &lt;a href=&quot;//www.slideshare.net/mraible/get-hip-with-jhipster-spring-boot-angularjs-bootstrap-angular-summit-2015&quot;&gt;SlideShare&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;
  For the last session of the day, I attended &lt;a href=&quot;https://twitter.com/johnlindquist&quot;&gt;John Lindquist&apos;s&lt;/a&gt; session on &lt;a href=&quot;http://angularsummit.com/conference/boston/2015/09/session?id=34230&quot;&gt;Angular 2 Components&lt;/a&gt;. John showed us
  how &lt;em&gt;everything is a component in Angular 2&lt;/em&gt;. He also said &quot;now is the time to learn ES6&quot; and built an
  &lt;a href=&quot;https://github.com/johnlindquist/angular-2-quickstart&quot;&gt;Angular 2 ToDo App&lt;/a&gt; using ES6 and a bit of TypeScript. You might recognize John&apos;s name; he&apos;s the founder of &lt;a href=&quot;http://egghead.io/&quot;&gt;egghead.io&lt;/a&gt;, an excellent
  site for &lt;a href=&quot;https://egghead.io/playlists/new-to-angular-start-here&quot;&gt;learning Angular&lt;/a&gt; with bite-sized videos.
&lt;/p&gt;
&lt;p&gt;Tuesday morning started with a &lt;a href=&quot;http://angularsummit.com/conference/boston/2015/09/session?id=34187&quot;&gt;
  Angular 2.0 keynote&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/ppavlovich&quot;&gt;Peter Pavlovich&lt;/a&gt;. I really enjoyed
  this session and received lots of good tips about getting ready for Angular 2. The tweet below from
  Ksenia Dmitrieva shows his advice.
&lt;/p&gt;
&lt;div style=&quot;margin: 0 auto; max-width: 500px;&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Best Practices for &lt;a href=&quot;https://twitter.com/hashtag/angularjs?src=hash&quot;&gt;#angularjs&lt;/a&gt; 1.X if you plan to switch to 2.0 by &lt;a href=&quot;https://twitter.com/ppavlovich&quot;&gt;@ppavlovich&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/AngularSummit?src=hash&quot;&gt;#AngularSummit&lt;/a&gt; &lt;a href=&quot;http://t.co/9nobqDc9G9&quot;&gt;pic.twitter.com/9nobqDc9G9&lt;/a&gt;&lt;/p&gt;&amp;mdash; Ksenia Dmitrieva (@KseniaDmitrieva) &lt;a href=&quot;https://twitter.com/KseniaDmitrieva/status/648865784152915968&quot;&gt;September 29, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;My biggest takeaway was to start following &lt;a href=&quot;https://github.com/johnpapa/angular-styleguide&quot;&gt;John Papa&apos;s Angular Style Guide&lt;/a&gt; &lt;abbr title=&quot;As Soon As Possible&quot;&gt;ASAP&lt;/abbr&gt;.
&lt;p&gt;The first session I attended on Tuesday was &lt;a href=&quot;https://twitter.com/JuddFlamm&quot;&gt;Judd Flamm&lt;/a&gt;&apos;s &lt;a href=&quot;https://angularsummit.com/conference/boston/2015/09/session?id=34298&quot;&gt;Google Material Design &amp;amp; Angular&lt;/a&gt;.
  I&apos;m using &lt;a href=&quot;https://fezvrasta.github.io/bootstrap-material-design/&quot;&gt;Material Design for Bootstrap&lt;/a&gt; on a side project, so I was interested in learning more about its inspiration.
  We learned that &lt;a href=&quot;https://design.google.com/&quot;&gt;Google Design&lt;/a&gt; has everything you need to know about why Material Design exists. We also
  learned about &lt;a href=&quot;https://material.angularjs.org&quot;&gt;Angular Material&lt;/a&gt; and spent most of the session looking at its components. Judd
  recommended &lt;a href=&quot;https://github.com/angular/material-start&quot;&gt;Angular Material-Start&lt;/a&gt; for those looking to get started quickly with both frameworks.
  Judd was a very entertaining speaker; I highly recommend you attend one of his talks if you get the opportunity.
&lt;/p&gt;
&lt;p&gt;After being dazzled by Peter&apos;s knowledge of Angular 2 in Tuesday&apos;s keynote, I attended two more of his talks: one on &lt;a href=&quot;https://www.meteor.com/&quot;&gt;Meteor&lt;/a&gt; and
  another on &lt;a href=&quot;http://aurelia.io/&quot;&gt;Aurelia&lt;/a&gt;. I&apos;ve known about Meteor for a while, but have become more intrigued by it lately with its
  &lt;a href=&quot;http://www.infoq.com/news/2015/09/meteor-12-ecmascript&quot;&gt;1.2 release&lt;/a&gt; and &lt;a href=&quot;http://info.meteor.com/blog/official-angular-support-with-angular-meteor-1.0.0&quot;&gt;Angular support&lt;/a&gt;. Meteor&apos;s
  command line tools that auto-inject CSS and JS demoed very well, as did it&apos;s installable features like a LESS support and Facebook authentication.
&lt;/p&gt;
&lt;p&gt;After hearing all the good things about Angular 2 from Peter, it was interesting to hear him downplay it in his Aurelia talk later that day. When he started showing code,
  it was pretty obvious that Aurelia is doing a great job of simplifying JavaScript MVC syntax for developers. You can develop components with almost half the
    code that Angular 2 requires, and it uses ES6, &lt;a href=&quot;http://jspm.io/&quot;&gt;jspm&lt;/a&gt; and &lt;a href=&quot;https://github.com/systemjs/systemjs&quot;&gt;SystemJS&lt;/a&gt;.
    If you&apos;re developing JavaScript, learning these tools will help prepare you for the future. It&apos;s cool that Aurelia encourages learning things you should learn anyway.
  &lt;/p&gt;
  &lt;p&gt;Aurelia and Angular 2 are both still in Alpha, so I&apos;m not sure it makes sense to use them on a project this year. However, I do think it&apos;s important to track
    them both. I especially think it&apos;s interesting that the founder of Aurelia, &lt;a href=&quot;http://twitter.com/EisenbergEffect&quot;&gt;Rob Eisenberg&lt;/a&gt;,
    &lt;a href=&quot;http://eisenbergeffect.bluespire.com/leaving-angular/&quot;&gt;left the Angular Team&lt;/a&gt; in November 2014 and &lt;a href=&quot;http://blog.durandal.io/2015/01/26/introducing-aurelia/&quot;&gt;
      announced Aurelia&lt;/a&gt; in January 2015 (&lt;a href=&quot;https://news.ycombinator.com/item?id=8948665&quot;&gt;Hacker News thread&lt;/a&gt;). Peter mentioned several times that Aurelia wants to help developers write apps,
      while AngularJS is more tied to helping Google write apps.
  &lt;/p&gt;
&lt;p&gt;
  There were around 400 people at Angular Summit, which I think is pretty good for a first-run conference. As with most No Fluff Just Stuff shows, it ran smoothly, had
    plenty of time between sessions and was filled with knowledgeable, entertaining speakers. It was fun doing my first keynote and I look forward to speaking again in November
    (at &lt;a href=&quot;http://www.devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;) and December (at
    &lt;a href=&quot;http://www.therichwebexperience.com/conference/fort_lauderdale/2015/12/home&quot;&gt;The Rich Web Experience&lt;/a&gt;).
  &lt;/head&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: .9em&quot;&gt;1. I know Dice.com is probably not a great site, but it makes sense to use it since I&apos;ve
been tracking JavaScript MVC framework job stats on it since February 2014.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations</id>
        <title type="html">SpringOne 2GX 2015: My Presentations on Comparing Hot JavaScript Frameworks and NoXML </title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/springone_2gx_2015_my_presentations"/>
        <published>2015-09-20T12:29:00-06:00</published>
        <updated>2015-09-20T18:40:33-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="xml" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springframework" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="s2gx" scheme="http://roller.apache.org/ns/tags/" />
        <category term="emberjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="noxml" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springone2gx" scheme="http://roller.apache.org/ns/tags/" />
        <category term="reactjs" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">Last week, I had the pleasure of traveling to Washington, DC to speak at the annual &lt;a href=&quot;http://www.springone2gx.com/&quot;&gt;SpringOne 2GX conference&lt;/a&gt;. I was pretty stressed for the last few weeks because I had to create two new presentations from scratch, and both had to be 90 minutes long. I was also hoping to finish the JHipster Book before the conference started. I was able to finish both presentations in the nick of time, but did not find the time to write the last chapter in the JHipster Book.&lt;/p&gt;
&lt;p&gt;The first presentation was titled &lt;a href=&quot;https://2015.event.springone2gx.com/schedule/sessions/comparing_hot_javascript_frameworks_angularjs_ember_js_and_react_js.html&quot;&gt;Comparing Hot JavaScript Frameworks: AngularJS, Ember.js and React.js&lt;/a&gt;. I started by revisiting the &lt;a href=&quot;//raibledesigns.com/rd/entry/comparing_jvm_web_frameworks_at&quot;&gt;Comparing JVM Web Frameworks talk I did at vJUG&lt;/a&gt; last February. I explained how I think traditional web frameworks are no longer relevant in 2015, but I do believe server-side rendering is still &lt;em&gt;very&lt;/em&gt; relevant. From there, I used &lt;a href=&quot;http://www.ybrikman.com/&quot;&gt;Yevgeniy Brikman&#8217;s&lt;/a&gt; framework scorecard (from his &lt;a href=&quot;http://www.slideshare.net/brikis98/nodejs-vs-play-framework&quot;&gt;Node.js vs. Play Framework presentation&lt;/a&gt;) to rank each framework by a number of different criteria. You can see the final results on &lt;a href=&quot;http://www.slideshare.net/mraible/comparing-hot-javascript-frameworks-angularjs-emberjs-and-reactjs-springone-2gx-2015/160&quot;&gt;slide 160&lt;/a&gt;. Since the scores were so close, I believe you could tweak some scores a bit (or add weights to the different criteria) and make any of the frameworks come out on top.
&lt;/p&gt;
&lt;p&gt;
You can click through the presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my presentations page&lt;/a&gt;, or &lt;a href=&quot;//www.slideshare.net/mraible/comparing-hot-javascript-frameworks-angularjs-emberjs-and-reactjs-springone-2gx-2015&quot;&gt;see it on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/NGLRPcZiLF0pBo&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;I started writing the second presentation a week before I had to deliver it. On Thursday, September 10th, I stayed up late, trying to figure out how to create a good presentation on NoXML &lt;em&gt;and&lt;/em&gt; finish the last part of the JHipster Book. Then it came to me, I needed to &lt;em&gt;parallelize&lt;/em&gt; and do them both at the same time. I decided to compare AppFuse (which is similar to a legacy Spring application with lots of XML) to JHipster (which hardly contains any XML). 
&lt;/p&gt;
&lt;p&gt;
I wrote a 10-page Google Doc on how I planned to do this, then went rafting and camping with my family for the weekend. I finished most of the presentation on Monday night, but then realized the presentation wouldn&apos;t be long enough to fill 90 minutes. So I hunkered down at midnight, created a new AppFuse application and removed a bunch of its XML. This took me until 3:30am, and I was able to accomplish the following tasks:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring XML to Java&lt;/li&gt;
&lt;li&gt;Spring Security Configuration to Java&lt;/li&gt;
&lt;li&gt;web.xml to WebApplicationInitializer&lt;/li&gt;
&lt;li&gt;Spring MVC to Java&lt;/li&gt;
&lt;li&gt;Migrated to Spring Boot&lt;/li&gt;
&lt;li&gt;Maven to Groovy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;p&gt;I was pretty pumped when I completed my final goal: converting to Spring Boot and getting a test to pass. I made commits to an &lt;a href=&quot;https://github.com/mraible/appfuse-noxml/commits/master&quot;&gt;appfuse-noxml project on GitHub&lt;/a&gt; as I accomplished each step. You can see all the changes in &lt;a href=&quot;https://github.com/mraible/appfuse-noxml/commits/master&quot;&gt;the project&apos;s commit log&lt;/a&gt;. While I&apos;d figured everything out, I still needed to complete the presentation. Luckily, I found time to do this the night before, the morning of, and in the final hour before I had to deliver the talk. You can imagine my relief when I was done delivering both talks. 
&lt;/p&gt;
&lt;p&gt;
You can click through the presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my presentations page&lt;/a&gt;, or &lt;a href=&quot;//www.slideshare.net/mraible/noxml-eliminating-xml-in-spring-projects-springone-2gx-2015&quot;&gt;view it on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/key/4V9V7NSsNC2rd7&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen&gt; &lt;/iframe&gt; 
&lt;/div&gt;
&lt;p&gt;While I didn&apos;t get to spend much time at the conference, I did have a lot of fun while I was there. I got to meet some new folks, reconnect with old friends, and enjoy beers and dinner with a smiling crew on Thursday night. The Broncos victory late that night was the icing on the cake. &lt;img src=&quot;//raibledesigns.com/images/smileys/smile.gif&quot; class=&quot;smiley&quot; alt=&quot;:)&quot; title=&quot;:)&quot;&gt;&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/grails_angular_vs_jhipster</id>
        <title type="html">Grails + Angular vs. JHipster</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/grails_angular_vs_jhipster"/>
        <published>2015-07-14T08:02:01-06:00</published>
        <updated>2015-07-14T14:02:01-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="spring-boot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="grails" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;I recently received an email from a long time follower of my comparing web frameworks research and presentations. He asked some interesting questions:
&lt;/p&gt;
&lt;p class=&quot;quote&quot;&gt;
I am starting on a new venture to build a direct to consumer web application. I am planning to leverage Cloud services to build my CI/CD pipeline. I am very strong with Java Backend/middleware and learning Javascript Front-end frameworks. I love Spring and SOFEA. Having said that, I am wondering if I should use Grails + Angular or JHipster? My primary concern with JHipster is there is hardly any &#8216;community&apos;, there is Julien and whatever he says/thinks goes! Can you give me some pointers?
&lt;/a&gt;
&lt;p&gt;I imagine there&apos;s other JVM developers with similar questions, so I figured I&apos;d publish my response for all to see.&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;
&lt;p style=&quot;margin-top: 0&quot;&gt;
JHipster may have a smaller community than Grails, but remember that it&apos;s built on Spring Boot and AngularJS. Both have huge communities. In fact, Grails 3 is built on Spring Boot, just like JHipster. 
&lt;/p&gt;
&lt;p&gt;
Even though JHipster generates your code in Java, there&apos;s nothing preventing you from writing your code in Groovy or Scala. I dig JHipster, but I&apos;ve also worked with AngularJS and Spring Boot for a couple years. The fact that someone put these technologies together and makes it easy to work with them is awesome. 
&lt;/p&gt;
&lt;p style=&quot;margin-bottom: 0&quot;&gt;
I like JHipster so much, I decided to write a book on it. I hope to finish it in the next couple months and have it published in the fall. It&apos;ll be a free download from InfoQ. Learn more at &lt;a href=&quot;http://www.jhipster-book.com/&quot;&gt;http://www.jhipster-book.com&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Yes, I&apos;m probably a bit biased since I&apos;m writing a JHipster book. However, it&apos;s been easy for me to introduce and use Spring Boot at my last few clients. They were already using Spring, so the transition to using a Spring simplifier was a no-brainer. I haven&apos;t had as much luck getting clients to adopt Grails, even though I&apos;ve suggested it. That could change now that it&apos;s based on Spring Boot.&lt;/p&gt;
&lt;p&gt;What&apos;s your experience? Would you recommend Grails + Angular over JHipster? If so, why?</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/getting_hip_with_jhipster_at</id>
        <title type="html">Getting Hip with JHipster at Denver&apos;s Java User Group</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/getting_hip_with_jhipster_at"/>
        <published>2015-04-09T08:31:54-06:00</published>
        <updated>2015-04-09T19:20:43-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="spring-boot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="html5" scheme="http://roller.apache.org/ns/tags/" />
        <category term="yeoman" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;Last night, I had the pleasure of &lt;a href=&quot;http://www.meetup.com/DenverJavaUsersGroup/events/220309287/&quot;&gt;speaking at Denver&apos;s Java User Group Meetup about JHipster&lt;/a&gt;. I&apos;ve been a big fan of &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt; ever since I started using it last fall. I developed a quick prototype for a client and wrote about &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_jhipster_on&quot;&gt;solving some issues I had with it on OS X&lt;/a&gt;. I like the project because it encapsulates the primary open source tools I&apos;ve been using for the last couple of years: &lt;a href=&quot;http://projects.spring.io/spring-boot/&quot;&gt;Spring Boot&lt;/a&gt;, &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; and &lt;a href=&quot;http://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;. I also wrote about its &lt;a href=&quot;http://www.infoq.com/news/2015/01/jhipster-2.0&quot;&gt;2.0 release&lt;/a&gt; on InfoQ in January.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://farm9.staticflickr.com/8820/16900780428_7093ff1754_c.jpg&quot; rel=&quot;lightbox[jhipsterdjug]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16900780428&quot; title=&quot;My Hipster Getup by Matt Raible, on Flickr&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8820/16900780428_7093ff1754_t.jpg&quot; width=&quot;100&quot; height=&quot;67&quot; alt=&quot;My Hipster Getup&quot; class=&quot;picture&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
To add some humor to my talk, I showed up as a well-dressed Java Developer. Like a mature gentleman might do, I started the evening with a glass of scotch (Glenlivet 12). Throughout the talk I became more hip and adjusted my attire, and beverage, accordingly. As you might expect, my demos had failures. The initial project creation stalled during Bower&apos;s &lt;em&gt;download all JavaScript dependencies&lt;/em&gt;. Luckily, I had a backup and was able to proceed. Towards the end, when I tried to deploy to Heroku, I was presented with a lovely message that &quot;Heroku toolbelt updating, please try again later&quot;. I guess auto-updating has its downsides. &lt;/p&gt;
&lt;p&gt;After finishing the demo, I cracked open a cold PBR to ease my frustration.&lt;/p&gt;
&lt;p&gt;I did two live coding sessions during this presentation; standing on the shoulders of giants to do so. I modeled Josh Long&apos;s &lt;a href=&quot;http://www.joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html&quot;&gt;Getting Started with Spring Boot&lt;/a&gt; to create a quick introduction to Spring Boot. IntelliJ IDEA 14.1 has a &lt;a href=&quot;http://blog.jetbrains.com/idea/2015/03/develop-spring-boot-applications-more-productively-with-intellij-idea-14-1/&quot;&gt;nice way to create Spring Boot projects&lt;/a&gt;, so that came in handy.  For the JHipster portion, I created a blogging app and used relationships and business logic similar to what Julien Dubois did in his &lt;a href=&quot;https://spring.io/blog/2015/03/31/webinar-replay-jhipster-for-spring-boot&quot;&gt;JHipster for Spring Boot Webinar&lt;/a&gt;. Watching Josh and Julien&apos;s demos will give you a similar experience to what DJUG attendees experienced last night, without the download/deployment failures.
&lt;/p&gt;
&lt;p&gt;You can click through my presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or &lt;a href=&quot;http://www.slideshare.net/mraible/get-hip-with-jhipster&quot;&gt;view it on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/46814366&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;You might notice my &lt;a href=&quot;http://www.slideshare.net/mraible/get-hip-with-jhipster/32&quot;&gt;announcement on slide #32&lt;/a&gt; that I&apos;ve signed up to write a book on JHipster.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm9.staticflickr.com/8825/17062597206_60a5bd6e19_c.jpg&quot; data-href=&quot;https://www.flickr.com/photos/mraible/17062597206&quot; rel=&quot;lightbox[jhipsterdjug]&quot; title=&quot;The JHipster Mini-Book by Matt Raible&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8825/17062597206_60a5bd6e19.jpg&quot; width=&quot;500&quot; alt=&quot;The JHipster Mini-Book&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I haven&apos;t started writing the book yet, but I have been talking with &lt;a href=&quot;http://infoq.com&quot;&gt;InfoQ&lt;/a&gt; and other folks about it for several months. I plan to use &lt;a href=&quot;https://github.com/asciidoctor/asciidoctor-gradle-examples&quot;&gt;Asciidoctor and Gradle&lt;/a&gt; as my authoring tools. If you have experience writing a book with these tools, I&apos;d love to hear about it. If you&apos;ve developed an application with JHipster and have some experience in the trenches, I&apos;d love to hear your stories too. 
&lt;/p&gt;
&lt;p&gt;
As I told DJUG last night, I plan to be done with the book in a few months. However, if you&apos;ve been a reader of this blog, you&apos;ll know I&apos;ve been planning to be done with my &apos;66 VW Bus in &lt;em&gt;just a few more months&lt;/em&gt; for quite some time, so that phrase has an interesting meaning for me. &lt;img src=&quot;https://raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;)&quot; title=&quot;;)&quot; /&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/best_practices_for_using_foundation1</id>
        <title type="html">Best Practices for using Foundation with AngularJS Revisited</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/best_practices_for_using_foundation1"/>
        <published>2015-02-19T09:38:42-07:00</published>
        <updated>2015-02-19T15:49:23-07:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="foundation" scheme="http://roller.apache.org/ns/tags/" />
        <category term="css" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="foundationforapps" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;
&lt;a href=&quot;http://foundation.zurb.com&quot;&gt;&lt;img src=&quot;//raibledesigns.com/repository/images/angular-foundation.png&quot; width=&quot;200&quot; class=&quot;picture&quot; alt=&quot;Angular Foundation&quot; style=&quot;margin-top: -12px&quot;&gt;&lt;/a&gt;
A couple weeks ago I wrote about &lt;a href=&quot;//raibledesigns.com/rd/entry/best_practices_for_using_foundation&quot;&gt;using Foundation with AngularJS&lt;/a&gt;. Based on research I&apos;d done, I concluded that it was best to use &lt;a href=&quot;http://foundation.zurb.com/apps/&quot;&gt;Foundation for Apps&lt;/a&gt; for any webapps my client created and &lt;a href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation for Sites&lt;/a&gt; for any websites (e.g. a WordPress-based intranet).&lt;/p&gt;
&lt;p&gt;After doing my initial research, I did some prototyping with Foundation for Apps (F4A). What I discovered is that F4A does &lt;em&gt;not&lt;/em&gt; include all the same components as Foundation for Sites (F5). For example, the top-bar and dropdown functionality are missing. I &lt;a href=&quot;http://foundation.zurb.com/forum/posts/22587-dropdowns-in-foundation-for-apps&quot;&gt;posted my issues to the Foundation Forums&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The response I received:&lt;/p&gt;
&lt;blockquote class=&quot;quote&quot;&gt;
It should work. You would need to copy over all the Scss and global mixins that you used in top-bar or at least all the output CSS from it. Otherwise there is no reason the components won&apos;t fit into the grid.&lt;/blockquote&gt;
&lt;p&gt;I was able to import Foundation for Sites into my project by adding it to bower.json:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;  &quot;dependencies&quot;: {
    &quot;foundation-apps&quot;: &quot;~1.0.2&quot;,
    &quot;foundation&quot;: &quot;~5.5.1&quot;
  }
&lt;/pre&gt;
&lt;p&gt;After doing this, I added the new path to Gulpfile.js:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;var sassPaths = [
  &apos;client/assets/scss&apos;,
  &apos;bower_components/foundation/scss&apos;,
  &apos;bower_components/foundation-apps/scss&apos;
];
&lt;/pre&gt;
&lt;p&gt;
After making this change, the top-bar rendered and my dropdowns worked. Since there was no jQuery in the page, I thought this might be a viable option. However, &lt;a href=&quot;http://foundation.zurb.com/forum/12220-jason-demitri&quot;&gt;Jason Demitri&lt;/a&gt; quickly pointed out it probably wouldn&apos;t work with mobile. He was right.&lt;/p&gt;
&lt;p&gt;While using F4A, I noticed that its components, and much of its look-n-feel, was different than F5. If you look at its &lt;a href=&quot;http://foundation.zurb.com/apps/app-templates/email/#!/&quot;&gt;Email App template&lt;/a&gt;, you&apos;ll see it looks kinda like a mobile app, even in a desktop browser. After trying F4A myself, I decided that F4A wasn&apos;t for us.  First of all, it doesn&apos;t seem to provide a consistent look and feel with a website that&apos;s written using F5. Furthermore, F4A only supports IE10+. In the healthcare industry, there&apos;s a lot of older browsers out there, so my client needs to support IE9 as a minimum.&lt;/p&gt;
&lt;p&gt;For these reasons, I decided to try &lt;a href=&quot;http://pineconellc.github.io/angular-foundation/&quot;&gt;Angular directives for Foundation&lt;/a&gt;. I took a prototype I&apos;d written with F5, removed its JavaScript, added Angular Foundation + Foundation dependencies to bower.json, added references to the respective scripts in index.html and added &apos;mm.foundation&apos; as a dependency in app.js. The experiment worked beautifully and I was quite happy with the results. I shared my findings with the team and we decided &lt;strong&gt;Angular Foundation is the best way to integrate Foundation and AngularJS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;F4A is pretty new and I imagine it&apos;ll add more of F5&apos;s features as it evolves. However, I don&apos;t know if the two will ever be so similar that they can live side-by-side and allow a seamless experience for users. If you&apos;re interested in mixing F4A and F5, you might want to watch Jason Demitri&apos;s &lt;a href=&quot;https://github.com/RelutionDev/foundationUltra&quot;&gt;foundationUltra&lt;/a&gt;. This project combines Angular Foundation, Foundation for Sites, Foundation for Apps and Font Awesome. You can see a demo at &lt;a href=&quot;http://relutiondev.github.io/foundationUltra/&quot;&gt;http://relutiondev.github.io/foundationUltra/&lt;/a&gt;.</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/converting_an_application_to_jhipster</id>
        <title type="html">Converting an Application to JHipster</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/converting_an_application_to_jhipster"/>
        <published>2015-02-12T09:28:59-07:00</published>
        <updated>2015-02-12T15:29:50-07:00</updated> 
        <category term="/Java" label="Java" />
        <category term="groovy" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jhipster" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jpa" scheme="http://roller.apache.org/ns/tags/" />
        <category term="scala" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springboot" scheme="http://roller.apache.org/ns/tags/" />
        <category term="spring" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="dosug" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">&lt;p&gt;&lt;a href=&quot;http://jhipster.github.io/&quot;&gt;&lt;img src=&quot;http://jhipster.github.io/images/logo-jhipster.png&quot; class=&quot;picture&quot; width=&quot;94&quot;&gt;&lt;/a&gt;
I&apos;ve been intrigued by &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt; ever since I first tried it &lt;a href=&quot;http://raibledesigns.com/rd/entry/getting_started_with_jhipster_on&quot;&gt;last September&lt;/a&gt;. I&apos;d worked with AngularJS and Spring Boot quite a bit, and I liked the idea that someone had combined them, adding some nifty features along the way. When I &lt;a href=&quot;http://raibledesigns.com/rd/entry/the_art_of_angularjs_in&quot;&gt;spoke about AngularJS&lt;/a&gt; earlier this month, I included &lt;a href=&quot;http://www.slideshare.net/mraible/the-art-of-angularjs-in-2015/67&quot;&gt;a few slides on JHipster&lt;/a&gt; near the end of the presentation.&lt;/p&gt;
&lt;p&gt;This week, I received an email from someone who attended that presentation. &lt;/p&gt;
&lt;blockquote class=&quot;quote&quot;&gt;
&lt;p style=&quot;margin-top: 0&quot;&gt;Hey Matt,&lt;br&gt;
We met a few weeks back when you presented at DOSUG. You were talking about JHipster which I had been eyeing for a few months and wanted your quick .02 cents.&lt;/p&gt;
&lt;p&gt;
I have built a pretty heavy application over the last 6 months that is using mostly the same tech as JHipster. 
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Spring&lt;/li&gt;
&lt;li&gt;JPA&lt;/li&gt;
&lt;li&gt;AngularJS&lt;/li&gt;
&lt;li&gt;Compass&lt;/li&gt;
&lt;li&gt;Grunt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
It&apos;s ridiculously close for most of the tech stack. So, I was debating rolling it over into a JHipster app to make it a more familiar stack for folks. My concern is that it I will spend months trying to shoehorn it in for not much ROI. Any thoughts on going down this path?
What are the biggest issues you&apos;ve seen in using JHipster?
It seems pretty straightforward except for the entity generators. I&apos;m concerned they are totally different than what I am using. 
&lt;/p&gt;
&lt;p style=&quot;margin-bottom: 0&quot;&gt;
The main difference in what I&apos;m doing compared to JHipster is my almost complete use of groovy instead of old school Java in the app. I would have to be forced into going back to regular java beans...
Thoughts?&lt;/p&gt; 
&lt;/blockquote&gt;
&lt;p&gt;I replied with the following advice:&lt;/p&gt;
&lt;blockquote class=&quot;quote&quot;&gt;
&lt;p style=&quot;margin-top: 0&quot;&gt;
JHipster is great for starting a project, but I don&apos;t know that it buys you much value after the first few months. I would stick with your current setup and consider JHipster for your next project. I&apos;ve only prototyped with it, I haven&apos;t created any client apps or put anything in production. I have with Spring Boot and AngularJS though, so I like that JHipster combines them for me.
&lt;/p&gt;
&lt;p&gt;
JHipster doesn&apos;t generate Scala or Groovy code, but you could still use them in a project as long as you had Maven/Gradle configured properly. 
&lt;/p&gt;
&lt;p style=&quot;margin-bottom: 0&quot;&gt;
You might try generating a new app with JHipster and examine how they&apos;re doing this. At the very least, it can be a good learning tool, even if you&apos;re not using it directly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Java Hipsters: Do you agree with this advice? Have you tried migrating an existing app to JHipster? Are any of you using Scala or Groovy in your JHipster projects?&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/best_practices_for_using_foundation</id>
        <title type="html">Best Practices for using Foundation with AngularJS</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/best_practices_for_using_foundation"/>
        <published>2015-02-05T09:21:50-07:00</published>
        <updated>2015-02-05T15:21:50-07:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="foundation" scheme="http://roller.apache.org/ns/tags/" />
        <category term="css" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
&lt;a href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;&lt;img src=&quot;/repository/images/foundation-for-apps-angularjs.jpg&quot; width=&quot;200&quot; alt=&quot;What You Need To Know About Zurb Foundation for Apps&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
I was recently tasked with doing some research to figure out the best way to use &lt;a
    href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; with &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. Goals for
    this research included:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Identify use cases of Foundation for Sites vs Foundation for Apps and recommend when to use each.&lt;/li&gt;
    &lt;li&gt;Look at pros and cons of using AngularJS with Foundation for Sites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m writing this blog post to get feedback from you, fellow web developers, on your experience with Foundation. Have
    you tried using Foundation for Sites with AngularJS? If so, did you experience any pain?&lt;/p&gt;
&lt;p&gt;From what I can tell, it looks like Foundation for Apps (FA) was created because folks had issues making AngularJS
    and Foundation 5 play nicely together. &lt;a href=&quot;http://zurb.com/article/1312/the-next-foundation&quot;&gt;The Next
        Foundation&lt;/a&gt; explains why FA was created. Reddit&apos;s web_design zone has quite a few &lt;a
        href=&quot;http://www.reddit.com/r/web_design/comments/26btc4/zurb_the_next_foundation_foundation_built_with/&quot;&gt;comments
        related to this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From there, I found a few ZURB blog posts that describe FA&apos;s three main advantages over Foundation for Sites
    (FS):&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1333/foundation-a-new-grid&quot;&gt;A New Grid&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1340/foundation-for-apps-motion-ui-is-the-new-&quot;&gt;Motion UI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1345/design-amazing-single-page-apps-with-the-&quot;&gt;AngularJS Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/21134-foundation-5-and-foundation-apps-differences&quot;&gt;This thread
    on the Foundation forums&lt;/a&gt; seems to indicate that FA would be good for developing applications while FS would be
    good for an intranet built on WordPress (since it&apos;s more of a website than a webapp). &lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
&lt;a href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;&lt;img src=&quot;/repository/images/foundation-for-apps-angularjs.jpg&quot; width=&quot;200&quot; alt=&quot;What You Need To Know About Zurb Foundation for Apps&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
I was recently tasked with doing some research to figure out the best way to use &lt;a
    href=&quot;http://foundation.zurb.com/&quot;&gt;Foundation&lt;/a&gt; with &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. Goals for
    this research included:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Identify use cases of Foundation for Sites vs Foundation for Apps and recommend when to use each.&lt;/li&gt;
    &lt;li&gt;Look at pros and cons of using AngularJS with Foundation for Sites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&apos;m writing this blog post to get feedback from you, fellow web developers, on your experience with Foundation. Have
    you tried using Foundation for Sites with AngularJS? If so, did you experience any pain?&lt;/p&gt;
&lt;p&gt;From what I can tell, it looks like Foundation for Apps (FA) was created because folks had issues making AngularJS
    and Foundation 5 play nicely together. &lt;a href=&quot;http://zurb.com/article/1312/the-next-foundation&quot;&gt;The Next
        Foundation&lt;/a&gt; explains why FA was created. Reddit&apos;s web_design zone has quite a few &lt;a
        href=&quot;http://www.reddit.com/r/web_design/comments/26btc4/zurb_the_next_foundation_foundation_built_with/&quot;&gt;comments
        related to this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From there, I found a few ZURB blog posts that describe FA&apos;s three main advantages over Foundation for Sites
    (FS):&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1333/foundation-a-new-grid&quot;&gt;A New Grid&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1340/foundation-for-apps-motion-ui-is-the-new-&quot;&gt;Motion UI&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://zurb.com/article/1345/design-amazing-single-page-apps-with-the-&quot;&gt;AngularJS Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/21134-foundation-5-and-foundation-apps-differences&quot;&gt;This thread
    on the Foundation forums&lt;/a&gt; seems to indicate that FA would be good for developing applications while FS would be
    good for an intranet built on WordPress (since it&apos;s more of a website than a webapp). &lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    Foundation for apps is for making responsive web apps vs responsive web sites. The difference is in the structure
    of an app. They usually take up the screen and instead of the page scrolling, content in the app scrolls. Apps
    employ stateful views, so a view can change without reloading the app, creating a better user experience.
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;http://foundation.zurb.com/forum/posts/16594-foundation-for-sites-to-foundation-for-apps-preparation&quot;&gt;This
    other thread&lt;/a&gt; backs up that notion.&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;Foundation for Apps will be an additional version of Foundation - not a replacement.&lt;/p&gt;

    &lt;p&gt;This means Foundation for Sites will continue on along side the new Apps version. Ink will be Foundation for
        Emails.&lt;/p&gt;

    &lt;p&gt;Having said that, choosing which one to use simply depends on the type of site you need to build. The Apps grid
        is better suited to make web apps or apps that can be wrapped for native.&lt;/p&gt;

    &lt;p&gt;The syntax is different (on purpose) and we are taking great care to make it easy to get.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;You should continue using Foundation for sites as long as you need traditional scrolling websites. We don&apos;t
        expect people to convert an existing site to an app unless they need to re-do their site anyways.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also found &lt;a href=&quot;https://github.com/pineconellc/angular-foundation&quot;&gt;Angular Foundation&lt;/a&gt; which is a &quot;a port of
    the AngularUI team&apos;s excellent angular-bootstrap project for use in the Foundation framework&quot;.&lt;/p&gt;

&lt;p&gt;The problem I see with using Angular Foundation over Foundation for Apps is that it&apos;s maintained by folks that aren&apos;t
    developing Foundation. It&apos;s more of a &quot;here&apos;s some Bootstrap widgets as Foundation widgets&quot;.&lt;/p&gt;

&lt;p&gt;It could also come down to IE support. Angular Foundation &lt;a href=&quot;http://foundation.zurb.com/docs/compatibility.html&quot;&gt;supports IE9+&lt;/a&gt; while Foundation for Apps is IE10+. From &lt;a
    href=&quot;http://www.htmlxprs.com/post/46/what-you-need-to-know-about-zurb-foundation-for-apps&quot;&gt;What You Need To Know
    About Zurb Foundation for Apps&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;p style=&quot;margin-top: 0&quot;&gt;Foundation for Apps works with all of the modern browsers including Internet Explorer 10. It doesn&apos;t support IE9
        and other older browsers because of the new CSS3 features and issues of AngularJS in older browsers. Below is
        the snapshot of the compatibility list grabbed from Zurb website.&lt;/p&gt;

    &lt;p style=&quot;margin-bottom: 0&quot;&gt;&lt;img src=&quot;//raibledesigns.com/repository/images/compatibility-foundation-for-apps.jpg&quot; alt=&quot;Foundation for Apps compatibility&quot; width=&quot;100%&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wrote this article in order to publish my research findings about Foundation for Apps vs. Foundation for Sites. It
seems like the natural thing to do is to use FA for any webapps we create and FS for any websites (e.g. WordPress sites). Do you agree with these findings?
Any other &lt;em&gt;recommended practices&lt;/em&gt; for integrating Foundation with AngularJS?
&lt;/p&gt;
&lt;p class=&quot;alert alert-info&quot;&gt;&lt;strong&gt;Sidenote&lt;/strong&gt;: For the project I&apos;m working on, we haven&apos;t chose a backend framework yet. We&apos;ve chosen &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; as our deployment
    platform, so there&apos;s a plethora of languages and frameworks to choose from.
    It&apos;s too bad &lt;a href=&quot;https://github.com/jhipster/generator-jhipster/issues/1039&quot;&gt;JHipster doesn&apos;t support
    Foundation&lt;/a&gt;. I could probably sell the team on Java + Spring Boot pretty easily if it did.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/the_art_of_angularjs_in</id>
        <title type="html">The Art of AngularJS in 2015</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/the_art_of_angularjs_in"/>
        <published>2015-02-04T09:14:57-07:00</published>
        <updated>2015-02-04T15:17:31-07:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="denver" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="grunt" scheme="http://roller.apache.org/ns/tags/" />
        <category term="dosug" scheme="http://roller.apache.org/ns/tags/" />
        <category term="http2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="coffeescript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="protractor" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="html5" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jasmine" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;I&apos;ve been tracking statistics on jobs and skills for JavaScript MVC frameworks ever since I &lt;a href=&quot;//raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Compared JVM Web Frameworks at Devoxx
    France in 2013&lt;/a&gt;. At that time, Backbone was the dominant framework.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_c.jpg&quot; title=&quot;2013 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255644670&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_m.jpg&quot; width=&quot;240&quot; alt=&quot;2013 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_c.jpg&quot; title=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255384478&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_m.jpg&quot; width=&quot;240&quot; alt=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Last year, I updated those statistics for a &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs&quot;&gt;presentation
    on AngularJS&lt;/a&gt; at Denver&apos;s Derailed. Angular had a similar amount of jobs as Backbone and a lot of people added it
    to their LinkedIn profiles. I found that Ember had grown around 300%, Backbone 200% and Angular 1000%!&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_c.jpg&quot; title=&quot;2014 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16256817639&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_m.jpg&quot; width=&quot;240&quot; alt=&quot;2014 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_c.jpg&quot; title=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443061465&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_m.jpg&quot; width=&quot;240&quot; alt=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Before presenting on AngularJS at &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;last night&apos;s Denver Open
    Source Users Group&lt;/a&gt;, I updated these statistics once again. The charts below show how the number of jobs for
    Angular has doubled in the last year, while jobs for Ember and Backbone have fallen slightly. As far as skills,
    developers learning Ember and Backbone has increased 200%, while skilled Angular folks has risen 400%.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_c.jpg&quot; title=&quot;2015 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443001655&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_m.jpg&quot; width=&quot;240&quot; alt=&quot;2015 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_c.jpg&quot; title=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16257092107&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_m.jpg&quot; width=&quot;240&quot; alt=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Yes, AngularJS has experienced &lt;em&gt;huge&lt;/em&gt; growth in the last couple of years. You might even say it&apos;s the &lt;em&gt;Struts
    of the JavaScript world&lt;/em&gt;.
&lt;/p&gt;&lt;p&gt;For the presentation I delivered last night, I made a number of improvements over last year&apos;s. I added a live coding
    demo based on my &lt;a href=&quot;&quot;&gt;Getting Started with AngularJS&lt;/a&gt; tutorial. I used IntelliJ&apos;s &lt;a href=&quot;https://www.jetbrains.com/idea/help/live-templates.html&quot;&gt;live templates&lt;/a&gt; to make it look easy. However,
    since the audience was quiet, and some were falling asleep, I skipped over the &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;testing&lt;/a&gt; demo.&lt;/p&gt;
</summary>
        <content type="html">&lt;p&gt;I&apos;ve been tracking statistics on jobs and skills for JavaScript MVC frameworks ever since I &lt;a href=&quot;//raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Compared JVM Web Frameworks at Devoxx
    France in 2013&lt;/a&gt;. At that time, Backbone was the dominant framework.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f_c.jpg&quot; title=&quot;2013 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255644670&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7452/16255644670_e426fb455f.jpg&quot; width=&quot;500&quot; alt=&quot;2013 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd_c.jpg&quot; title=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16255384478&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7411/16255384478_67712c17dd.jpg&quot; width=&quot;500&quot; alt=&quot;2013 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Last year, I updated those statistics for a &lt;a href=&quot;//raibledesigns.com/rd/entry/the_art_of_angularjs&quot;&gt;presentation
    on AngularJS&lt;/a&gt; at Denver&apos;s Derailed. Angular had a similar amount of jobs as Backbone and a lot of people added it
    to their LinkedIn profiles. I found that Ember had grown around 300%, Backbone 200% and Angular 1000%!&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a_c.jpg&quot; title=&quot;2014 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16256817639&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16256817639_b1ea05213a.jpg&quot; width=&quot;500&quot; alt=&quot;2014 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c_c.jpg&quot; title=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443061465&quot;&gt;&lt;img src=&quot;https://farm8.staticflickr.com/7381/16443061465_e89eda261c.jpg&quot; width=&quot;500&quot; alt=&quot;2014 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Before presenting on AngularJS at &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;last night&apos;s Denver Open
    Source Users Group&lt;/a&gt;, I updated these statistics once again. The charts below show how the number of jobs for
    Angular has doubled in the last year, while jobs for Ember and Backbone have fallen slightly. As far as skills,
    developers learning Ember and Backbone has increased 200%, while skilled Angular folks has risen 400%.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3_c.jpg&quot; title=&quot;2015 Dice Jobs for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16443001655&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8637/16443001655_fb8593e2f3.jpg&quot; width=&quot;500&quot; alt=&quot;2015 Dice Jobs for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black;&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65_c.jpg&quot; title=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks by Matt Raible, on Flickr&quot; rel=&quot;lightbox[artofangular2015]&quot; data-href=&quot;https://www.flickr.com/photos/mraible/16257092107&quot;&gt;&lt;img src=&quot;https://farm9.staticflickr.com/8628/16257092107_c4a9735b65.jpg&quot; width=&quot;500&quot; alt=&quot;2015 LinkedIn Skills for JavaScript MVC Frameworks&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Yes, AngularJS has experienced &lt;em&gt;huge&lt;/em&gt; growth in the last couple of years. You might even say it&apos;s the &lt;em&gt;Struts
    of the JavaScript world&lt;/em&gt;. I like to say that &lt;a href=&quot;http://www.infoq.com/news/2013/04/struts1-eol&quot;&gt;Struts 1.x was the &apos;Killer App&apos; for J2EE&lt;/a&gt; back in the day.
&lt;/p&gt;&lt;p&gt;For the presentation I delivered last night, I made a number of improvements over last year&apos;s. I added a live coding
    demo based on my &lt;a href=&quot;&quot;&gt;Getting Started with AngularJS&lt;/a&gt; tutorial. I used IntelliJ&apos;s &lt;a href=&quot;https://www.jetbrains.com/idea/help/live-templates.html&quot;&gt;live templates&lt;/a&gt; to make it look easy. However,
    since the audience was quiet, and some were falling asleep, I skipped over the &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;testing&lt;/a&gt; demo.&lt;/p&gt;
&lt;p&gt;I added a few slides on &lt;a href=&quot;http://foundation.zurb.com/apps/&quot;&gt;Foundation for Apps&lt;/a&gt; (FA). We&apos;ve selected
    AngularJS and Foundation on my current project, and I&apos;ve been researching how to integrate the two lately. FA is one
    solution I&apos;ve found, &lt;a href=&quot;http://pineconellc.github.io/angular-foundation/&quot;&gt;Angular Foundation&lt;/a&gt; is
    another. If you know of others, please let me know.&lt;/p&gt;
&lt;p&gt;For Java developers getting started with Angular, I recommended &lt;a href=&quot;http://jhipster.github.io/&quot;&gt;JHipster&lt;/a&gt;. I
    talked about its foundational frameworks and project options when creating your project. I included screenshots of
    its slick metrics UI and code generation features.&lt;/p&gt;
&lt;p&gt;I also added a slide for Dave Syer&apos;s excellent five-part series on Spring and AngularJS:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/12/spring-and-angular-js-a-secure-single-page-application&quot;&gt;Spring and
        Angular JS: A Secure Single Page Application&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii&quot;&gt;The Login Page:
        Angular JS and Spring Security Part II&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/20/the-resource-server-angular-js-and-spring-security-part-iii&quot;&gt;The
        Resource Server: Angular JS and Spring Security Part III&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/01/28/the-api-gateway-pattern-angular-js-and-spring-security-part-iv&quot;&gt;The
        API Gateway Pattern: Angular JS and Spring Security Part IV&lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v&quot;&gt;SSO with
        OAuth2: Angular JS and Spring Security Part V&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, I added a number of slides on &lt;a href=&quot;http://www.infoq.com/news/2014/10/angular-2-atscript&quot;&gt;Angular 2.0&lt;/a&gt;.
    I encouraged folks to checkout &lt;a href=&quot;http://12factor.net/&quot;&gt;The Twelve-Factor App&lt;/a&gt; and James Ward&apos;s
    &lt;a href=&quot;http://www.jamesward.com/2014/12/03/java-doesnt-suck-youre-just-using-it-wrong&quot;&gt;Java Doesn&#8217;t Suck &#8211; You&#8217;re Just Using it Wrong&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;The discussion with the audience was great, particularly around HTTP/2 and minification/concatenation of assets.
    Thanks to all who attended, I really enjoyed having the opportunity to share what I&apos;ve learned.&lt;/p&gt;
&lt;p&gt;You can click through my presentation below, download it from &lt;a href=&quot;//raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a href=&quot;http://www.slideshare.net/mraible/the-art-of-angularjs-in-2015&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/44244006&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;If you live in Denver, there&apos;s a number of interesting meetups happening in the next couple months. &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Monday, February 16: &lt;a href=&quot;http://www.meetup.com/HTML5-Denver-Users-Group/events/220053261/&quot;&gt;Introduction to ReactJS&lt;/a&gt; at HTML5 Denver&lt;/li&gt;
    &lt;li&gt;Tuesday &amp;amp; Wednesday, March 3rd and 4th: &lt;a href=&quot;http://thingmonk.com/&quot;&gt;ThingMonk&lt;/a&gt; at &lt;a href=&quot;http://www.drinkmilehighspirits.com/&quot;&gt;&lt;em&gt;a distillery!&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Wednesday, April 8: I&apos;ll be speaking about JHipster at Denver Java User Group&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m also looking for speakers to teach programming to kids at &lt;a href=&quot;http://www.meetup.com/Devoxx4Kids-Denver/&quot;&gt;Devoxx4Kids Denver&lt;/a&gt;.
    Let me know if you have a fun topic you&apos;d like to present.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/testing_angularjs_applications</id>
        <title type="html">Testing AngularJS Applications</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/testing_angularjs_applications"/>
        <published>2015-02-02T10:11:56-07:00</published>
        <updated>2015-02-02T20:31:21-07:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="karma" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="node" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jasmine" scheme="http://roller.apache.org/ns/tags/" />
        <category term="git" scheme="http://roller.apache.org/ns/tags/" />
        <category term="protractor" scheme="http://roller.apache.org/ns/tags/" />
        <category term="npm" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. It describes
    how to test a simple AngularJS application. In a previous article, &lt;a
            href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll learn&lt;/h3&gt;
&lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
        href=&quot;http://angular.github.io/protractor/#/&quot;&gt;Protractor&lt;/a&gt; for integration testing. Angular&apos;s documentation
    has a
    good &lt;a href=&quot;https://docs.angularjs.org/guide/unit-testing&quot;&gt;developer&apos;s guide to unit testing&lt;/a&gt; if you&apos;d like
    more information on testing and why it&apos;s important. &lt;/p&gt;
&lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing manually. This
    manual testing will take longer and longer as your codebase grows. &lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. We recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;</summary>
        <content type="html">&lt;p&gt;This article is the second in a series about learning &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. It describes
    how to test a simple AngularJS application. In a previous article, &lt;a
            href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting Started with AngularJS&lt;/a&gt;, I
    showed how to develop a simple search and edit feature.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll learn&lt;/h3&gt;
&lt;p&gt;You&apos;ll learn to use &lt;a href=&quot;http://jasmine.github.io/&quot;&gt;Jasmine&lt;/a&gt; for unit testing controllers and &lt;a
        href=&quot;http://angular.github.io/protractor/#/&quot;&gt;Protractor&lt;/a&gt; for integration testing. Angular&apos;s documentation
    has a
    good &lt;a href=&quot;https://docs.angularjs.org/guide/unit-testing&quot;&gt;developer&apos;s guide to unit testing&lt;/a&gt; if you&apos;d like
    more information on testing and why it&apos;s important. &lt;/p&gt;
&lt;p&gt;The best reason for writing tests is to automate your testing. Without tests, you&apos;ll likely be testing manually. This
    manual testing will take longer and longer as your codebase grows. &lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. We recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Get the tutorial project&lt;/h3&gt;
&lt;p&gt;Clone the angular-tutorial repository using &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt; and
    install the dependencies. &lt;/p&gt;

&lt;pre&gt;
git clone https://github.com/mraible/angular-tutorial.git
cd angular-tutorial
npm install&lt;/pre&gt;
    &lt;p&gt;If you haven&apos;t completed the &lt;a href=&quot;//raibledesigns.com/rd/entry/getting_started_with_angularjs&quot;&gt;Getting
        Started with AngularJS&lt;/a&gt; tutorial, you should peruse it so you
        understand how this application works. You can also simply start the app with &amp;quot;npm start&amp;quot; and view it
        in your browser at &lt;a href=&quot;http://localhost:8000/app/&quot;&gt;http://localhost:8000/app/&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Test the SearchController&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/search_test.js&lt;/code&gt; and populate it with the basic test infrastructure. This code
            sets up a mock &lt;code&gt;SearchService&lt;/code&gt; that has the first function we want to test:
            &lt;code&gt;query(term)&lt;/code&gt;. It uses &lt;code&gt;$provide&lt;/code&gt; to override the default &lt;code&gt;SearchService&lt;/code&gt;.
            &lt;a href=&quot;http://stackoverflow.com/questions/20828179/angular-unit-test-controllers-mocking-service-inside-controller&quot;&gt;Angular
                unit-test controllers - mocking service inside controller&lt;/a&gt; was a useful question on Stack Overflow
            for figuring out how to mock services in controllers.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;&apos;use strict&apos;;

describe(&apos;myApp.search module&apos;, function() {
    var mockSearchService;

    beforeEach(module(&apos;myApp.search&apos;, function($provide) {
        mockSearchService = {query: function(term) {}};
        $provide.value(&quot;SearchService&quot;, mockSearchService);
    }));
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;karma.conf.js&lt;/code&gt; (in the root directory) to add the search implementation and test.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;files : [
  ...
  &apos;app/components/**/*.js&apos;,
  &apos;app/search/search.js&apos;,
  &apos;app/search/search_test.js&apos;,
  &apos;app/view*/**/*.js&apos;
],&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add the first test to &lt;code&gt;search_test.js&lt;/code&gt;. This test verifies that setting a search term and
            executing the &lt;code&gt;search()&lt;/code&gt; function will call the service and return results. You can see the results returned
            from the service are mocked with
            &lt;code&gt;deferred.resolve()&lt;/code&gt;. The &lt;code&gt;deferred.resolve()&lt;/code&gt; call is &lt;a
                    href=&quot;http://entwicklertagebuch.com/blog/2013/10/how-to-handle-angularjs-promises-in-jasmine-unit-tests/&quot;&gt;how
                to handle promises in unit tests&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search by term&apos;, function() {
    var scope, rootScope, controller, deferred;

	// setup the controller with dependencies.
    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();
        controller = $controller(&apos;SearchController&apos;, {$scope: scope, SearchService: mockSearchService });
        deferred = $q.defer();
    }));

    it(&apos;should search when a term is set and search() is called&apos;, function() {
        spyOn(mockSearchService, &apos;query&apos;).andReturn(deferred.promise);
        scope.term = &apos;M&apos;;
        scope.search();
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;}});
        rootScope.$apply();
        expect(scope.searchResults).toEqual({name: &quot;Peyton Manning&quot;});
    });
});&lt;/pre&gt;
        &lt;p&gt;Related: &lt;a href=&quot;http://angular-tips.com/blog/2014/03/introduction-to-unit-test-spies/&quot;&gt;Introduction to
            Unit Test: Spies&lt;/a&gt; is a good introduction to using spies in unit tests.&lt;br/&gt;&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Run the following command on the command line to start the Karma test runner. You can leave this process
            running and new tests will be run automatically. You can change the mocked data and expectation to see your
            test fail.&lt;/p&gt;
        &lt;pre&gt;npm test&lt;/pre&gt;
        &lt;div class=&quot;alert alert-success&quot;&gt;&lt;strong&gt;Running Tests from IntelliJ IDEA&lt;/strong&gt;&lt;br&gt;
            See &lt;a href=&quot;https://www.jetbrains.com/idea/help/running-unit-tests-on-karma.html&quot;&gt;Running Unit Tests on
                Karma&lt;/a&gt; to learn how to run your tests from IntelliJ IDEA.
        &lt;/div&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add a test to verify a search occurs automatically when the term is in the URL. Notice how the code structure had
            to change a bit to handle &lt;code&gt;$routeParams&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search by term automatically&apos;, function() {
    var scope, rootScope, controller, location, deferred;

    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();

        // in this case, expectations need to be setup before controller is initialized
        var routeParams = {&quot;term&quot;: &quot;peyton&quot;};
        deferred = $q.defer();
        spyOn(mockSearchService, &apos;query&apos;).andReturn(deferred.promise);
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;}});

        controller = $controller(&apos;SearchController&apos;, {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
    }));

    it(&apos;should search automatically when a term is on the URL&apos;, function() {
        rootScope.$apply();
        expect(scope.searchResults).toEqual({name: &quot;Peyton Manning&quot;});
    });
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add a test to verify the &lt;code&gt;EditController&lt;/code&gt; works as expected.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;edit person&apos;, function() {
    var scope, rootScope, controller, location, deferred;

    beforeEach(inject(function($rootScope, $controller, $q) {
        rootScope = $rootScope;
        scope = $rootScope.$new();

        // expectations need to be setup before controller is initialized
        var routeParams = {&quot;id&quot;: &quot;1&quot;};
        deferred = $q.defer();
        spyOn(mockSearchService, &apos;fetch&apos;).andReturn(deferred.promise);
        deferred.resolve({data: {name: &quot;Peyton Manning&quot;, address: {street: &quot;12345 Blake Street&quot;, city: &quot;Denver&quot;}}});

        controller = $controller(&apos;EditController&apos;, {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
    }));

    it(&apos;should fetch a single record&apos;, function() {
        rootScope.$apply();
        expect(scope.person.name).toBe(&quot;Peyton Manning&quot;);
        expect(scope.person.address.street).toBe(&quot;12345 Blake Street&quot;);
    });
});&lt;/pre&gt;
&lt;p&gt;After adding this test, you&apos;ll likely see the following error in your terminal.&lt;/p&gt;
&lt;pre class=&quot;brush: shell&quot;&gt;
Chrome 40.0.2214 (Mac OS X 10.10.2) myApp.search module edit person should fetch a single record FAILED
	fetch() method does not exist
	TypeError: Cannot read property &apos;name&apos; of undefined
&lt;/pre&gt;
        &lt;p&gt;This happens because the &lt;code&gt;mockSearchService&lt;/code&gt; does not have a fetch method defined. Modify the &lt;code&gt;beforeEach()&lt;/code&gt; on line 7 to add this function.&lt;/p&gt;
    &lt;pre class=&quot;brush: js&quot;&gt;mockSearchService = {query: function(term) {}, fetch: function(id) {}};&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Extra Credit&lt;/h3&gt;
&lt;p&gt;Create a test for saving a person. &lt;a
        href=&quot;http://stackoverflow.com/questions/13664144/how-to-unit-test-angularjs-controller-with-location-service&quot;&gt;Here&apos;s
    a question on Stack Overflow&lt;/a&gt; that might help you verify the location after a save has been performed.&lt;/p&gt;
&lt;h3&gt;Test the Search Feature&lt;/h3&gt;
&lt;p&gt;To test if the application works end-to-end, you can write scenarios with &lt;a
        href=&quot;http://angular.github.io/protractor/&quot;&gt;Protractor&lt;/a&gt;. These are also known as integration tests, as they
    test
    the &lt;em&gt;integration&lt;/em&gt; between all layers of your application.&lt;/p&gt;
&lt;p&gt;To verify end-to-end tests work in the project before you begin, run the following command in one terminal
    window:&lt;/p&gt;
&lt;pre&gt;npm start&lt;/pre&gt;
&lt;p&gt;Then in another window, run the following to execute the tests:&lt;/p&gt;
&lt;pre&gt;npm run protractor&lt;/pre&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Write your first integration test to verify you can navigate to /search and enter a search term to see
            results. Add the following to &lt;code&gt;e2e-tests/scenarios.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;search&apos;, function() {
  var searchTerm = element(by.model(&apos;term&apos;));
  var searchButton = element(by.id(&apos;search&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/search&apos;);
  });

  it(&apos;should allow searching at /search&apos;, function() {
    searchTerm.sendKeys(&quot;M&quot;);
    searchButton.click().then(function() {
      expect(element.all(by.repeater(&apos;person in searchResults&apos;)).count()).toEqual(3);
    });
  });
});&lt;/pre&gt;
        &lt;p&gt;The &amp;quot;searchTerm&amp;quot; variable represents the input field. The &lt;code&gt;by.model()&lt;/code&gt; syntax binds to
            the &amp;quot;ng-model&amp;quot; attribute you defined in the HTML. &lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Run &amp;quot;npm run protractor&amp;quot;. This should fail with the following error.&lt;/p&gt;
&lt;pre class=&quot;brush: bash&quot;&gt;[launcher] Running 1 instances of WebDriver
Selenium standalone server started at http://172.16.6.39:64230/wd/hub
...F
Failures:
  1) my app search should allow searching at /search
   Message:
     NoSuchElementError: No element found using locator: By.id(&quot;search&quot;)&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;To fix, you need to add an &amp;quot;id&amp;quot; attribute to the Search button in
            &lt;code&gt;app/search/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;form ng-submit=&quot;search()&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; name=&quot;search&quot; ng-model=&quot;term&quot;&amp;gt;
    &amp;lt;button id=&quot;search&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;Run the tests again using &amp;quot;npm run protractor&amp;quot;. They should all pass this time.&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Write another test to verify editing a user displays their information.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;edit person&apos;, function() {
  var name = element(by.model(&apos;person.name&apos;));
  var street = element(by.model(&apos;person.address.street&apos;));
  var city = element(by.model(&apos;person.address.city&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/edit/1&apos;);
  });

  it(&apos;should allow viewing a person&apos;, function() {
    // getText() doesn&apos;t work with input elements, see the following for more information:
    // https://github.com/angular/protractor/blob/master/docs/faq.md#the-result-of-gettext-from-an-input-element-is-always-empty
    expect(name.getAttribute(&apos;value&apos;)).toEqual(&quot;Peyton Manning&quot;);
    expect(street.getAttribute(&apos;value&apos;)).toEqual(&quot;1234 Main Street&quot;);
    expect(city.getAttribute(&apos;value&apos;)).toEqual(&quot;Greenwood Village&quot;);
  });
});&lt;/pre&gt;
        &lt;p&gt;Verify it works with &amp;quot;npm run protractor&amp;quot;.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Finally, write a test to verify you can save a person and their details are updated. Figuring out how to
            verify the URL after it changed was assisted by &lt;a href=&quot;https://github.com/angular/protractor/issues/610&quot;&gt;this
                issue&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;describe(&apos;save person&apos;, function() {
  var name = element(by.model(&apos;person.name&apos;));
  var save = element(by.id(&apos;save&apos;));

  beforeEach(function() {
    browser.get(&apos;index.html#/edit/1&apos;);
  });

  it(&apos;should allow updating a name&apos;, function() {
    name.sendKeys(&quot; Updated&quot;);
    save.click().then(function() {
      // verify url set back to search results
      browser.driver.wait(function() {
        return browser.driver.getCurrentUrl().then(function(url) {
          expect(url).toContain(&apos;/search/Peyton%20Manning%20Updated&apos;);
          return url;
        });
      });
      // verify one element matched this change
      expect(element.all(by.repeater(&apos;person in searchResults&apos;)).count()).toEqual(1);
    });
  });
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;When you run this test with &amp;quot;npm run protractor&amp;quot;, it should fail because there&apos;s no element with
            &lt;code&gt;id=&amp;quot;save&amp;quot;&lt;/code&gt; in &lt;code&gt;app/search/edit.html&lt;/code&gt;. Add it to the Save button in this
            file and try again. You should see something similar to the following:&lt;/p&gt;
&lt;pre class=&quot;brush: bash&quot;&gt;Finished in 4.478 seconds
6 tests, 9 assertions, 0 failures&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/&quot;&gt;https://github.com/mraible/angular-tutorial&lt;/a&gt; on the &lt;strong&gt;testing&lt;/strong&gt; branch.
&lt;/p&gt;
&lt;p&gt;There are two commits that make the changes for the two main steps in this tutorial:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/mraible/angular-tutorial/commit/f3551cc84bd2f0fb35b4dd9ac38cbf6f417c106f&quot;&gt;Unit Tests&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/mraible/angular-tutorial/commit/12e37eb7ad8cde82c6ec92c8562700a7fb6952cc&quot;&gt;Integration Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on testing AngularJS applications. The first couple AngularJS applications
I developed didn&apos;t have tests and required a lot of manual testing to verify their quality. After learning that testing AngularJS
apps is pretty easy, I now do it on all my projects. Hopefully this tutorial motivates you to do do the same.
&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/getting_started_with_angularjs</id>
        <title type="html">Getting Started with AngularJS</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/getting_started_with_angularjs"/>
        <published>2015-01-29T11:12:38-07:00</published>
        <updated>2015-09-23T06:44:03-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="npm" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="git" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="node" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;I was hired by my current client in November to help them choose a technology stack for developing modern web applications.
   In our first sprint, we decided to look at JavaScript MVC frameworks. I suggested &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;, &lt;a href=&quot;http://emberjs.com/&quot;&gt;Ember.js&lt;/a&gt; and &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;. Since
    most of the team was new to JavaScript MVC, I decided to create a tutorial for them. I tried to make it easy so they
    could learn how to write a simple web application with AngularJS. I thought others could benefit from this article as well,
so I asked (and received) permission from my client to publish it here.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll build&lt;/h3&gt;
&lt;p&gt;You&apos;ll build a simple web application with AngularJS. You&apos;ll also add search and edit features with mock data.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;</summary>
        <content type="html">&lt;p&gt;I was hired by my current client in November to help them choose a technology stack for developing modern web applications.
   In our first sprint, we decided to look at JavaScript MVC frameworks. I suggested &lt;a href=&quot;https://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;, &lt;a href=&quot;http://emberjs.com/&quot;&gt;Ember.js&lt;/a&gt; and &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;. Since
    most of the team was new to JavaScript MVC, I decided to create a tutorial for them. I tried to make it easy so they
    could learn how to write a simple web application with AngularJS. I thought others could benefit from this article as well,
so I asked (and received) permission from my client to publish it here.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll build&lt;/h3&gt;
&lt;p&gt;You&apos;ll build a simple web application with AngularJS. You&apos;ll also add search and edit features with mock data.&lt;/p&gt;
&lt;h3&gt;What you&apos;ll need&lt;/h3&gt;
&lt;ul&gt;
    &lt;li&gt;About 15-30 minutes&lt;/li&gt;
    &lt;li&gt;A favorite text editor or IDE. I recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; installed.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and NPM installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a simple web application&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Clone the angular-seed repository using &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;
git clone https://github.com/angular/angular-seed.git angular-tutorial
cd angular-tutorial&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;There are two kinds of dependencies in this project: tools and angular framework code. The tools help manage
            and test the application.&lt;/p&gt;
        &lt;ul&gt;
            &lt;li&gt;To get the tools that depend upon via &lt;code&gt;npm&lt;/code&gt;, the &lt;a href=&quot;https://www.npmjs.org/&quot;&gt;node
                package manager&lt;/a&gt;.
            &lt;/li&gt;
            &lt;li&gt;To get the angular code via &lt;code&gt;bower&lt;/code&gt;, a &lt;a href=&quot;http://bower.io/&quot;&gt;client-side code package
                manager&lt;/a&gt;.
            &lt;/li&gt;
        &lt;/ul&gt;
        &lt;p&gt;The project has preconfigured &lt;code&gt;npm&lt;/code&gt;&amp;nbsp;to automatically run &lt;code&gt;bower&lt;/code&gt;&amp;nbsp;so you can
            simply do:&lt;/p&gt;
        &lt;pre&gt;npm install&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Run the application&lt;/h3&gt;
&lt;p&gt;The project is configured with a simple development web server. The simplest way to start this server is:&lt;/p&gt;
&lt;pre&gt;npm start&lt;/pre&gt;
&lt;p&gt;Now browse to the app at &lt;code&gt;&lt;a href=&quot;http://localhost:8000/app/index.html&quot;&gt;http://localhost:8000/app/&lt;/a&gt;&lt;/code&gt;.
&lt;/p&gt;
&lt;h3&gt;Add a search feature&lt;/h3&gt;
&lt;p&gt;To add a search feature, open the project in an IDE or your favorite text editor. For IntelliJ IDEA, use File &amp;gt;
    New Project &amp;gt; Static Web and point to the directory you cloned angular-seed to.&lt;/p&gt;
&lt;h3&gt;The Basics&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Create an &lt;code&gt;app/search/index.html&lt;/code&gt; file with the following HTML:&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;form ng-submit=&quot;search()&quot;&amp;gt;
    &amp;lt;input type=&quot;search&quot; name=&quot;search&quot; ng-model=&quot;term&quot;&amp;gt;
    &amp;lt;button&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/search.js&lt;/code&gt; and define the routes (URLs) and controller for the search feature.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;myApp.search&apos;, [&apos;ngRoute&apos;])

    .config([&apos;$routeProvider&apos;, function ($routeProvider) {
        $routeProvider
            .when(&apos;/search&apos;, {
                templateUrl: &apos;search/index.html&apos;,
                controller: &apos;SearchController&apos;
            })
    }])

    .controller(&apos;SearchController&apos;, function () {
        console.log(&quot;In Search Controller...&quot;);
    });
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/app.js&lt;/code&gt; and add the &amp;quot;myApp.search&amp;quot; module you created above.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;angular.module(&apos;myApp&apos;, [
  &apos;ngRoute&apos;,
  &apos;myApp.view1&apos;,
  &apos;myApp.view2&apos;,
  &apos;myApp.version&apos;,
  &apos;myApp.search&apos;
])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/index.html&lt;/code&gt; and add a link to the search.js file.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;script src=&quot;view2/view2.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;search/search.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;components/version/version.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Refresh your browser and navigate to &lt;a href=&quot;http://localhost:8000/app/#/search&quot;&gt;http://localhost:8000/app/#/search.&lt;/a&gt;
            You should see an input field and search button. You should also see a log message printed in your browser&apos;s
            console.
            In Chrome, you can view the console using View &amp;gt; Developer &amp;gt; JavaScript Console. You can make it
            easier to navigate to this page by adding a menu item in &lt;code&gt;app/index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;ul class=&quot;menu&quot;&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/view1&quot;&amp;gt;view1&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/view2&quot;&amp;gt;view2&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#/search&quot;&amp;gt;search&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This section has shown you how to add a new controller and view to a basic AngularJS application. This was fairly simple to create.
    The next section shows you how to create a fake backend API. &lt;/p&gt;
&lt;h3&gt;The Backend&lt;/h3&gt;
&lt;p&gt;To get search results, you&apos;re going to create a SearchService
    that makes HTTP requests. These HTTP requests will be handled by a mock backend using some of Angular&apos;s built-in
    mocking tools. The backend implementation was created using &lt;a
        href=&quot;http://www.jeremyzerr.com/angularjs-backend-less-development-using-httpbackend-mock&quot;&gt;AngularJS Backend-less Development Using a $httpBackend Mock&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Add a &lt;code&gt;SearchService&lt;/code&gt; to &lt;code&gt;app/search/search.js&lt;/code&gt;. This is done in Angular using its &lt;a
            href=&quot;https://docs.angularjs.org/guide/providers#factory-recipe&quot;&gt;Factory Recipe&lt;/a&gt;. Make sure to remove the
            semicolon from the &lt;code&gt;.controller&lt;/code&gt; code block.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.controller(&apos;SearchController&apos;, function () {
    console.log(&quot;In Search Controller...&quot;);
})

.factory(&apos;SearchService&apos;, function ($http) {
    var service = {
        query: function (term) {
            return $http.get(&apos;/search/&apos; + term);
        }
    };
    return service;
});
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Inject the &lt;code&gt;SearchService&lt;/code&gt; into the &lt;code&gt;SearchController&lt;/code&gt; and use it to get results from
            the backend. The form in &lt;code&gt;app/search/index.html&lt;/code&gt; calls the &lt;code&gt;search()&lt;/code&gt; function. The
            &amp;quot;term&amp;quot; is defined by the input field in this page with
            &lt;span style=&quot;color: rgb(0,0,255);&quot;&gt;ng-model=&lt;/span&gt;&lt;span
                style=&quot;color: rgb(0,128,0);&quot;&gt;&amp;quot;term&amp;quot;.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.controller(&apos;SearchController&apos;, function ($scope, SearchService) {
    $scope.search = function () {
        console.log(&quot;Search term is: &quot; + $scope.term);
        SearchService.query($scope.term).then(function (response) {
            $scope.searchResults = response.data;
        });
    };
})&lt;/pre&gt;
        &lt;p&gt;If you try to search for a &quot;foo&quot; term now, you&apos;ll see the following error in your console.&lt;/p&gt;
        &lt;pre&gt;Search term is: foo&lt;br/&gt;GET &lt;a href=&quot;http://localhost:8000/search/foo&quot;&gt;http://localhost:8000/search/foo&lt;/a&gt; 404 (Not Found)&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/mock-api.js&lt;/code&gt; for the fake backend. Populate it with the following JavaScript.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;// We will be using backend-less development
// $http uses $httpBackend to make its calls to the server
// $resource uses $http, so it uses $httpBackend too
// We will mock $httpBackend, capturing routes and returning data
angular.module(&apos;myApp&apos;)
    .service(&apos;ServerDataModel&apos;, function ServerDataModel() {
        this.data = [
            {
                id: 1,
                name: &quot;Peyton Manning&quot;,
                phone: &quot;(303) 567-8910&quot;,
                address: {
                    street: &quot;1234 Main Street&quot;,
                    city: &quot;Greenwood Village&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;80111&quot;
                }
            },
            {
                id: 2,
                name: &quot;Demaryius Thomas&quot;,
                phone: &quot;(720) 213-9876&quot;,
                address: {
                    street: &quot;5555 Marion Street&quot;,
                    city: &quot;Denver&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;80202&quot;
                }
            },
            {
                id: 3,
                name: &quot;Von Miller&quot;,
                phone: &quot;(917) 323-2333&quot;,
                address: {
                    street: &quot;14 Mountain Way&quot;,
                    city: &quot;Vail&quot;,
                    state: &quot;CO&quot;,
                    zip: &quot;81657&quot;
                }
            }
        ];

        this.getData = function () {
            return this.data;
        };

        this.search = function (term) {
            if (term == &quot;&quot; || term == &quot;*&quot;) {
                return this.getData();
            }
            // find the name that matches the term
            var list = $.grep(this.getData(), function (element, index) {
                term = term.toLowerCase();
                return (element.name.toLowerCase().match(term));
            });

            if (list.length === 0) {
                return [];
            } else {
                return list;
            }
        };
    })
    .run(function ($httpBackend, ServerDataModel) {

        $httpBackend.whenGET(/\/search\/\w+/).respond(function (method, url, data) {
            // parse the matching URL to pull out the term (/search/:term)
            var term = url.split(&apos;/&apos;)[2];

            var results = ServerDataModel.search(term);

            return [200, results, {Location: &apos;/search/&apos; + term}];
        });

        $httpBackend.whenGET(/search\/index.html/).passThrough();
        $httpBackend.whenGET(/view/).passThrough();
    });&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;This file uses &lt;a href=&quot;http://api.jquery.com/jquery.grep/&quot;&gt;jQuery.grep()&lt;/a&gt;, so you&apos;ll need to install
            jQuery. Modify &lt;code&gt;bower.json&lt;/code&gt; and add jQuery to the list of dependencies. &lt;pre
        class=&quot;brush: js&quot;&gt;&quot;dependencies&quot;: {
  ...
  &quot;html5-boilerplate&quot;: &quot;~4.3.0&quot;,
  &quot;jquery&quot;: &quot;~1.10.x&quot;
}&lt;/pre&gt;
        &lt;p&gt;Stop the npm process, run
            &amp;quot;npm install&amp;quot; to download and install jQuery, then &amp;quot;npm start&amp;quot; again.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/app.js&lt;/code&gt;&amp;nbsp;and add a dependency on &lt;a
            href=&quot;https://docs.angularjs.org/api/ngMockE2E&quot;&gt;ngMockE2E&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;angular.module(&apos;myApp&apos;, [&apos;ngMockE2E&apos;,
  &apos;ngRoute&apos;,
  ...&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/index.html&lt;/code&gt; to include references to &lt;code&gt;jquery.js&lt;/code&gt;,
            &lt;code&gt;angular-mocks.js&lt;/code&gt; and &lt;code&gt;mock-api.js&lt;/code&gt;.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;script src=&quot;bower_components/angular-route/angular-route.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;bower_components/angular-mocks/angular-mocks.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;bower_components/jquery/jquery.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&amp;lt;script src=&quot;search/mock-api.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;components/version/version.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add the following HTML to &lt;code&gt;app/search/index.html&lt;/code&gt; to display the search results.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;div&amp;gt;
    &amp;lt;pre&amp;gt;{{ searchResults | json}}&amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
        &lt;p&gt;After making this change, you should be able to search for &amp;quot;p&amp;quot;, &amp;quot;d&amp;quot; or &amp;quot;v&amp;quot; and
            see results as JSON.&lt;/p&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;To make the results more readable, change the above HTML to use a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; and Angular&apos;s &lt;a
            href=&quot;https://docs.angularjs.org/api/ng/directive/ngRepeat&quot;&gt;ng-repeat&lt;/a&gt; directive.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;table ng-show=&quot;searchResults.length&quot; style=&quot;width: 100%&quot;&amp;gt;
    &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Phone&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Address&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
    &amp;lt;tr ng-repeat=&quot;person in searchResults&quot;&amp;gt;
        &amp;lt;td&amp;gt;{{person.name}}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{person.phone}}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{person.address.street}}&amp;lt;br/&amp;gt;
            {{person.address.city}}, {{person.address.state}} {{person.address.zip}}
        &amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This section has shown you how to fetch and display search results. The next section builds on this and shows how to edit and save a record.&lt;/p&gt;
&lt;h3&gt;Add an Edit Feature&lt;/h3&gt;
&lt;ol&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;app/search/index.html&lt;/code&gt;&amp;nbsp;to add a link for editing a person.&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
&amp;lt;table ng-show=&quot;searchResults.length&quot; style=&quot;width: 100%&quot;&amp;gt;
    ...
    &amp;lt;tr ng-repeat=&quot;person in searchResults&quot;&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;a href=&quot;&quot; ng-click=&quot;edit(person)&quot;&amp;gt;{{person.name}}&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;
        ...
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Add an &lt;code&gt;edit()&lt;/code&gt; function to &lt;code&gt;SearchController&lt;/code&gt;.
           Notice that the &lt;a href=&quot;https://docs.angularjs.org/api/ng/service/$location&quot;&gt;$location service&lt;/a&gt;
            dependency has been added to the controller&apos;s initialization function. &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;SearchController&apos;, function ($scope, $location, SearchService) {
    ...

    $scope.edit = function (person) {
        $location.path(&quot;/edit/&quot; + person.id);
    }
})&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create a route to handle the edit URL in &lt;code&gt;app/search/search.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.config([&apos;$routeProvider&apos;, function ($routeProvider) {
    $routeProvider
        .when(&apos;/search&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        .when(&apos;/edit/:id&apos;, {
            templateUrl: &apos;search/edit.html&apos;,
            controller: &apos;EditController&apos;
        });
}])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;app/search/edit.html&lt;/code&gt; to display an editable form. The HTML below shows how you can use &lt;a
            href=&quot;https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_data_attributes&quot;&gt;HTML5&apos;s data
            attributes&lt;/a&gt; to have valid attributes instead of ng-*.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;form ng-submit=&quot;save()&quot;&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;name&quot;&amp;gt;Name:&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.name&quot; id=&quot;name&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label for=&quot;phone&quot;&amp;gt;Phone:&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.phone&quot; id=&quot;phone&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend&amp;gt;Address:&amp;lt;/legend&amp;gt;
        &amp;lt;address style=&quot;margin-left: 50px&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.street&quot;&amp;gt;&amp;lt;br/&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.city&quot;&amp;gt;,
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.state&quot; size=&quot;2&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; data-ng-model=&quot;person.address.zip&quot; size=&quot;5&quot;&amp;gt;
        &amp;lt;/address&amp;gt;
    &amp;lt;/fieldset&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Modify &lt;code&gt;SearchService&lt;/code&gt;&amp;nbsp;to contain functions for finding a person by their id, and saving
            them.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.factory(&apos;SearchService&apos;, function ($http) {
    var service = {
        query: function (term) {
            return $http.get(&apos;/search/&apos; + term);
        },
        fetch: function (id) {
            return $http.get(&apos;/edit/&apos; + id);
        },
        save: function(data) {
            return $http.post(&apos;/edit/&apos; + data.id, data);
        }
    };
    return service;
});&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Create &lt;code&gt;EditController&lt;/code&gt; in &lt;code&gt;app/search/search.js&lt;/code&gt;. &lt;a
            href=&quot;https://docs.angularjs.org/api/ngRoute/service/$routeParams&quot;&gt;$routeParams&lt;/a&gt; is an Angular service
            that allows you to grab parameters from a URL.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;EditController&apos;, function ($scope, $location, $routeParams, SearchService) {
    SearchService.fetch($routeParams.id).then(function (response) {
        $scope.person = response.data;
    });

    $scope.save = function() {
        SearchService.save($scope.person).then(function(response) {
            $location.path(&quot;/search/&quot; + $scope.person.name);
        });
    }
})&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;At the bottom of &lt;code&gt;app/search/mock-api.js&lt;/code&gt;, in its &lt;code&gt;run()&lt;/code&gt; function, add the following:
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;$httpBackend.whenGET(/\/search/).respond(function (method, url, data) {
    var results = ServerDataModel.search(&quot;&quot;);

    return [200, results];
});

$httpBackend.whenGET(/\/edit\/\d+/).respond(function (method, url, data) {
    // parse the matching URL to pull out the id (/edit/:id)
    var id = url.split(&apos;/&apos;)[2];

    var results = ServerDataModel.find(id);

    return [200, results, {Location: &apos;/edit/&apos; + id}];
});

$httpBackend.whenPOST(/\/edit\/\d+/).respond(function(method, url, data) {
    var params = angular.fromJson(data);

    // parse the matching URL to pull out the id (/edit/:id)
    var id = url.split(&apos;/&apos;)[2];

    var person = ServerDataModel.update(id, params);

    return [201, person, { Location: &apos;/edit/&apos; + id }];
});

$httpBackend.whenGET(/search\/edit.html/).passThrough();&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;Further up in the same file, add &lt;code&gt;find()&lt;/code&gt; and &lt;code&gt;update()&lt;/code&gt; methods to &lt;code&gt;ServerDataModel&lt;/code&gt;.
        &lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;this.getData = function () {
    return this.data;
};

this.search = function (term) {
    ...
};

this.find = function (id) {
    // find the game that matches that id
    var list = $.grep(this.getData(), function (element, index) {
        return (element.id == id);
    });
    if (list.length === 0) {
        return {};
    }
    // even if list contains multiple items, just return first one
    return list[0];
};

this.update = function (id, dataItem) {
    // find the game that matches that id
    var people = this.getData();
    var match = null;
    for (var i = 0; i &lt; people.length; i++) {
        if (people[i].id == id) {
            match = people[i];
            break;
        }
    }
    if (!angular.isObject(match)) {
        return {};
    }
    angular.extend(match, dataItem);
    return match;
};&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;p&gt;The &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; in &lt;code&gt;app/search/edit.html&lt;/code&gt;&amp;nbsp;calls a &lt;code&gt;save()&lt;/code&gt; function to update a
            person&apos;s data. You already implemented this above. The function executes the logic below.&amp;nbsp;&lt;/p&gt;
        &lt;pre class=&quot;brush: js&quot;&gt;$location.path(&quot;/search/&quot; + $scope.person.name);&lt;/pre&gt;
        &lt;p&gt;Since the SearchController doesn&apos;t execute a search automatically when you execute this URL, add the
            following logic to do so in &lt;code&gt;app/search/search.js&lt;/code&gt;. Note that
            &lt;code&gt;$routeParams&lt;/code&gt; is added to the list of injected dependencies.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;SearchController&apos;, function ($scope, $location, $routeParams, SearchService) {
    if ($routeParams.term) {
        SearchService.query($routeParams.term).then(function (response) {
            $scope.term = $routeParams.term;
            $scope.searchResults = response.data;
        });
    }
    ...
})&lt;/pre&gt;
        &lt;p&gt;You&apos;ll also need to add a new route so search/term is recognized.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;.config([&apos;$routeProvider&apos;, function ($routeProvider) {
    $routeProvider
        .when(&apos;/search&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        .when(&apos;/search/:term&apos;, {
            templateUrl: &apos;search/index.html&apos;,
            controller: &apos;SearchController&apos;
        })
        ...
}])&lt;/pre&gt;
    &lt;/li&gt;
    &lt;li&gt;After making all these changes, you should be able to refresh your browser and search/edit/update a person&apos;s
        information. If it works - nice job!
    &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;p&gt;A completed project with this code in it is available on GitHub at &lt;a
    href=&quot;https://github.com/mraible/angular-tutorial&quot;&gt;https://github.com/mraible/angular-tutorial&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;There are three commits that make the changes for the three main steps in this tutorial:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/9792a0fbf1c2a5f1171498de7666e6f13cdd0537&quot;&gt;The
        Basics&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/e9c277a50606d7ebaf9bcefa46f5942e2edf7ecf&quot;&gt;The
        Backend&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a
        href=&quot;https://github.com/mraible/angular-tutorial/commit/56be9decd0242dd60f06ef7232db723d6595ed0c&quot;&gt;Add
        an Edit Feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Extra Credit&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://linqed.eu/2014/10/07/deploying-angular-seed-to-heroku/&quot;&gt;Deploy your completed app to Heroku&lt;/a&gt;. See
    running version of this tutorial at &lt;a
        href=&quot;https://angular-123.herokuapp.com&quot;&gt;https://angular-123.herokuapp.com&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this quick-and-easy tutorial on how to get started with AngularJS. In a future tutorial, I&apos;ll show
you &lt;a href=&quot;//raibledesigns.com/rd/entry/testing_angularjs_applications&quot;&gt;how to write unit tests and integration tests for this application&lt;/a&gt;. If you&apos;re in Denver next Tuesday, I&apos;ll be &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/219099019/&quot;&gt;speaking about AngularJS at Denver&apos;s Open Source Users Group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re a Java developer that&apos;s interested in developing with AngularJS and Spring Boot, you might want to checkout
the &lt;a href=&quot;http://www.infoq.com/news/2015/01/jhipster-2.0&quot;&gt;recently released JHipster 2.0&lt;/a&gt;.&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/developing_an_ios_native_app</id>
        <title type="html">Developing an iOS Native App with Ionic</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/developing_an_ios_native_app"/>
        <published>2014-03-27T16:38:55-06:00</published>
        <updated>2014-05-08T19:47:26-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="ionic" scheme="http://roller.apache.org/ns/tags/" />
        <category term="ios" scheme="http://roller.apache.org/ns/tags/" />
        <category term="mobile" scheme="http://roller.apache.org/ns/tags/" />
        <category term="html5" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="ionicframework" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
    In my current project, I&apos;ve been helping a client develop a native iOS app for their customers. It&apos;s
    written mostly in Objective-C and talks to a REST API. I talked about &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/documenting_your_spring_api_with&quot;&gt;how we documented our REST API&lt;/a&gt;
    a couple days ago. We developed a prototype for this application back in December, using
    &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; and &lt;a href=&quot;http://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;. Rather than
    using &lt;a href=&quot;http://phonegap.com/&quot;&gt;PhoneGap&lt;/a&gt;, we loaded our app in a UIWebView.
&lt;/p&gt;

&lt;p&gt;
    It all seemed to work well until we needed to read an activation code with the device&apos;s camera. Since
    we didn&apos;t know how to do &lt;a href=&quot;http://en.wikipedia.org/wiki/Optical_character_recognition&quot;&gt;OCR&lt;/a&gt; in JavaScript,
    we figured a mostly-native app was the way to go. We hired an outside company to do iOS development in January and
    they&apos;ve been developing the app since the beginning of February. In the last couple weeks, we encountered some
    screens that seemed fitting for HTML5, so we turned back to our AngularJS prototype.
&lt;/p&gt;

&lt;p&gt;The prototype used Bootstrap heavily, but we quickly learned it didn&apos;t look like an iOS 7 app, which is what our
    UX Designer requested. A co-worker pointed out &lt;a href=&quot;http://ionicframework.com/&quot;&gt;Ionic&lt;/a&gt;, developed by
    &lt;a href=&quot;http://drifty.com/&quot;&gt;Drifty&lt;/a&gt;. It&apos;s basically &lt;em&gt;Bootstrap for Native&lt;/em&gt;, so the apps you develop
    look and behave like a mobile application.
&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;strong&gt;What is Ionic?&lt;/strong&gt;&lt;br/&gt;
    Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly
    interactive apps. Built with Sass and optimized for AngularJS.
&lt;/blockquote&gt;

&lt;p&gt;
    I started developing with Ionic a few weeks ago. Using its CSS classes and AngularJS directives,
    I was able to create several new screens in a matter of days. Most of the time, I was learning new things:
    how to &lt;a
        href=&quot;http://forum.ionicframework.com/t/is-it-possible-to-override-the-back-button-behavior-on-certain-screens/1867/2&quot;&gt;
    override its back button&lt;/a&gt; behavior (to launch back into the native app), how to
    &lt;a href=&quot;http://ionicframework.com/docs/angularjs/controllers/view-state/&quot;&gt;configure routes&lt;/a&gt; with
    &lt;a href=&quot;https://github.com/angular-ui/ui-router&quot;&gt;ui-router&lt;/a&gt;, and how to make the
    &lt;a href=&quot;http://ionicframework.com/docs/angularjs/views/loading/&quot;&gt;$ionicLoading service&lt;/a&gt; look native. Now that
    I know a lot of the basics, I feel like I can really crank out some code.
&lt;/p&gt;

&lt;p style=&quot;border: 1px solid #91c89c;
color: #333;
padding: 10px 10px 10px 10px;
background: #f3f9f4;&quot;&gt;
    &lt;strong&gt;Tip:&lt;/strong&gt;
    I learned how subviews work with ui-router thanks to a YouTube video of &lt;a
        href=&quot;http://www.youtube.com/watch?v=dqJRoh8MnBo&quot;&gt;Tim Kindberg on Angular UI-Router&lt;/a&gt;. However, subviews
    never fully made sense until I saw
    &lt;a href=&quot;http://forum.ionicframework.com/t/using-state-go-to-navigate-between-views-doesnt-work-location-path-does/1846/11&quot;&gt;
        Jared Bell&apos;s diagram&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
    To demonstrate how easy it is to use Ionic, I whipped up a quick example application. You can get the source
    on GitHub at &lt;a href=&quot;https://github.com/mraible/boot-ionic&quot;&gt;https://github.com/mraible/boot-ionic&lt;/a&gt;. The app is a
    refactored version of Josh Long&apos;s &lt;a href=&quot;https://github.com/mraible/boot-examples/tree/master/x-auth-security&quot;&gt;x-auth-security&lt;/a&gt; that uses Ionic instead
    of raw AngularJS and Bootstrap. To keep things simple, I did not develop the native app that wraps the HTML.
&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
    In my current project, I&apos;ve been helping a client develop a native iOS app for their customers. It&apos;s
    written mostly in Objective-C and talks to a REST API. I talked about &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/documenting_your_spring_api_with&quot;&gt;how we documented our REST API&lt;/a&gt;
    a couple days ago. We developed a prototype for this application back in December, using
    &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; and &lt;a href=&quot;http://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;. Rather than
    using &lt;a href=&quot;http://phonegap.com/&quot;&gt;PhoneGap&lt;/a&gt;, we loaded our app in a UIWebView.
&lt;/p&gt;

&lt;p&gt;
    It all seemed to work well until we needed to read an activation code with the device&apos;s camera. Since
    we didn&apos;t know how to do &lt;a href=&quot;http://en.wikipedia.org/wiki/Optical_character_recognition&quot;&gt;OCR&lt;/a&gt; in JavaScript,
    we figured a mostly-native app was the way to go. We hired an outside company to do iOS development in January and
    they&apos;ve been developing the app since the beginning of February. In the last couple weeks, we encountered some
    screens that seemed fitting for HTML5, so we turned back to our AngularJS prototype.
&lt;/p&gt;

&lt;p&gt;The prototype used Bootstrap heavily, but we quickly learned it didn&apos;t look like an iOS 7 app, which is what our
    UX Designer requested. A co-worker pointed out &lt;a href=&quot;http://ionicframework.com/&quot;&gt;Ionic&lt;/a&gt;, developed by
    &lt;a href=&quot;http://drifty.com/&quot;&gt;Drifty&lt;/a&gt;. It&apos;s basically &lt;em&gt;Bootstrap for Native&lt;/em&gt;, so the apps you develop
    look and behave like a mobile application.
&lt;/p&gt;

&lt;blockquote class=&quot;quote&quot;&gt;
    &lt;strong&gt;What is Ionic?&lt;/strong&gt;&lt;br/&gt;
    Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly
    interactive apps. Built with Sass and optimized for AngularJS.
&lt;/blockquote&gt;

&lt;p&gt;
    I started developing with Ionic a few weeks ago. Using its CSS classes and AngularJS directives,
    I was able to create several new screens in a matter of days. Most of the time, I was learning new things:
    how to &lt;a
        href=&quot;http://forum.ionicframework.com/t/is-it-possible-to-override-the-back-button-behavior-on-certain-screens/1867/2&quot;&gt;
    override its back button&lt;/a&gt; behavior (to launch back into the native app), how to
    &lt;a href=&quot;http://ionicframework.com/docs/angularjs/controllers/view-state/&quot;&gt;configure routes&lt;/a&gt; with
    &lt;a href=&quot;https://github.com/angular-ui/ui-router&quot;&gt;ui-router&lt;/a&gt;, and how to make the
    &lt;a href=&quot;http://ionicframework.com/docs/angularjs/views/loading/&quot;&gt;$ionicLoading service&lt;/a&gt; look native. Now that
    I know a lot of the basics, I feel like I can really crank out some code.
&lt;/p&gt;

&lt;p style=&quot;border: 1px solid #91c89c;
color: #333;
padding: 10px 10px 10px 10px;
background: #f3f9f4;&quot;&gt;
    &lt;strong&gt;Tip:&lt;/strong&gt;
    I learned how subviews work with ui-router thanks to a YouTube video of &lt;a
        href=&quot;http://www.youtube.com/watch?v=dqJRoh8MnBo&quot;&gt;Tim Kindberg on Angular UI-Router&lt;/a&gt;. However, subviews
    never fully made sense until I saw
    &lt;a href=&quot;http://forum.ionicframework.com/t/using-state-go-to-navigate-between-views-doesnt-work-location-path-does/1846/11&quot;&gt;
        Jared Bell&apos;s diagram&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
    To demonstrate how easy it is to use Ionic, I whipped up a quick example application. You can get the source
    on GitHub at &lt;a href=&quot;https://github.com/mraible/boot-ionic&quot;&gt;https://github.com/mraible/boot-ionic&lt;/a&gt;. The app is a
    refactored version of Josh Long&apos;s &lt;a href=&quot;https://github.com/mraible/boot-examples/tree/master/x-auth-security&quot;&gt;x-auth-security&lt;/a&gt; that uses Ionic instead
    of raw AngularJS and Bootstrap. To keep things simple, I did not develop the native app that wraps the HTML.
&lt;/p&gt;

&lt;p&gt;Below are the steps I used to convert from AngularJS + Bootstrap to Ionic. If you want to convert a simple AngularJS
    app to use Ionic, hopefully this will help.&lt;/p&gt;

&lt;p&gt;
    &lt;strong&gt;1. Download Ionic and add it to your project.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;Ionic 1.0 Beta was released earlier this week. You can download it from &lt;a
        href=&quot;http://code.ionicframework.com/1.0.0-beta.1/ionic-v1.0.0-beta.1.zip&quot;&gt;here&lt;/a&gt;. Add its files
    to your project. In this example, I &lt;a
            href=&quot;https://github.com/mraible/boot-ionic/commit/d564bb8ecb26c8519f88d6797db53f6ae327d9ad&quot;&gt;added them&lt;/a&gt;
    to &lt;em&gt;src/main/resources/public&lt;/em&gt;. In my index.html, I removed Bootstrap&apos;s CSS and replaced it with Ionic&apos;s.
&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-    &amp;lt;link href=&quot;webjars/bootstrap/3.1.1/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot;&amp;gt;
+    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;css/ionic.css&quot;/&amp;gt;
  &amp;lt;/head&amp;gt;
-&amp;lt;body style=&quot;padding-top: 60px&quot;&amp;gt;
+&amp;lt;body&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Next, I replaced Angular, Bootstrap and jQuery&apos;s JavaScript references.&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-    &amp;lt;script src=&quot;webjars/jquery/2.0.3/jquery.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
-    &amp;lt;script src=&quot;webjars/bootstrap/3.1.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
-    &amp;lt;script src=&quot;webjars/angularjs/1.2.13/angular.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
+    &amp;lt;script src=&quot;js/ionic.bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
     &amp;lt;script src=&quot;webjars/angularjs/1.2.13/angular-resource.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
-    &amp;lt;script src=&quot;webjars/angularjs/1.2.13/angular-route.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
     &amp;lt;script src=&quot;webjars/angularjs/1.2.13/angular-cookies.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;blockquote class=&quot;quote&quot; style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;strong&gt;What about WebJars?&lt;/strong&gt;&lt;br/&gt;
    You might ask - why not use &lt;a href=&quot;http://www.webjars.org/&quot;&gt;WebJars&lt;/a&gt;? You can, once
    &lt;a href=&quot;https://github.com/webjars/ionic/pull/2&quot;&gt;this pull request&lt;/a&gt; is accepted and an updated version is
deployed to Maven central. &lt;a href=&quot;https://github.com/mraible/boot-ionic/commit/1bffd792683b2428fc16a73f3e7e3e6d43f327c2&quot;&gt;Here&apos;s how&lt;/a&gt;
the application would change.&lt;/blockquote&gt;
&lt;p&gt;
    &lt;strong&gt;2. Change from Angular&apos;s Router to ui-router.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;Ionic uses ui-router for matching URLs and loading particular pages.
    The raw Angular routing looks pretty similar to how it does with ui-router, except it uses a
    &lt;code&gt;$stateProvider&lt;/code&gt;
    service instead of &lt;code&gt;$routeProvider&lt;/code&gt;. You&apos;ll notice I also added &apos;ionic&apos; as a dependency.
&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-angular.module(&apos;exampleApp&apos;, [&apos;ngRoute&apos;, &apos;ngCookies&apos;, &apos;exampleApp.services&apos;])
+angular.module(&apos;exampleApp&apos;, [&apos;ionic&apos;, &apos;ngCookies&apos;, &apos;exampleApp.services&apos;])
 	.config(
-		[ &apos;$routeProvider&apos;, &apos;$locationProvider&apos;, &apos;$httpProvider&apos;, function($routeProvider, $locationProvider, $httpProvider) {
+		[ &apos;$stateProvider&apos;, &apos;$urlRouterProvider&apos;, &apos;$httpProvider&apos;, function($stateProvider, $urlRouterProvider, $httpProvider) {

-	    $routeProvider.when(&apos;/create&apos;, { templateUrl: &apos;partials/create.html&apos;, controller: CreateController});
+           $stateProvider.state(&apos;create&apos;, {url: &apos;/create&apos;, templateUrl: &apos;partials/create.html&apos;, controller: CreateController})
+               .state(&apos;edit&apos;, {url: &apos;/edit/:id&apos;, templateUrl: &apos;partials/edit.html&apos;, controller: EditController})
+               .state(&apos;login&apos;, {url: &apos;/login&apos;, templateUrl: &apos;partials/login.html&apos;, controller: LoginController})
+               .state(&apos;index&apos;, {url: &apos;/index&apos;, templateUrl: &apos;partials/index.html&apos;, controller: IndexController});

-	    $routeProvider.when(&apos;/edit/:id&apos;, { templateUrl: &apos;partials/edit.html&apos;, controller: EditController});
-	    $routeProvider.when(&apos;/login&apos;, { templateUrl: &apos;partials/login.html&apos;, controller: LoginController});
-	    $routeProvider.otherwise({templateUrl: &apos;partials/index.html&apos;, controller: IndexController});
-
-	    $locationProvider.hashPrefix(&apos;!&apos;);
+	    $urlRouterProvider.otherwise(&apos;/index&apos;);
&lt;/pre&gt;
&lt;p&gt;
    &lt;strong&gt;3. Add Ionic elements to your index.html.&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
    In contrast to Bootstrap&apos;s navbar, Ionic has header and footer elements. Rather than using a &lt;a
        href=&quot;http://docs.angularjs.org/api/ngRoute/directive/ngView&quot;&gt;ng-view&lt;/a&gt;
    directive, you use an &amp;lt;ion-nav-view&amp;gt;. It&apos;s a pretty slick setup once you understand it, especially since they
    allow you to easily override &lt;a href=&quot;http://ionicframework.com/docs/api/directive/ionNavBackButton/&quot;&gt;back-button
    behavior&lt;/a&gt; and
    &lt;a href=&quot;http://ionicframework.com/docs/api/directive/ionNavButtons/&quot;&gt;nav buttons&lt;/a&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-    &amp;lt;nav class=&quot;navbar navbar-fixed-top navbar-default&quot; role=&quot;navigation&quot;&amp;gt;
-        &amp;lt;!-- lots of HTML here --&gt;
-    &amp;lt;/nav&amp;gt;
-
-    &amp;lt;div class=&quot;container&quot;&amp;gt;
-        &amp;lt;div class=&quot;alert alert-danger&quot; ng-show=&quot;error&quot;&amp;gt;{{error}}&amp;lt;/div&amp;gt;
-        &amp;lt;div ng-view&amp;gt;&amp;lt;/div&amp;gt;
-    &amp;lt;/div&amp;gt;
+    &amp;lt;ion-nav-bar class=&quot;bar-positive nav-title-slide-ios7&quot;&amp;gt;&amp;lt;/ion-nav-bar&amp;gt;
+    &amp;lt;ion-nav-view animation=&quot;slide-left-right&quot;&amp;gt;
+        &amp;lt;div class=&quot;alert alert-danger&quot; ng-show=&quot;error&quot;&amp;gt;{{error}}&amp;lt;/div&amp;gt;
+    &amp;lt;/ion-nav-view&amp;gt;
+    &amp;lt;ion-footer-bar class=&quot;bar-dark&quot; ng-show=&quot;user&quot;&amp;gt;
+        &amp;lt;button class=&quot;button button-assertive&quot; ng-click=&quot;logout()&quot;&amp;gt;
+            Logout
+        &amp;lt;/button&amp;gt;
+    &amp;lt;/ion-footer-bar&amp;gt;
&lt;/pre&gt;
&lt;p&gt;
    &lt;strong&gt;4. Change your templates to use &amp;lt;ion-view&gt; and &amp;lt;ion-content&gt;.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;After routes are migrated and basic navigation is working, you&apos;ll need to modify your templates to use &amp;lt;ion-view&amp;gt;
    and &amp;lt;ion-content&amp;gt;. Here&apos;s a diff from the most complicated page in the app.
&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-&amp;lt;div style=&quot;float: right&quot;&amp;gt;
-	&amp;lt;a href=&quot;#!/create&quot; class=&quot;btn btn-default&quot; ng-show=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;Create&amp;lt;/a&amp;gt;
-&amp;lt;/div&amp;gt;
-&amp;lt;div class=&quot;page-header&quot;&amp;gt;
-	&amp;lt;h3&amp;gt;News&amp;lt;/h3&amp;gt;
-&amp;lt;/div&amp;gt;
+&amp;lt;ion-view title=&quot;News&quot;&amp;gt;
+    &amp;lt;ion-content&amp;gt;
+        &amp;lt;ion-nav-buttons side=&quot;left&quot;&amp;gt;
+            &amp;lt;div class=&quot;buttons&quot; ng-show=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;
+                &amp;lt;button class=&quot;button button-icon icon ion-ios7-minus-outline&quot;
+                        ng-click=&quot;data.showDelete = !data.showDelete&quot;&amp;gt;&amp;lt;/button&amp;gt;
+            &amp;lt;/div&amp;gt;
+        &amp;lt;/ion-nav-buttons&amp;gt;
+        &amp;lt;ion-nav-buttons side=&quot;right&quot;&amp;gt;
+            &amp;lt;a href=&quot;#/create&quot; class=&quot;button button-icon icon ion-ios7-plus-outline&quot;
+               ng-show=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;&amp;lt;/a&amp;gt;
+        &amp;lt;/ion-nav-buttons&amp;gt;

-&amp;lt;div ng-repeat=&quot;newsEntry in newsEntries&quot;&amp;gt;
-	&amp;lt;hr /&amp;gt;
-	&amp;lt;div class=&quot;pull-right&quot;&amp;gt;
-		&amp;lt;a ng-click=&quot;deleteEntry(newsEntry)&quot; class=&quot;btn btn-xs btn-default&quot; ng-show=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;Remove&amp;lt;/a&amp;gt;
-		&amp;lt;a href=&quot;#!/edit/{{newsEntry.id}}&quot; class=&quot;btn btn-xs btn-default&quot; ng-show=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;Edit&amp;lt;/a&amp;gt;
-	&amp;lt;/div&amp;gt;
-	&amp;lt;h4&amp;gt;{{newsEntry.date | date}}&amp;lt;/h4&amp;gt;
-	&amp;lt;p&amp;gt;{{newsEntry.content}}&amp;lt;/p&amp;gt;
-&amp;lt;/div&amp;gt;
-&amp;lt;hr /&amp;gt;
+        &amp;lt;ion-list show-delete=&quot;data.showDelete&quot; on-delete=&quot;deleteEntry(item)&quot;
+                  option-buttons=&quot;itemButtons&quot; can-swipe=&quot;hasRole(&apos;ROLE_ADMIN&apos;)&quot;&amp;gt;
+            &amp;lt;ion-item ng-repeat=&quot;newsEntry in newsEntries&quot; item=&quot;newsEntry&quot;&amp;gt;
+                &amp;lt;h4&amp;gt;{{newsEntry.date | date}}&amp;lt;/h4&amp;gt;
+                &amp;lt;p&amp;gt;{{newsEntry.content}}&amp;lt;/p&amp;gt;
+            &amp;lt;/ion-item&amp;gt;
+        &amp;lt;/ion-list&amp;gt;
+    &amp;lt;/ion-content&amp;gt;
+&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;p&gt;

    I did migrate
    to use an &lt;a href=&quot;http://codepen.io/ionic/pen/JsHjf&quot;&gt;&amp;lt;ion-list&amp;gt; with delete/options buttons&lt;/a&gt;, so
some additional JavaScript changes were needed.
&lt;/p&gt;
&lt;pre class=&quot;brush: xml&quot;&gt;
-function IndexController($scope, NewsService) {
+function IndexController($scope, $state, NewsService) {

    $scope.newsEntries = NewsService.query();

+     $scope.data = {
+         showDelete: false
+     };
+
    $scope.deleteEntry = function(newsEntry) {
         newsEntry.$remove(function() {
              $scope.newsEntries = NewsService.query();
         });
    };
+
+     $scope.itemButtons = [{
+         text: &apos;Edit&apos;,
+         type: &apos;button-assertive&apos;,
+         onTap: function (item) {
+              $state.go(&apos;edit&apos;, {id: item.id});
+         }
+     }];
}
&lt;/pre&gt;
&lt;h3&gt;Screenshots&lt;/h3&gt;
&lt;p&gt;After making all these changes, the app looks pretty good in Chrome.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;https://farm4.staticflickr.com/3733/13454844134_3bec9e6d75_b.jpg&quot;
   data-href=&quot;https://www.flickr.com/photos/mraible/13454844134&quot; title=&quot;Ionic Login&quot; rel=&quot;lightbox[ionic]&quot;&gt;
    &lt;img src=&quot;//farm4.staticflickr.com/3733/13454844134_3bec9e6d75_n.jpg&quot; width=&quot;244&quot; height=&quot;320&quot; alt=&quot;Ionic Login&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://farm6.staticflickr.com/5545/13454844054_fec4b44a35_b.jpg&quot;
   data-href=&quot;https://www.flickr.com/photos/mraible/13454844054&quot; title=&quot;Ionic News&quot; rel=&quot;lightbox[ionic]&quot;&gt;
    &lt;img src=&quot;//farm6.staticflickr.com/5545/13454844054_fec4b44a35_n.jpg&quot; width=&quot;244&quot; height=&quot;320&quot; alt=&quot;Ionic News&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;https://farm4.staticflickr.com/3808/13454594373_3092b81b25_b.jpg&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/13454594373&quot; title=&quot;Ionic Swipe&quot; rel=&quot;lightbox[ionic]&quot;&gt;
        &lt;img src=&quot;//farm4.staticflickr.com/3808/13454594373_3092b81b25_n.jpg&quot; width=&quot;244&quot; height=&quot;320&quot; alt=&quot;Ionic Swipe&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;https://farm4.staticflickr.com/3769/13454594503_c5609d4000_b.jpg&quot;
       data-href=&quot;https://www.flickr.com/photos/mraible/13454594503&quot; title=&quot;Ionic Edit&quot; rel=&quot;lightbox[ionic]&quot;&gt;
        &lt;img src=&quot;//farm4.staticflickr.com/3769/13454594503_c5609d4000_n.jpg&quot; width=&quot;244&quot; height=&quot;320&quot; alt=&quot;Ionic Edit&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h3&gt;Tips and Tricks&lt;/h3&gt;
&lt;p&gt;
    In additional to figuring out how to use Ionic, I discovered a few other tidbits along the way. First of all,
    we had a different default color for the header. Since Ionic uses generic color names (e.g. light, stable, positive, calm),
    I found it easy to change the default value for &quot;positive&quot; and then continue to use their class names.
&lt;/p&gt;
&lt;p&gt;
    &lt;strong&gt;Modifying CSS variable colors&lt;/strong&gt;&lt;br/&gt;
To modify the base color for &quot;positive&quot;, I &lt;a href=&quot;https://github.com/driftyco/ionic&quot;&gt;cloned the source&lt;/a&gt;, and
modified &lt;em&gt;scss/_variables.scss&lt;/em&gt;.
&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
$light: #fff !default;
$stable: #f8f8f8 !default;
-$positive: #4a87ee !default;
+$positive: #589199 !default;
$calm: #43cee6 !default;
$balanced: #66cc33 !default;
$energized: #f0b840 !default;
&lt;/pre&gt;
&lt;p&gt;After making this change, I ran &quot;grunt&quot; and copied &lt;em&gt;dist/css/ionic.css&lt;/em&gt; into our project.&lt;/p&gt; 
&lt;p&gt;
    &lt;strong&gt;iOS Native Integration&lt;/strong&gt;&lt;br/&gt;
    Our app uses a similar token-based authentication mechanism as x-auth-security, except its backed by Crowd.
    However, since users won&apos;t be logging directly into the Ionic app, we added
    the &quot;else&quot; clause in &lt;em&gt;app.js&lt;/em&gt; to allow a token to be passed in via URL. We also allowed the backend API
    path to be overridden.
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
/* Try getting valid user from cookie or go to login page */
var originalPath = $location.path();
$location.path(&quot;/login&quot;);
var user = $cookieStore.get(&apos;user&apos;);

if (user !== undefined) {
    $rootScope.user = user;
    $http.defaults.headers.common[xAuthTokenHeaderName] = user.token;
    $location.path(originalPath);
} else {
    // token passed in from native app
    var authToken = $location.search().token;
    if (authToken) {
        $http.defaults.headers.common[&apos;X-Auth-Token&apos;] = authToken;
    }
}

// allow overriding the base API path
$rootScope.apiPath = &apos;/api/v1.0&apos;;
if ($location.search().apiPath) {
    $rootScope.apiPath = $location.search().apiPath;
}
&lt;/pre&gt;
&lt;p&gt;By adding this logic, the iOS app can pull up any particular page in a webview and let the Ionic app talk to the API. Here&apos;s
    what the Objective-C code looks like:
    &lt;/p&gt;
&lt;pre class=&quot;brush: java&quot;&gt;
NSString *versionNumber = @&quot;v1.0&quot;;
NSString *apiPath = @&quot;https://server.com/api/&quot;;
NSString *authToken = [TemporaryDataStore sharedInstance].authToken;
// webapp is a symbolic link to the Ionic app, created with Angular Seed
NSString *htmlFilePath = [[NSBundle mainBundle] pathForResource:@&quot;index&quot; ofType:@&quot;html&quot; inDirectory:@&quot;webapp/app&quot;];

// Note: We need to do it this way because &apos;fileURLWithPath:&apos; would encode the &apos;#&apos; to &apos;%23&quot; which breaks the html page
NSURL *htmlFileURL = [NSURL fileURLWithPath:htmlFilePath];

NSString *webappURLPath = [NSString stringWithFormat:@&quot;%@#/news?apiPath=%@%@&amp;token=%@&quot;,
                           htmlFileURL.absoluteString, apiPath, versionNumber, authToken];

// Now convert the string to a URL (doesn&apos;t seem to encode the &apos;#&apos; this way)
NSURL *webappURL = [NSURL URLWithString:webappURLPath];
[super updateWithURL:webappURL];
&lt;/pre&gt;
&lt;p&gt;We also had to write some logic to navigate back to the native app. We used a &lt;a href=&quot;http://www.idev101.com/code/Objective-C/custom_url_schemes.html&quot;&gt;
    custom URL scheme&lt;/a&gt; to do this, and the Ionic app simply called it. To override the default back button, I added
    an &quot;ng-controller&quot; attribute to &amp;lt;ion-nav-bar&amp;gt; and added a custom back button.

&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;ion-nav-bar class=&quot;bar-positive nav-title-slide-ios7&quot; ng-controller=&quot;NavController&quot;&amp;gt;
    &amp;lt;ion-nav-back-button class=&quot;button-icon&quot; ng-click=&quot;goBack()&quot;&amp;gt;
        &amp;lt;i class=&quot;ion-arrow-left-c&quot;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;/ion-nav-back-button&amp;gt;
&amp;lt;/ion-nav-bar&amp;gt;
&lt;/pre&gt;
&lt;p&gt;To detect if the app was loaded by iOS (vs. a browser, which we tested in), we used the following logic:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
// set native app indicator
if (document.location.toString().indexOf(&apos;appName.app&apos;) &gt; -1) {
    $rootScope.isNative = true;
}
&lt;/pre&gt;
&lt;p&gt;Our Ionic app has three entry points, defined by &quot;stateName1&quot;, &quot;stateName2&quot; and &quot;stateName3&quot; in this example. The code for our &lt;code&gt;NavController&lt;/code&gt; handles navigating back normally (when in a browser) or back to the native app. The &quot;appName&quot; reference below is a 3-letter acronym we used for our app.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.controller(&apos;NavController&apos;, function($scope, $ionicNavBarDelegate, $state) {
    $scope.goBack = function() {
        if ($scope.isNative &amp;amp;&amp;amp; backToNative($state)) {
            location.href=&apos;appName-ios://back&apos;;
        } else {
            $ionicNavBarDelegate.back();
        }
    };

    function backToNative($state) {
        var entryPoints = &amp;#91;&apos;stateName1&apos;, &apos;stateName2&apos;, &apos;stateName3&apos;&amp;#93;;
        return entryPoints.some(function (entry) {
            return $state.current === $state.get(entry);
        });
    }
})
&lt;/pre&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;

    I&apos;ve enjoyed working with Ionic over the last month. The biggest change I&apos;ve had to make to our AngularJS app has been
    to integrate &lt;a href=&quot;https://github.com/angular-ui/ui-router&quot;&gt;ui-router&lt;/a&gt;. Apart from this, the JavaScript didn&apos;t
    change much. However, the HTML had to change quite a bit. As far as CSS is concerned, I found myself tweaking things
    to fit our designs, but less so than I did with Bootstrap.     When I&apos;ve run into issues with Ionic, the community has been very helpful on their
    &lt;a href=&quot;http://forum.ionicframework.com/&quot;&gt;forum&lt;/a&gt;. It&apos;s the first forum I&apos;ve used that&apos;s powered by
    &lt;a href=&quot;http://www.discourse.org/&quot;&gt;Discourse&lt;/a&gt;, and I dig it.&lt;/p&gt;
&lt;p&gt;You can find the source from this article in my &lt;a href=&quot;https://github.com/mraible/boot-ionic&quot;&gt;boot-ionic project&lt;/a&gt;. Clone it and run &quot;mvn spring-boot:run&quot;, then open &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;If you&apos;re looking to create a native app using HTML5 technologies, I highly recommend you take a look at Ionic.
    We&apos;re glad we did.
    &lt;a href=&quot;http://www.infoq.com/news/2014/03/angular-2-0&quot;&gt;Angular 2.0 will target mobile apps&lt;/a&gt; and Ionic is already
    making them look pretty damn good.
&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/the_art_of_angularjs</id>
        <title type="html">The Art of AngularJS</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/the_art_of_angularjs"/>
        <published>2014-02-27T09:44:29-07:00</published>
        <updated>2014-05-08T21:28:11-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="coffeescript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="http2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="protractor" scheme="http://roller.apache.org/ns/tags/" />
        <category term="html5" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jasmine" scheme="http://roller.apache.org/ns/tags/" />
        <category term="grunt" scheme="http://roller.apache.org/ns/tags/" />
        <category term="derailed" scheme="http://roller.apache.org/ns/tags/" />
        <content type="html">Last night, I had the pleasure of &lt;a href=&quot;http://www.meetup.com/DeRailed/events/164446322/&quot;&gt;speaking at Denver&apos;s DeRailed about AngularJS&lt;/a&gt;. &lt;a href=&quot;https://twitter.com/kitesurfer&quot;&gt;Fernand&lt;/a&gt; (the group&apos;s leader) asked me to speak in December, just after I&apos;d finished a &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries&quot;&gt;European speaking tour&lt;/a&gt;. The Modern Java Web Developer talk I created for that tour included a &lt;a href=&quot;https://vimeo.com/80314102&quot;&gt;20-minute AngularJS Deep Dive&lt;/a&gt; screencast. I figured it wouldn&apos;t be much work to augment the screencast and create an hour long talk, so I agreed.
&lt;/p&gt;
&lt;p&gt;When I started creating the presentation last week, I decided I didn&apos;t want to make the audience watch my screencast as part of the presentation. They could easily do that on their own time. So I wrote, from scratch, a brand new presentation on AngularJS. I tried to include all the things about Angular that I thought were important and useful for me in my learning process. The result is a presentation I&apos;m proud of and enjoyed delivering. &lt;/p&gt;
&lt;p&gt;

&lt;p&gt;You can click through it below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-art-of-angularjs&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/31721908?rel=0&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen webkitallowfullscreen mozallowfullscreen&gt; &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You might notice the presentation has a whole lot of code in it. Normally, when I copy/paste code into a presentation, I use IntelliJ IDEA and everything works. This time, there was something amiss between IDEA 13 and Keynote 6. I tried using IDEA&apos;s plugins (namely &lt;a href=&quot;http://plugins.jetbrains.com/plugin/7198&quot;&gt;Copy on steroids&lt;/a&gt; and &lt;a href=&quot;http://plugins.jetbrains.com/plugin/190&quot;&gt;Copy as HTML&lt;/a&gt;), but none of them worked. IDEA 12 resulted in the same problem. Then I turned to other solutions. I &lt;a href=&quot;https://gist.github.com/jimbojsb/1630790&quot;&gt;installed highlight&lt;/a&gt; and copied code from the command line. This worked, but the fonts and colors weren&apos;t to my liking. Finally, I decided to try another editor: &lt;a href=&quot;http://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt; with &lt;a href=&quot;https://github.com/n1k0/SublimeHighlight&quot;&gt;SublimeHighlight&lt;/a&gt;. This worked &lt;em&gt;great&lt;/em&gt; and I&apos;m very happy with the results.
&lt;/p&gt;
&lt;p&gt;Most of my presentations end with a Questions/Contact slide. For this one, I added a few more: people to follow on Twitter, resources to learn from and projects with useful code. Below are a handful of links that greatly enhanced my AngularJS knowledge in the last year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.parleys.com/play/5148922b0364bc17fc56c91b&quot;&gt;Devoxx 2012 - Re-imagining the browser with AngularJS&lt;/a&gt;. This is the original video I watched about AngularJS. I learned enough from this one video to start developing my first app for a client. I wrote about it in a four-part series on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv&quot;&gt;Developing with AngularJS&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ng-book.com/&quot;&gt;ng-book: The Complete Book on AngularJS&lt;/a&gt;. A great book with all the nitty-gritty Angular details you ever wanted to know.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UYVcY9EJcRs&quot;&gt;David Mosher&apos;s Testing Strategies for AngularJS&lt;/a&gt;. I stumbled upon this a week ago and it&apos;s greatly enhanced my knowledge of how to test AngularJS apps. It also introduced me to &lt;a href=&quot;http://linemanjs.com/&quot;&gt;Lineman&lt;/a&gt;, which I&apos;m thankful for.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://egghead.io/&quot;&gt;Egghead.io&lt;/a&gt; - bit-sized videos of AngularJS knowledge. Very useful for when you want to learn how to do a specific thing quickly.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philipsorst/angular-rest-springsecurity&quot;&gt;AngularJS + REST + Spring Security&lt;/a&gt;. This is a sample application from &lt;a href=&quot;http://sorst.net/&quot;&gt;Philip Sorst&lt;/a&gt; (&lt;a href=&quot;https://github.com/joshlong/boot-examples/tree/master/x-auth-security&quot;&gt;ported to Spring Boot&lt;/a&gt; by my good friend &lt;a href=&quot;http://www.joshlong.com/&quot;&gt;Josh Long&lt;/a&gt;) that shows how to integrate AngularJS with Spring Security for stateless, token-based authentication.
&lt;/ul&gt;
&lt;p&gt;One of the audience members at DeRailed recommended &lt;a href=&quot;http://www.thinkster.io/&quot;&gt;thinkster.io&lt;/a&gt; as a good resource too.&lt;/p&gt;
&lt;p&gt;Thanks to Fernand for inviting me to speak and causing me to write this presentation. Creating it greatly improved my AngularJS knowledge and I learned about some new tools in the process. If you&apos;d like to tap into my wealth of knowledge, I&apos;m available for a new gig in April. &lt;img src=&quot;//raibledesigns.com/images/smileys/wink.gif&quot; class=&quot;smiley&quot; alt=&quot;;)&quot; title=&quot;;)&quot;&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/comparing_jvm_web_frameworks_at</id>
        <title type="html">Comparing JVM Web Frameworks at vJUG</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/comparing_jvm_web_frameworks_at"/>
        <published>2014-02-06T10:54:17-07:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="jsf" scheme="http://roller.apache.org/ns/tags/" />
        <category term="grails" scheme="http://roller.apache.org/ns/tags/" />
        <category term="webframeworks" scheme="http://roller.apache.org/ns/tags/" />
        <category term="playframework" scheme="http://roller.apache.org/ns/tags/" />
        <category term="struts2" scheme="http://roller.apache.org/ns/tags/" />
        <category term="vaadin" scheme="http://roller.apache.org/ns/tags/" />
        <category term="tapestry" scheme="http://roller.apache.org/ns/tags/" />
        <category term="springmvc" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="wicket" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jvm" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;A couple months ago, I was invited to &lt;a href=&quot;http://www.meetup.com/virtualJUG/events/153096902/&quot;&gt;speak at Virtual JUG&lt;/a&gt; - an online-only Java User Group organized by the &lt;a href=&quot;http://zeroturnaround.com/&quot;&gt;ZeroTurnaround&lt;/a&gt; folks. They chose my Comparing JVM Web Frameworks presentation and we agreed I&apos;d speak yesterday morning. They used a combination of Google Hangouts, live streaming on YouTube and IRC to facilitate the meeting. It all went pretty smoothly and produced a comfortable speaking environment. To practice for vJUG, I delivered the same talk on Tuesday night at the &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/155080452/&quot;&gt;Denver Open Source Users Group&lt;/a&gt;.
&lt;p&gt;
The last time I delivered this talk was at &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Devoxx France&lt;/a&gt; in March 2013. I didn&apos;t change any of the format this time, keeping with referencing the Paradox of Choice and encouraging people to define constraints to help them make their decision. I did add a few new slides regarding RebelLabs&apos; &lt;a href=&quot;http://zeroturnaround.com/rebellabs/the-curious-coders-java-web-frameworks-comparison-spring-mvc-grails-vaadin-gwt-wicket-play-struts-and-jsf/&quot;&gt;Curious Coder&#8217;s Java Web Frameworks Comparison: Spring MVC, Grails, Vaadin, GWT, Wicket, Play, Struts and JSF&lt;/a&gt; and &lt;a href=&quot;http://zeroturnaround.com/rebellabs/the-2014-decision-makers-guide-to-java-web-frameworks/&quot;&gt;The 2014 Decision Maker&#8217;s Guide to Java Web Frameworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also updated all the pretty graphs (which may or may not have any significance) with the latest stats from Dice.com, LinkedIn, StackOverflow and respective mailing lists. Significant changes I found compared to one year ago:&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;A couple months ago, I was invited to &lt;a href=&quot;http://www.meetup.com/virtualJUG/events/153096902/&quot;&gt;speak at Virtual JUG&lt;/a&gt; - an online-only Java User Group organized by the &lt;a href=&quot;http://zeroturnaround.com/&quot;&gt;ZeroTurnaround&lt;/a&gt; folks. They chose my Comparing JVM Web Frameworks presentation and we agreed I&apos;d speak yesterday morning. They used a combination of Google Hangouts, live streaming on YouTube and IRC to facilitate the meeting. It all went pretty smoothly and produced a comfortable speaking environment. To practice for vJUG, I delivered the same talk on Tuesday night at the &lt;a href=&quot;http://www.meetup.com/DOSUG1/events/155080452/&quot;&gt;Denver Open Source Users Group&lt;/a&gt;.
&lt;p&gt;
The last time I delivered this talk was at &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_france_a_great_conference&quot;&gt;Devoxx France&lt;/a&gt; in March 2013. I didn&apos;t change any of the format this time, keeping with referencing the Paradox of Choice and encouraging people to define constraints to help them make their decision. I did add a few new slides regarding RebelLabs&apos; &lt;a href=&quot;http://zeroturnaround.com/rebellabs/the-curious-coders-java-web-frameworks-comparison-spring-mvc-grails-vaadin-gwt-wicket-play-struts-and-jsf/&quot;&gt;Curious Coder&#8217;s Java Web Frameworks Comparison: Spring MVC, Grails, Vaadin, GWT, Wicket, Play, Struts and JSF&lt;/a&gt; and &lt;a href=&quot;http://zeroturnaround.com/rebellabs/the-2014-decision-makers-guide-to-java-web-frameworks/&quot;&gt;The 2014 Decision Maker&#8217;s Guide to Java Web Frameworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also updated all the pretty graphs (which may or may not have any significance) with the latest stats from Dice.com, LinkedIn, StackOverflow and respective mailing lists. Significant changes I found compared to one year ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job Listings on Dice.com
&lt;ul&gt;
&lt;li&gt;Play Framework job listings increased almost 4x&lt;/li&gt;
&lt;li&gt;Tapestry jobs are 1/3 of what they were a year ago&lt;/li&gt;
&lt;li&gt;Wicket jobs are 1/2 of what they were a year ago&lt;/li&gt;
&lt;li&gt;JavaScript framework jobs are up quite a bit: Ember.js up ~300%, AngularJS up 900%, Backbone up 160%&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn Skills
&lt;ul&gt;
&lt;li&gt;Rails down ~30%&lt;/li&gt;
&lt;li&gt;Grails up 25%&lt;/li&gt;
&lt;li&gt;Play Framework up 200%&lt;/li&gt;
&lt;li&gt;Spring Roo up 40%&lt;/li&gt;
&lt;li&gt;Ember.js up 300%&lt;/li&gt;
&lt;li&gt;AngularJS up 840%&lt;/li&gt;
&lt;li&gt;Backbone up 200%&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can tell from these findings, AngularJS has gained quite a bit of mindshare in the last year. There&apos;s a lot of companies looking for JavaScript skills and quite a few folks have added JavaScript frameworks to their LinkedIn profiles.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&quot;https://www.youtube.com/watch?v=ygW8fJVlDxQ&quot;&gt;watch the recording on YouTube&lt;/a&gt; or click play in the embedded video below.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/ygW8fJVlDxQ&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/p&gt;
&lt;p&gt;You can also quickly browse the slide deck below, &lt;a href=&quot;http://static.raibledesigns.com/repository/presentations/Comparing_JVM_Web_Frameworks_February2014.pdf&quot;&gt;download the PDF&lt;/a&gt; or &lt;a href=&quot;http://www.slideshare.net/mraible/comparing-jvm-web-frameworks-february-2014&quot;&gt;view it on SlideShare&lt;/a&gt;.&lt;/li&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/30861557?rel=0&quot; width=&quot;512&quot; height=&quot;325&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; style=&quot;border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen webkitallowfullscreen mozallowfullscreen&gt; &lt;/iframe&gt;
&lt;/p&gt;
&lt;p&gt;Thanks to all the folks who attended these talks. And thanks to &lt;a href=&quot;http://twitter.com/dosug&quot;&gt;@dosug&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/virtualjug&quot;&gt;@virtualjug&lt;/a&gt; for giving me the opportunity to speak.&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/using_grunt_with_angularjs_for</id>
        <title type="html">Using Grunt with AngularJS for Front End Optimization</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/using_grunt_with_angularjs_for"/>
        <published>2014-01-15T12:15:52-07:00</published>
        <updated>2014-01-15T22:11:46-07:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="grunt" scheme="http://roller.apache.org/ns/tags/" />
        <category term="pagespeed" scheme="http://roller.apache.org/ns/tags/" />
        <category term="yslow" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;I&apos;m passionate about front end optimization and have been for years. My original inspiration was Steve Souders and his &lt;a href=&quot;http://raibledesigns.com/rd/entry/oscon_2008_even_faster_web&quot;&gt;Even Faster Web Sites talk at OSCON 2008&lt;/a&gt;. Since then, I&apos;ve &lt;a href=&quot;http://raibledesigns.com/rd/entry/javascript_and_css_concatenation&quot;&gt;optimized this blog&lt;/a&gt;, made it even faster &lt;a href=&quot;http://raibledesigns.com/rd/entry/new_look_and_feel_designed&quot;&gt;with a new design&lt;/a&gt;, doubled the speed of several apps for clients and showed how to &lt;a href=&quot;http://raibledesigns.com/rd/entry/improving_appfuse_s_pagespeed_with&quot;&gt;make AppFuse faster&lt;/a&gt;. As part of my &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries&quot;&gt;Devoxx 2013 presentation&lt;/a&gt;, I showed &lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;how to do page speed optimization in a Java webapp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I developed a couple AngularJS apps last year. To concat and minify their stylesheets and scripts, I used mechanisms that already existed in the projects. On one project, it was Ant and its &lt;a href=&quot;https://ant.apache.org/manual/Tasks/concat.html&quot;&gt;concat task&lt;/a&gt;. On the other, it was part of a Grails application, so I used the &lt;a href=&quot;http://grails.org/plugin/resources&quot;&gt;resources&lt;/a&gt; and &lt;a href=&quot;http://grails.org/plugin/yui-minify-resources&quot;&gt;yui-minify-resources&lt;/a&gt; plugins.
&lt;/p&gt;
&lt;p&gt;The Angular project I&apos;m working on now will be published on a web server, as well as bundled in an iOS native app. Therefore, I turned to &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt; to do the optimization this time. I found it to be quite simple, once I figured out &lt;a href=&quot;http://stackoverflow.com/questions/21056767/angular-and-grunt&quot;&gt;how to make it work with Angular&lt;/a&gt;. Based on my findings, I submitted a &lt;a href=&quot;https://github.com/angular/angular-seed/pull/131&quot;&gt;pull request to add Grunt to angular-seed&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;Below are the steps I used to add Grunt to my Angular project.&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;I&apos;m passionate about front end optimization and have been for years. My original inspiration was Steve Souders and his &lt;a href=&quot;http://raibledesigns.com/rd/entry/oscon_2008_even_faster_web&quot;&gt;Even Faster Web Sites talk at OSCON 2008&lt;/a&gt;. Since then, I&apos;ve &lt;a href=&quot;http://raibledesigns.com/rd/entry/javascript_and_css_concatenation&quot;&gt;optimized this blog&lt;/a&gt;, made it even faster &lt;a href=&quot;http://raibledesigns.com/rd/entry/new_look_and_feel_designed&quot;&gt;with a new design&lt;/a&gt;, doubled the speed of several apps for clients and showed how to &lt;a href=&quot;http://raibledesigns.com/rd/entry/improving_appfuse_s_pagespeed_with&quot;&gt;make AppFuse faster&lt;/a&gt;. As part of my &lt;a href=&quot;http://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries&quot;&gt;Devoxx 2013 presentation&lt;/a&gt;, I showed &lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;how to do page speed optimization in a Java webapp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I developed a couple AngularJS apps last year. To concat and minify their stylesheets and scripts, I used mechanisms that already existed in the projects. On one project, it was Ant and its &lt;a href=&quot;https://ant.apache.org/manual/Tasks/concat.html&quot;&gt;concat task&lt;/a&gt;. On the other, it was part of a Grails application, so I used the &lt;a href=&quot;http://grails.org/plugin/resources&quot;&gt;resources&lt;/a&gt; and &lt;a href=&quot;http://grails.org/plugin/yui-minify-resources&quot;&gt;yui-minify-resources&lt;/a&gt; plugins.
&lt;/p&gt;
&lt;p&gt;The Angular project I&apos;m working on now will be published on a web server, as well as bundled in an iOS native app. Therefore, I turned to &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt; to do the optimization this time. I found it to be quite simple, once I figured out &lt;a href=&quot;http://stackoverflow.com/questions/21056767/angular-and-grunt&quot;&gt;how to make it work with Angular&lt;/a&gt;. Based on my findings, I submitted a &lt;a href=&quot;https://github.com/angular/angular-seed/pull/131&quot;&gt;pull request to add Grunt to angular-seed&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;Below are the steps I used to add Grunt to my Angular project.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Grunt&apos;s command line interface with &quot;sudo npm install -g grunt-cli&quot;.&lt;/li&gt;
&lt;li&gt;Edit package.json to include a version number (e.g. &quot;version&quot;: &quot;1.0.0&quot;).&lt;/li&gt;
&lt;li&gt;Add Grunt plugins in package.json to do concat/minify/asset versioning:
&lt;pre class=&quot;brush: js&quot;&gt;
    &quot;grunt&quot;: &quot;~0.4.1&quot;,
    &quot;grunt-contrib-concat&quot;: &quot;~0.3.0&quot;,
    &quot;grunt-contrib-uglify&quot;: &quot;~0.2.7&quot;,
    &quot;grunt-contrib-cssmin&quot;: &quot;~0.7.0&quot;,
    &quot;grunt-usemin&quot;: &quot;~2.0.2&quot;,
    &quot;grunt-contrib-copy&quot;: &quot;~0.5.0&quot;,
    &quot;grunt-rev&quot;: &quot;~0.1.0&quot;,
    &quot;grunt-contrib-clean&quot;: &quot;~0.5.0&quot;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Run &quot;sudo npm install&quot; to install the project&apos;s dependencies.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;Gruntfile.js&lt;/code&gt; that runs all the plugins.
&lt;pre class=&quot;brush: js&quot;&gt;
module.exports = function (grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON(&apos;package.json&apos;),

        clean: [&quot;dist&quot;, &apos;.tmp&apos;],

        copy: {
            main: {
                expand: true,
                cwd: &apos;app/&apos;,
                src: [&apos;**&apos;, &apos;!js/**&apos;, &apos;!lib/**&apos;, &apos;!**/*.css&apos;],
                dest: &apos;dist/&apos;
            },
            shims: {
                expand: true,
                cwd: &apos;app/lib/webshim/shims&apos;,
                src: [&apos;**&apos;],
                dest: &apos;dist/js/shims&apos;
            }
        },

        rev: {
            files: {
                src: [&apos;dist/**/*.{js,css}&apos;, &apos;!dist/js/shims/**&apos;]
            }
        },

        useminPrepare: {
            html: &apos;app/index.html&apos;
        },

        usemin: {
            html: [&apos;dist/index.html&apos;]
        },

        uglify: {
            options: {
                report: &apos;min&apos;,
                mangle: false
            }
        }
    });

    grunt.loadNpmTasks(&apos;grunt-contrib-clean&apos;);
    grunt.loadNpmTasks(&apos;grunt-contrib-copy&apos;);
    grunt.loadNpmTasks(&apos;grunt-contrib-concat&apos;);
    grunt.loadNpmTasks(&apos;grunt-contrib-cssmin&apos;);
    grunt.loadNpmTasks(&apos;grunt-contrib-uglify&apos;);
    grunt.loadNpmTasks(&apos;grunt-rev&apos;);
    grunt.loadNpmTasks(&apos;grunt-usemin&apos;);

    // Tell Grunt what to do when we type &quot;grunt&quot; into the terminal
    grunt.registerTask(&apos;default&apos;, [
        &apos;copy&apos;, &apos;useminPrepare&apos;, &apos;concat&apos;, &apos;uglify&apos;, &apos;cssmin&apos;, &apos;rev&apos;, &apos;usemin&apos;
    ]);
};
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Add comments to app/index.html so usemin knows what files to process. The comments are the important part, your files will likely be different.
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;!-- build:css css/app-name.min.css --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;lib/bootstrap/bootstrap.min.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;lib/font-awesome/font-awesome.min.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;lib/toaster/toaster.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;css/app.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;css/custom.css&quot;/&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;css/responsive.css&quot;/&amp;gt;
&amp;lt;!-- endbuild --&amp;gt;
...

&amp;lt;!-- build:js js/app-name.min.js --&amp;gt;
&amp;lt;script src=&quot;lib/jquery/jquery-1.10.2.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/bootstrap/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/angular/angular.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/angular/angular-animate.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/angular/angular-cookies.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/angular/angular-resource.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/angular/angular-route.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/fastclick.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/toaster/toaster.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/webshim/modernizr.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;lib/webshim/polyfiller.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/services.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/controllers.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/filters.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/directives.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;!-- endbuild --&amp;gt;
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A couple of things to note: 1) the &lt;em&gt;copy&lt;/em&gt; task copies the &quot;shims&quot; directory from &lt;a href=&quot;http://afarkas.github.io/webshim/demos/&quot;&gt;Webshims lib&lt;/a&gt; because it loads files dynamically and 2) setting &quot;mangle: false&quot; on the &lt;em&gt;uglify&lt;/em&gt; task is necessary for Angular&apos;s dependency injection to work. I tried to use &lt;a href=&quot;https://npmjs.org/package/grunt-ngmin&quot;&gt;grunt-ngmin&lt;/a&gt; with uglify and had no luck.&lt;/p&gt;
&lt;p&gt;After making these changes, I&apos;m able to run &quot;grunt&quot; and get an optimized version of my app in the &quot;dist&quot; folder of my project. For development, I continue to run the app from my &quot;app&quot; folder, so I don&apos;t currently have a need for watching and processing assets on-the-fly. That could change if I start using LESS or CoffeeScript.
&lt;/p&gt;
&lt;p&gt;The results speak for themselves: from 27 requests to 5 on initial load, and only 3 requests for less than 2K after that.&lt;/p&gt;
&lt;table class=&quot;comparison&quot; style=&quot;max-width: 600px&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;YSlow&lt;/th&gt;
&lt;th&gt;Page Speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No optimization&lt;/td&gt;
&lt;td&gt;75 &lt;div style=&quot;float: right&quot;&gt;27 HTTP requests / 464K&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;55/100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache optimization (gzip and expires headers)&lt;/td&gt;
&lt;td&gt;89
&lt;div style=&quot;float: right&quot;&gt;
initial load: 26 requests / 166K&lt;br/&gt;
primed cache: 4 requests / 40K 
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;88/100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache + concat/minified/versioned files&lt;/td&gt;
&lt;td&gt;98
&lt;div style=&quot;float: right&quot;&gt;
initial load: 5 requests / 136K&lt;br/&gt;
primed cache: 3 requests / 1.4K
&lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;93/100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Andreas Andreou has a &lt;a href=&quot;https://twitter.com/andyhot/status/423571136538877952&quot;&gt;nice tip&lt;/a&gt; on how to reduce the LOC in this example.
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &quot;matchdep&quot; as a dependency in package.json (or run &quot;sudo npm install matchdep --save-dev&quot;).
&lt;pre class=&quot;brush: js; gutter: false&quot;&gt;
&quot;matchdep&quot;: &quot;~0.3.0&quot;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Replace all the &lt;code&gt;grunt.loadNpmTasks(...)&lt;/code&gt; calls with the following:
&lt;pre class=&quot;brush: js; gutter: false&quot;&gt;
require(&apos;matchdep&apos;).filterDev(&apos;grunt-*&apos;).forEach(grunt.loadNpmTasks);
&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thanks Andreas!&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries</id>
        <title type="html">Devoxx 2013 + a Nordic Countries Speaking Tour</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/devoxx_2013_a_nordic_countries"/>
        <published>2013-11-28T12:07:26-07:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/Java" label="Java" />
        <category term="css" scheme="http://roller.apache.org/ns/tags/" />
        <category term="devoxx" scheme="http://roller.apache.org/ns/tags/" />
        <category term="nordea" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="avegagroup" scheme="http://roller.apache.org/ns/tags/" />
        <category term="java" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jvm" scheme="http://roller.apache.org/ns/tags/" />
        <category term="webdevelopment" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089788834/&quot;
       title=&quot;Trish at Pelgrom by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Trish at Pelgrom&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
    Two weeks ago, Trish and I boarded a flight for one of our favorite conferences: &lt;a
        href=&quot;http://devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;. After a brief layover in Frankfurt, we arrived in Amsterdam and took a train to Antwerp. Within hours, we&apos;d settled into our hotel near the center of Antwerp and strolled over to the
    dungeonous, yet cozy, &lt;a href=&quot;http://www.pelgrom.be/&quot;&gt;Pelgrom restaurant&lt;/a&gt;. We were hoping for a delicious
    dinner, but found much more. We
    ran into James Ward, Dick Wall and a number of other enthusiastic speakers from the conference. Since I had to speak
    the next day, we didn&apos;t stay long, but we did share a number of laughs with some great people.
&lt;/p&gt;
&lt;p&gt;
    Tuesday (November 12), was a University Day at Devoxx, and I had my talk that afternoon. I spent a couple hours
    finishing up my talk
    that morning, then grabbed a taxi to head to the conference. I was honored with the opportunity to speak in Room 8,
    which is a huge theater that holds several hundred people.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089790274/&quot;
       title=&quot;Devoxx: A Speaker&apos;s Perspective by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Devoxx: A Speaker&apos;s Perspective&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102110783/&quot;
       title=&quot;The Modern JVM Web Developer by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;The Modern JVM Web Developer&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102183263/&quot;
       title=&quot;AngularJS Deep Dive by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;AngularJS Deep Dive&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    I presented a lengthened version of The Modern Java Web Developer presentation I did early this year (at &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer&quot;&gt;Denver&apos;s JUG&lt;/a&gt; and &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/javaone_2013_my_presentations&quot;&gt;JavaOne&lt;/a&gt;). Based on &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer1&quot;&gt;your feedback&lt;/a&gt;, I chose to do deep
    dives on AngularJS, Bootstrap and Page Speed. I&apos;ve always enjoyed speaking at Devoxx because attendees are so
    enthusiastic and passionate about the conference. I received an immense amount of feedback, both in praises and
    criticisms. The critics indicated there were &lt;a
        href=&quot;http://steveschols.wordpress.com/2013/11/21/devoxx-2013-a-retrospective/&quot;&gt;too many buzzwords&lt;/a&gt; and not
    enough substance. Others complained that the AngularJS &lt;a
        href=&quot;http://www.informit.com/articles/article.aspx?p=1930512&amp;amp;seqNum=3&quot;&gt;Lipsync&lt;/a&gt; that I did was &lt;em&gt;too
    deep&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    I made sure to review and process everyone&apos;s comments, and then used them to improve the presentation throughout the
    following week. I learned to elaborate on the fact that many of the technologies were important to know about, but
    not important to know through-and-through. I made sure to mention that the use of CoffeeScript and LESS is often
    limited (or embraced) by team members and their willingness to try new things. If you&apos;re not writing thousands of
    lines of JavaScript or CSS, it probably doesn&apos;t make sense to use these languages. Furthermore, if your team members
    are struggling to write JavaScript or CSS, introducing a new language is probably not the best thing. I also
    reminded people to be skeptical of new technology, but also to be open-minded and give everything a chance. The
    10-minute, download-and-try test, is a great way to do that.
&lt;/p&gt;
&lt;p&gt;You can find my presentation below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-modern-java-web-developer-bootcamp-devoxx-2013&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;http://www.slideshare.net/slideshow/embed_code/28649243?rel=0&quot; width=&quot;600&quot; height=&quot;375&quot; frameborder=&quot;0&quot;
            marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
            style=&quot;border:1px solid #ccc;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    Within this presentation, there are links to each of the deep dives. The last two are screencasts that I added
    audio to a few days ago.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://static.raibledesigns.com/bootstrap3&quot;&gt;Bootstrap 3&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/angularjs-deep-dive&quot;&gt;AngularJS Deep Dive&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;Page Speed Demo&lt;/a&gt;
&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7452/11089788834_b7541d335d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089788834/&quot;
       title=&quot;Trish at Pelgrom by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7452/11089788834_b7541d335d_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Trish at Pelgrom&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
    Two weeks ago, Trish and I boarded a flight for one of our favorite conferences: &lt;a
        href=&quot;http://devoxx.be/&quot;&gt;Devoxx&lt;/a&gt;. After a brief layover in Frankfurt, we arrived in Amsterdam and took a train to Antwerp. Within hours, we&apos;d settled into our hotel near the center of Antwerp and strolled over to the
    dungeonous, yet cozy, &lt;a href=&quot;http://www.pelgrom.be/&quot;&gt;Pelgrom restaurant&lt;/a&gt;. We were hoping for a delicious
    dinner, but found much more. We
    ran into James Ward, Dick Wall and a number of other enthusiastic speakers from the conference. Since I had to speak
    the next day, we didn&apos;t stay long, but we did share a number of laughs with some great people.
&lt;/p&gt;
&lt;p&gt;
    Tuesday (November 12), was a University Day at Devoxx, and I had my talk that afternoon. I spent a couple hours
    finishing up my talk
    that morning, then grabbed a taxi to head to the conference. I was honored with the opportunity to speak in Room 8,
    which is a huge theater that holds several hundred people.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2809/11089790274_ac70261260_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089790274/&quot;
       title=&quot;Devoxx: A Speaker&apos;s Perspective by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2809/11089790274_ac70261260_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Devoxx: A Speaker&apos;s Perspective&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102110783/&quot;
       title=&quot;The Modern JVM Web Developer by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2805/11102110783_8c0ea95e1d_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;The Modern JVM Web Developer&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3727/11102183263_7fc28918f6_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102183263/&quot;
       title=&quot;AngularJS Deep Dive by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3727/11102183263_7fc28918f6_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;AngularJS Deep Dive&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    I presented a lengthened version of The Modern Java Web Developer presentation I did early this year (at &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer&quot;&gt;Denver&apos;s JUG&lt;/a&gt; and &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/javaone_2013_my_presentations&quot;&gt;JavaOne&lt;/a&gt;). Based on &lt;a
        href=&quot;http://raibledesigns.com/rd/entry/the_modern_java_web_developer1&quot;&gt;your feedback&lt;/a&gt;, I chose to do deep
    dives on AngularJS, Bootstrap and Page Speed. I&apos;ve always enjoyed speaking at Devoxx because attendees are so
    enthusiastic and passionate about the conference. I received an immense amount of feedback, both in praises and
    criticisms. The critics indicated there were &lt;a
        href=&quot;http://steveschols.wordpress.com/2013/11/21/devoxx-2013-a-retrospective/&quot;&gt;too many buzzwords&lt;/a&gt; and not
    enough substance. Others complained that the AngularJS &lt;a
        href=&quot;http://www.informit.com/articles/article.aspx?p=1930512&amp;amp;seqNum=3&quot;&gt;Lipsync&lt;/a&gt; that I did was &lt;em&gt;too
    deep&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;
    I made sure to review and process everyone&apos;s comments, and then used them to improve the presentation throughout the
    following week. I learned to elaborate on the fact that many of the technologies were important to know about, but
    not important to know through-and-through. I made sure to mention that the use of CoffeeScript and LESS is often
    limited (or embraced) by team members and their willingness to try new things. If you&apos;re not writing thousands of
    lines of JavaScript or CSS, it probably doesn&apos;t make sense to use these languages. Furthermore, if your team members
    are struggling to write JavaScript or CSS, introducing a new language is probably not the best thing. I also
    reminded people to be skeptical of new technology, but also to be open-minded and give everything a chance. The
    10-minute, download-and-try test, is a great way to do that.
&lt;/p&gt;
&lt;p&gt;You can find my presentation below, download it from &lt;a href=&quot;http://raibledesigns.com/rd/page/publications&quot;&gt;my
    presentations page&lt;/a&gt;, or view it &lt;a
        href=&quot;http://www.slideshare.net/mraible/the-modern-java-web-developer-bootcamp-devoxx-2013&quot;&gt;on SlideShare&lt;/a&gt;.
&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
    &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/28649243?rel=0&quot; width=&quot;600&quot; height=&quot;375&quot; frameborder=&quot;0&quot;
            marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot;
            style=&quot;border:1px solid #ccc;border-width:1px 1px 0;margin-bottom:5px&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;
    Within this presentation, there are links to each of the deep dives. The last two are screencasts that I added
    audio to a few days ago.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a href=&quot;http://static.raibledesigns.com/bootstrap3&quot;&gt;Bootstrap 3&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/angularjs-deep-dive&quot;&gt;AngularJS Deep Dive&lt;/a&gt; | 
&lt;a href=&quot;https://vimeo.com/mraible/page-speed-demo&quot;&gt;Page Speed Demo&lt;/a&gt;
&lt;/p&gt;
&lt;div style=&quot;width: 575px; margin: auto;&quot;&gt;
    &lt;div style=&quot;display: inline;&quot;&gt;
        &lt;iframe src=&quot;//player.vimeo.com/video/80314102&quot; width=&quot;280&quot; height=&quot;175&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
    &lt;div style=&quot;display: inline; float: right;&quot;&gt;
        &lt;iframe src=&quot;//player.vimeo.com/video/80391343&quot; width=&quot;280&quot; height=&quot;175&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
    We stayed in Antwerp until Friday, attending the conference, taking pictures, networking over beers and having a
    fabulous time with everyone attending Devoxx.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7350/11102130224_8b2d9d1109_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102130224/&quot;
       title=&quot;Street Shadows by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7350/11102130224_8b2d9d1109_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Street Shadows&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3706/11102238913_441d2fb728_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102238913/&quot;
       title=&quot;Devoxx posse at the Antwerp Town Hall by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3706/11102238913_441d2fb728_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Devoxx posse at the Antwerp Town Hall&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm6.staticflickr.com/5533/11102264113_6747b92a61_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102264113/&quot;
       title=&quot;Matt and Josh next to the Cathedral Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm6.staticflickr.com/5533/11102264113_6747b92a61_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Matt and Josh next to the Cathedral Antwerp&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm8.staticflickr.com/7371/11089691035_34e95b21d0_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mraible/11089691035/&quot;
       title=&quot;Devoxx Late Night by mraible, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7371/11089691035_34e95b21d0_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Devoxx Late Night&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

    &lt;a href=&quot;http://farm4.staticflickr.com/3775/11102166205_fe4c769a30_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102166205/&quot;
       title=&quot;Trish and Amelia by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3775/11102166205_fe4c769a30_s.jpg&quot; width=&quot;75&quot;
            alt=&quot;Trish and Amelia&quot; style=&quot;border: 1px solid black; margin-left: 10px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3801/11102112776_b8b7c90b9a_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102112776/&quot;
       title=&quot;Town Hall Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3801/11102112776_b8b7c90b9a.jpg&quot; width=&quot;500&quot;
            alt=&quot;Town Hall Antwerp&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7399/11102078745_37f02cdc32_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102078745/&quot;
       title=&quot;Antwerp square by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7399/11102078745_37f02cdc32.jpg&quot; width=&quot;500&quot;
            alt=&quot;Antwerp square&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

&lt;/p&gt;

&lt;p&gt;
    Thursday night, we dined at &lt;a
        href=&quot;http://www.tripadvisor.com/Restaurant_Review-g188636-d2536032-Reviews-Matty-Antwerp_Antwerp_Province.html&quot;&gt;Matty&lt;/a&gt;,
    one of the best restaurants in Antwerp.
    The food was excellent and provided a nice start for a night that included the Devoxx Party at Noxx and a journey to
    &lt;a href=&quot;http://biercentral.be/&quot;&gt;Bier Central&lt;/a&gt;.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2813/11102230866_8b45e6d1e5_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102230866/&quot;
       title=&quot;Matty Restaurant Antwerp by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2813/11102230866_8b45e6d1e5_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Matty Restaurant Antwerp&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2872/11102238026_29af90ac67_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102238026/&quot;
       title=&quot;Steak at Matty by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2872/11102238026_29af90ac67_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Steak at Matty&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7291/11102264004_71837f43e7_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102264004/&quot;
       title=&quot;Dessert by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7291/11102264004_71837f43e7_q.jpg&quot; width=&quot;150&quot;
            alt=&quot;Dessert&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    On Friday, we traveled to Brussels for a weekend in one of the &lt;a
        href=&quot;http://www.usatoday.com/story/travel/destinations/2012/10/02/10-best-beer-cities-in-the-world/1608885/&quot;&gt;best
    beer cities in the world&lt;/a&gt;. Trish booked us a room at the &lt;a href=&quot;http://www.hotel-saint-michel.be/&quot;&gt;Hotel Saint
    Michel&lt;/a&gt;, which was right on the &lt;a href=&quot;http://www.europeish.com/25-amazingly-stunning-european-squares/&quot;&gt;most
    beautiful square in Europe&lt;/a&gt;. Their pre-Christmas light show was spectacular. The beer was delicious, the location
    was magnificent and we thoroughly enjoyed ourselves, especially the &lt;a
        href=&quot;http://farm8.staticflickr.com/7342/11102281806_49f4a65516_c.jpg&quot;
        data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102281806/&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;mussels in
    Brussels&lt;/a&gt;. Thanks in particular to &lt;a href=&quot;https://twitter.com/snicoll&quot;&gt;St&#233;phane&lt;/a&gt; and &lt;a
        href=&quot;https://twitter.com/philipluppens&quot;&gt;Philip&lt;/a&gt; for your recommendations.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2830/11102444343_3005a06b72_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102444343/&quot;
       title=&quot;Grand Place Brussels by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2830/11102444343_3005a06b72.jpg&quot; width=&quot;500&quot;
            alt=&quot;Grand Place Brussels&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p id=&quot;nordic-speaking-tour&quot;&gt;
    &lt;strong&gt;Nordic Countries Speaking Tour&lt;/strong&gt;&lt;br/&gt;
    On Sunday (November 17), we flew to Stockholm to being the second half our trip. Nordea hired me to deliver my
    Devoxx presentation as part their Java Competence Network. Nordea&apos;s Jonny Berggren first contacted me in March 2010
    about this opportunity, so it was fun to see it finally happen. We agreed that I&apos;d speak at their four main
    locations: Stockholm, Helsinki, Oslo and Copenhagen. 
&lt;/p&gt;
&lt;p&gt;
    Mattias Karlsson (of &lt;a href=&quot;http://www.jfokus.se/&quot;&gt;Jfokus&lt;/a&gt; fame) also presented me with an opportunity to speak at his
    company while I was in Sweden.
&lt;/p&gt;
&lt;p&gt;
    I started the week delivering my talk on Monday afternoon at Nordea. Then we met up with Mattias, walked to his
    company and I delivered it again 45 minutes later. It was exhausting to talk for six hours in one day, but it all seemed
    to go well. I especially enjoyed the enthusiasm of Mattias&apos;s Avega Group.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3817/11102348876_9d52cb9c3e_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102348876/&quot;
       title=&quot;Speaking at Avega Group by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3817/11102348876_9d52cb9c3e_n.jpg&quot; width=&quot;320&quot;
            alt=&quot;Speaking at Avega Group&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    That evening, we took the train to the airport, boarded a flight to Helsinki and arrived just after midnight. While
    on the train, we sat next to a Finlander, Karol, that gave us all kinds of great advice on what to do. Tuesday in
    Helsinki was cold and dreary; perfect sauna weather. We walked around a bit in the rain that morning and visited
    Senate Square on Karol&apos;s recommendation. I mentioned to the developers there that my Mom&apos;s grandparents were from
    Finland (Oulu and Hamina), and that I&apos;d grown up in a rustic cabin built by my Finish grandfather, Matti Hill.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3749/11102390024_179d50d342_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102390024/&quot;
       title=&quot;Matt in Helsinki Capitol by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3749/11102390024_179d50d342_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Matt in Helsinki Capitol&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm3.staticflickr.com/2824/11102401054_357a5665f8_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102401054/&quot;
       title=&quot;Downtown Helsinki by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm3.staticflickr.com/2824/11102401054_357a5665f8_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Downtown Helsinki&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    We flew to Oslo Tuesday evening, rode the super-fast train from the airport and got to bed just before midnight.
    Wednesday morning, we walked around the Vigelandsparken Sculpture Park, and then I headed to the Nordea office while
    Trish did a walkabout and rode a Viking ship around the bay. We met up afterwards at the wonderful &lt;a
        href=&quot;http://www.beerpalace.no/&quot;&gt;Beer Palace&lt;/a&gt; for some pizza and delicious German/Belgian beer.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3716/11102323505_758f0b2480_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102323505/&quot;
       title=&quot;Oslo Opera by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3716/11102323505_758f0b2480.jpg&quot; width=&quot;500&quot;
            alt=&quot;Oslo Opera&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7445/11102326065_c9807c37d5_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102326065/&quot;
       title=&quot;Ship and Oslo Opera by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7445/11102326065_c9807c37d5_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Ship and Oslo Opera&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    &lt;a href=&quot;http://farm4.staticflickr.com/3816/11102531063_e63b78e22e_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102531063/&quot;
       title=&quot;Crew&apos;s Sunset Oslo by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm4.staticflickr.com/3816/11102531063_e63b78e22e_m.jpg&quot; width=&quot;240&quot;
            alt=&quot;Crew&apos;s Sunset Oslo&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p style=&quot;text-align: center&quot;&gt;

    &lt;a href=&quot;http://farm6.staticflickr.com/5512/11102364565_d44c5bf9dc_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102364565/&quot;
       title=&quot;Oslo ship in the Harbor by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm6.staticflickr.com/5512/11102364565_d44c5bf9dc.jpg&quot; width=&quot;500&quot;
            alt=&quot;Oslo ship in the Harbor&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&quot;http://farm8.staticflickr.com/7317/11102552963_caa2a844cd_c.jpg&quot;
       data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102552963/&quot;
       title=&quot;Danish Welcome by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img
            src=&quot;//farm8.staticflickr.com/7317/11102552963_caa2a844cd_t.jpg&quot; width=&quot;100&quot;
            alt=&quot;Danish Welcome&quot; class=&quot;picture&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;
    Copenhagen was our last stop, a city which neither of us have ever traveled to. After arriving, we quickly got a
    hint that Copenhagen was a special place. We did a bit of research on &lt;a
        href=&quot;http://en.wikipedia.org/wiki/Copenhagen&quot;&gt;Wikipedia&lt;/a&gt; and learned it was The City of Bikes and their
    craft brewing industry has blossomed in the last decade, now sporting over 100 microbreweries. My last talk on
    Thursday morning went very well, especially since my presentation and advice was well polished by that point.
&lt;/p&gt;
&lt;p&gt;
    That afternoon, we rented bikes from our hotel, slowly ate &lt;a
        href=&quot;http://www.tripadvisor.com/Restaurant_Review-g189541-d3454699-Reviews-Sticks_n_Sushi_Tivoli_Hotel_Congress_Center-Copenhagen_Zealand.html&quot;&gt;sushi&lt;/a&gt;
    on the top of the Tivoli Hotel, and then rode to &lt;a href=&quot;http://www.tivoli.dk/&quot;&gt;Tivoli Gardens&lt;/a&gt;. Tivoli Gardens
    was decorated as a Christmas wonderland and their amusement park made us smile and giggle with glee. We stayed there
    for hours before riding home. Biking around town with hundreds of other cyclists was really cool and fun. I hope
    Denver gets &lt;em&gt;Copenhagenized&lt;/em&gt; someday, the abundance of bike-only roads is simply awesome.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3778/11102417765_08ccdbdc77_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102417765/&quot; title=&quot;Happy Couple at Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3778/11102417765_08ccdbdc77_q.jpg&quot; width=&quot;150&quot; alt=&quot;Happy Couple at Tivoli&quot; style=&quot;border: 1px solid black&quot;&gt;&lt;/a&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3821/11102492616_cc34d454e9_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102492616/&quot; title=&quot;Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3821/11102492616_cc34d454e9_q.jpg&quot; width=&quot;150&quot; alt=&quot;Tivoli&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;a href=&quot;http://farm4.staticflickr.com/3689/11102535804_660b10428a_c.jpg&quot; data-href=&quot;http://www.flickr.com/photos/mcginityphoto/11102535804/&quot; title=&quot;Tivoli by McGinityPhoto, on Flickr&quot; rel=&quot;lightbox[devoxx2013]&quot;&gt;&lt;img src=&quot;//farm4.staticflickr.com/3689/11102535804_660b10428a_q.jpg&quot; width=&quot;150&quot; alt=&quot;Tivoli&quot; style=&quot;border: 1px solid black; margin-left: 15px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
    Our Nordic countries tour was especially pleasant because Christmas decorations were everywhere. In the US, many
    folks look down upon Christmas decorations before Thanksgiving, but since Europeans don&apos;t celebrate Thanksgiving - there&apos;s no reason not to ease into the Christmas spirit. We figured Abbie and Jack would have a blast at Tivoli in a future November or December. Yes, it was
    a bit chilly (20-30&amp;deg;F) in most of the countries, but we were well dressed for it. Unfortunately, we didn&apos;t get
    to see any snow.
&lt;/p&gt;
&lt;p&gt;
    Many thanks to Devoxx, Nordea, Mattias&apos;s Avega Group and the hundreds of developers who listened to me talk about being a
    modern web developer. We had a wonderful time speaking, laughing, photographing, drinking your delicious beer and
    seeing all your smiling faces.&lt;/p&gt;
&lt;p style=&quot;border-top: 1px dotted silver; color: #999; padding-top: 5px&quot;&gt;
    For more photos of this whirlwind trip, see Trish&apos;s &lt;a href=&quot;http://www.flickr.com/photos/mcginityphoto/sets/72157638119009136/&quot;&gt;
    EU and Scandanavian Speaking Tour 2013&lt;/a&gt;, while mine are in
    &lt;a href=&quot;http://www.flickr.com/photos/mraible/sets/72157638123386015/&quot;&gt;Devoxx 2013 and Nordic Speaking Tour&lt;/a&gt;.
&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv</id>
        <title type="html">Developing with AngularJS - Part IV: Making it Pop</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv"/>
        <published>2013-09-12T10:54:29-06:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="design" scheme="http://roller.apache.org/ns/tags/" />
        <category term="css3" scheme="http://roller.apache.org/ns/tags/" />
        <category term="responsivedesign" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="taleo" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;
Welcome to the final article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I learned its concepts, beat my head against-the-wall with and finally tamed it enough to create a &quot;My Dashboard&quot; feature for a client. For previous articles, please see the following:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;Part III: Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last mile of development for the My Dashboard feature was to spice things up a bit and make it look better. We hired a design company to come up a new look and feel and they went to work. Within a week, we had a meeting with them and they presented a few different options. We picked the one we liked the best and went to work. Below are screenshots that I used to implement the new design.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971086/&quot; href=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_c.jpg&quot; title=&quot;My Dashboard - New Design&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard - New Design&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971332/&quot; href=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_c.jpg&quot; title=&quot;My Dashboard with Show More&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard with Show More&quot; style=&quot;border: 1px solid silver; margin-left: 20px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
Welcome to the final article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I learned its concepts, beat my head against-the-wall with and finally tamed it enough to create a &quot;My Dashboard&quot; feature for a client. For previous articles, please see the following:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;Part III: Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last mile of development for the My Dashboard feature was to spice things up a bit and make it look better. We hired a design company to come up a new look and feel and they went to work. Within a week, we had a meeting with them and they presented a few different options. We picked the one we liked the best and went to work. Below are screenshots that I used to implement the new design.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971086/&quot; href=&quot;http://farm6.staticflickr.com/5447/8904971086_e24b89fa7e_c.jpg&quot; title=&quot;My Dashboard - New Design&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm6.staticflickr.com/5447/8904971086_e24b89fa7e_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard - New Design&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971332/&quot; href=&quot;http://farm3.staticflickr.com/2866/8904971332_9e919549b8_c.jpg&quot; title=&quot;My Dashboard with Show More&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2866/8904971332_9e919549b8_m.jpg&quot; width=&quot;240&quot; height=&quot;229&quot; alt=&quot;My Dashboard with Show More&quot; style=&quot;border: 1px solid silver; margin-left: 20px&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;At first, I thought implementing this design might take quite a bit of effort, since it looked like it used custom fonts. It&apos;s true we could use CSS3&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face&quot;&gt;@font-face&lt;/a&gt;, but I knew it might take awhile to find the right fonts with the appropriate licenses. When I received the screenshot below, I was pleased to see that all fonts were web-safe.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904971378/&quot; href=&quot;http://farm3.staticflickr.com/2839/8904971378_a0ac267cc0_c.jpg&quot; title=&quot;My Dashboard Fonts&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2839/8904971378_a0ac267cc0.jpg&quot; width=&quot;500&quot; height=&quot;430&quot; alt=&quot;My Dashboard Fonts&quot; style=&quot;border: 1px solid silver&quot;&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;h3 id=&quot;design-elements&quot;&gt;Design Elements&lt;/h3&gt;
&lt;p&gt;There are a number of elements in this new design that I had to create. For example, if numbers were only 1 digit, we had to add a leading zero to them in the summary band. Other design elements we needed to implement are listed below:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;A background image that filled the page&lt;/li&gt;&lt;li&gt;Fade to white on summary widget titles&lt;/li&gt;&lt;li&gt;A responsive grid for summary widgets&lt;/li&gt;&lt;li&gt;Provide a colored background for odd rows in the summary grid&lt;/li&gt;&lt;li&gt;Add a &quot;Show More&quot; band at the bottom of Tasks, Summary and Reports when there&apos;s more items to display&lt;/li&gt;&lt;/ul&gt;
&lt;p class=&quot;nolink&quot;&gt;In addition to these elements, there was quite a bit of work to conform to the new colors, fonts and drop-shadows. I implemented all of these using CSS3 (border-radius, box-shadow, box-sizing, linear-gradient), and lots of trial-and-error. To use the best fonts across various devices, I used CSS-Trick&apos;s &lt;a href=&quot;http://css-tricks.com/snippets/css/font-stacks/&quot;&gt;Font Stacks&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;new-background&quot;&gt;New Background&lt;/h3&gt;
&lt;p&gt;The new background shown in the screenshots above has a light source in the middle of it. Therefore, it&apos;s impossible to tile/repeat it across the page since it&apos;s not uniform. To make it work, I used a 1024 x 768 image and CSS3&apos;s &lt;code&gt;background-size: cover&lt;/code&gt;. For more information on background-size, see SitePoint&apos;s &lt;a href=&quot;http://www.sitepoint.com/css3-background-size-property/&quot;&gt;How to Resize Background Images with CSS3&lt;/a&gt;. This worked great on smaller screens, but we noticed some issues on 30&quot; monitors. Therefore, we ended up getting a new repeatable background and stopped using background-size.&lt;/p&gt;
&lt;h3 id=&quot;leadingzero-filter&quot;&gt;LeadingZero filter&lt;/h3&gt;
&lt;p&gt;For the first leading zero feature, I wrote an Angular filter. I put the code for this in &lt;em&gt;filters.js&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
filter(&apos;leadingZero&apos;, function() {
    return function(input) {
        if (input.length === 1) {
            return &quot;0&quot; + input;
        } else if (input.length &amp;gt; 2) {
            return &quot;+99&quot;;
        } else {
            return input;
        }
    }
});
&lt;/pre&gt;
&lt;p&gt;This filter is used in the HTML template as follows:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;summary-value&quot;&amp;gt;{{widget.value | leadingZero}}&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;text-fadeout&quot;&gt;Text Fade Out&lt;/h3&gt;
&lt;p&gt;To implement the fade-to-white text in summary titles, I started with &lt;a href=&quot;http://css-tricks.com/text-fade-read-more/&quot;&gt;this tutorial&lt;/a&gt;. I quickly discovered that it worked best for vertical text blocks and not for horizontal text. Then I found &lt;a href=&quot;http://xion.org.pl/2011/12/26/text-ellipsis-with-gradient-fade-in-pure-css/&quot;&gt;Text Ellipsis with Gradient Fade in Pure CSS&lt;/a&gt;, which uses :after to position a block over the text that fades to white. Since the title is not the right-most element (the numbers are), I had to figure out best positioning that worked cross-browser. Below is the CSS I used to implement this feature.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.dashboard .summary-title:after {
    display: block;
    position: absolute;
    right: 66px;
    top: 5px;
    bottom: 5px;
    width: 30px;
    background: -moz-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* FF3.6+ */
    background: -webkit-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* Opera 11.10+ */
    background: -ms-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* IE10+ */
    background: linear-gradient(to right,  rgba(255,255,255,0) 0%, #fff 20px); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr=&apos;#00ffffff&apos;, endColorstr=&apos;#ffffff&apos;,GradientType=1 ); /* IE6-9 */
    content: &quot;&quot;;
}
&lt;/pre&gt;
&lt;h3 id=&quot;responsive-grid&quot;&gt;Responsive Grid&lt;/h3&gt;
&lt;p&gt;To implement the responsive grid of summary widgets, I started with Codrops&apos; &lt;a href=&quot;http://tympanus.net/codrops/2013/04/17/responsive-full-width-grid/&quot;&gt;Responsive Full Width Grid Tutorial&lt;/a&gt;. This proved to be a great model and I used the following CSS to position all the &amp;lt;li&amp;gt;&apos;s appropriately. In the code below, &lt;code&gt;.summary-item&lt;/code&gt; is the class on the &amp;lt;li&amp;gt; elements.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.dashboard .summary-item {
    border-right: 1px solid #d1d1d1;
    border-bottom: 1px solid #d1d1d1;
    /* put the border on the inside of the box */
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -ms-box-sizing: border-box;
    box-sizing: border-box;
    font-family: Constantia, &quot;Lucida Bright&quot;, Lucidabright, &quot;Lucida Serif&quot;, Lucida, &quot;DejaVu Serif&quot;, &quot;Bitstream Vera Serif&quot;, &quot;Liberation Serif&quot;, Georgia, serif;
    font-size: 14px;
    color: #666;
    height: 50px;
    box-shadow: inset 0 0 6px rgba(0,0,0, 0.25);
    /* responsive grid */
    position: relative;
    float: left;
    overflow: hidden;
    width: 25% /* Fallback */
    width: -webkit-calc(100% / 4);
    width: calc(100% / 4);
}

@media screen and (max-width: 1400px) {
    .dashboard .summary-item {
        width: 33.33333333333333%; /* Fallback */
        width: -webkit-calc(100% / 3);
        width: calc(100% / 3);
    }
}

@media screen and (max-width: 1000px) {
    .dashboard .summary-item {
        width: 50%; /* Fallback */
        width: -webkit-calc(100% / 2);
        width: calc(100% / 2);
    }
}
&lt;/pre&gt;
&lt;p&gt;This worked great in most browsers, but we did find an issue with IE9. When squishing/expanding the browser window, sometimes there would be a blank column on the right side. To fix this, I changed the width on the default &lt;code&gt;.summary-item&lt;/code&gt; to be 25%, and removed the lines with &lt;code&gt;calc&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.dashboard .summary-item {
    ...
    width: 25%
}
&lt;/pre&gt;
&lt;h3 id=&quot;coloring-oddrows&quot;&gt;Coloring Odd Rows&lt;/h3&gt;
&lt;p&gt;Coloring odd rows in a table is easy, but when the rows are in a responsive grid, that&apos;s a whole different story. For tables, the CSS rules are extremely simple:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
tr:nth-child(even) {background: #CCC}
tr:nth-child(odd) {background: #FFF}
&lt;/pre&gt;
&lt;p&gt;Via Twitter, &lt;a href=&quot;http://twitter.com/tomaslin&quot;&gt;@tomaslin&lt;/a&gt; advised me that the nth-child selector could probably be used for this, but it&apos;d likely require some JavaScript to make it responsive. I found the excellent &lt;a href=&quot;http://nthmaster.com/&quot;&gt;Master of the :nth-child&lt;/a&gt; and began trying to figure it out. The following function is what we now use to color odd rows in the Summary Bar.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function colorRows() {
    var lisInRow = 0;
    var items = $(&apos;.summary-items li&apos;);
    items.each(function() {
        if($(this).prev().length &amp;gt; 0) {
            if($(this).position().top != $(this).prev().position().top) return false;
            lisInRow++;
        }
        else {
            lisInRow++;
        }
    });
    var rows = items.length / lisInRow;
    for (var i = 0; i &amp;lt; rows; i++) {
        var selector = &quot;nth-child(n+{x}):nth-child(-n+{y})&quot;;
        var x = (lisInRow * i) + 1;
        var y = x + (lisInRow - 1);
        selector = selector.replace(&apos;{x}&apos;, &apos;&apos; + x);
        selector = selector.replace(&apos;{y}&apos;, &apos;&apos; + y);
        if (i % 2) {
            $(&apos;.summary-items li:&apos; + selector).addClass(&apos;odd&apos;);
        } else {
            $(&apos;.summary-items li:&apos; + selector).removeClass(&apos;odd&apos;);
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;The above code is in &lt;em&gt;dashboard.js&lt;/em&gt; and is called anytime the browser window is resized (to adapt to the responsive grid).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(window).resize(colorRows);
&lt;/pre&gt;
&lt;p&gt;It&apos;s also called when summary widgets are re-ordered, in the &lt;code&gt;updateOrder()&lt;/code&gt; function of &lt;code&gt;WidgetController&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.updateOrder = function(event, ui) {
    ...
    Preferences.saveWidgetOrder(type, {items: items});
    if (type === &apos;summary&apos;) {
        colorRows();
    }
};
&lt;/pre&gt;
&lt;p&gt;I&apos;d like to figure out how to make this more Angular-esque, but all the &quot;how to hook into window.resize&quot; articles I found make it seem harder than this.&lt;/p&gt;
&lt;h3 id=&quot;show-more&quot;&gt;Show More&lt;/h3&gt;
&lt;p&gt;The last feature I had to implement was the &quot;Show More&quot; bar that appears when widgets are hidden. This was the most difficult thing to implement and I tried many different things before arriving at a solution that works. First of all, the widgets bars that can be expanded are put into their original (collapsed) state using &lt;code&gt;max-height&lt;/code&gt; and &lt;code&gt;overflow: hidden&lt;/code&gt;. From there, I look at the list inside the bar and compare the height&apos;s of the two elements. If the list is taller than the bar, the Show More bar is added.&lt;/p&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
I originally looked at the list&apos;s &lt;code&gt;:last-child&lt;/code&gt; to see if it was visible, but jQuery&apos;s &lt;a href=&quot;http://api.jquery.com/hidden-selector/&quot;&gt;:hidden selector&lt;/a&gt; only works on items that are hidden by &lt;code&gt;display: none&lt;/code&gt; rather than ones that are hidden by &lt;code&gt;overflow&lt;/code&gt;.
&lt;/div&gt;
&lt;p&gt;As you can see from the code below, there&apos;s special logic needed to expand the min-height of the Summary Bar since it doesn&apos;t have enough room at the bottom to add the bar in its collapsed state.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function showMore(element) {
    var bar = element.parent().parent();
    var list = element.parent();
    var barId = bar.attr(&apos;id&apos;);
    var listHeight = list.height();
    var barHeight = bar.height();
    var isSummaryBar = (barId.indexOf(&apos;summary&apos;) &amp;gt; -1);
    var summaryBarMinHeight = 260;
    var showMoreShouldBeVisible = (isSummaryBar &amp;amp;&amp;amp; element.position().top &amp;gt;= 200) ? true : listHeight &amp;gt; barHeight;
    if (showMoreShouldBeVisible) {
        var messages = {};
        // the variables below are defined in the host page, before this file is loaded
        messages.more = showMoreText;
        messages.less = showLessText;

        var showMore = $(&apos;&amp;lt;div class=&quot;show-more&quot;/&amp;gt;&apos;).html(messages.more + &quot; &amp;lt;b class=&apos;caret&apos;&amp;gt;&amp;lt;/b&amp;gt;&quot;);
        showMore.appendTo(bar);
        // summary bar doesn&apos;t have enough room for the Show More bar in its collapsed state,
        // so change it from 242 to 260
        if (isSummaryBar) {
            bar.css({&apos;min-height&apos;: summaryBarMinHeight + &apos;px&apos;, &apos;max-height&apos;: &apos;&apos;});
        }

        showMore.bind(&apos;click&apos;, function (e) {
            var element = $(this);
            var parent = element.parent();

            if (element.hasClass(&apos;less&apos;)) {
                parent.css({&quot;max-height&quot;: &apos;&apos;});
                if (isSummaryBar) {
                    parent.css({&quot;min-height&quot;: summaryBarMinHeight + &apos;px&apos;}).animate(200);
                } else {
                    parent.css({&quot;min-height&quot;: &apos;&apos;}).animate(200);
                }
                element.removeClass(&apos;less&apos;);
                element.html(messages.more + &apos; &amp;lt;b class=&quot;caret&quot;&amp;gt;&amp;lt;/b&amp;gt;&apos;);
            } else {
                parent.css({
                    &quot;max-height&quot;: 9999,
                    &quot;min-height&quot;: &apos;auto&apos;
                }).animate({
                        &quot;min-height&quot;: parent.height() + 19
                    }, 200);

                element.addClass(&apos;less&apos;);
                element.html(messages.less + &apos; &amp;lt;b class=&quot;caret caret-up&quot;&amp;gt;&amp;lt;/b&amp;gt;&apos;);
            }

            // prevent jump-down
            return false;
        });
    } else {
        // Remove show-more in case it was previously added
        if (bar.find(&apos;.show-more&apos;).length &amp;gt; 0) {
            if (isSummaryBar) {
                bar.css(&apos;min-height&apos;, summaryBarMinHeight - 18)
            } else {
                bar.attr(&apos;style&apos;, &apos;&apos;);
            }

            bar.find(&apos;.show-more&apos;).remove();
        }
    }
}

function showMoreOnResize() {
    var dataItems = $(&apos;.task-items,.summary-items,.report-items&apos;);
    dataItems.each(function() {
        var lastItem = $(this).find(&apos;li:last-child&apos;);
        if (lastItem.length &amp;gt; 0) {
            showMore(lastItem);
        }
    });
}
&lt;/pre&gt;
&lt;p&gt;At first, I wrote this logic as a directive, but when I needed it for responsiveness, I moved it into &lt;em&gt;dashboard.js&lt;/em&gt;. The &lt;code&gt;showMoreOnResize()&lt;/code&gt; function is called on window resize.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(window).resize(showMoreOnResize);
&lt;/pre&gt;
&lt;p&gt;I also found that I had to add it to the Preferences service after widgets were saved (since the number displayed could change).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
factory(&apos;Preferences&apos;, function ($filter) {
    return {
        ...
        // Save hidden and visible (and order) widgets from config dialog
        saveWidgetPreferences: function (type, widgets) {
            ...
            DWRFacade.saveDashboardWidgetPreference(type, preferences, {
                callback: function() {
                    // recalculate show more bar
                    showMoreOnResize();
                },
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        }
    }
});
&lt;/pre&gt;
&lt;p&gt;To implement the .caret-up (the .caret class is from Bootstrap), I found &lt;a href=&quot;https://github.com/twitter/bootstrap/issues/2902&quot;&gt;a caret-right howto&lt;/a&gt; and used it to create &lt;code&gt;.caret-up&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.caret-up {
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid transparent;
    border-bottom: 4px solid black;
}
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;
The final My Dashboard feature is something that I&apos;m quite proud of. A fellow developer, Vlad, did an excellent job of implementing the backend and admin portions. The Product Team&apos;s vision and desire to make it &lt;em&gt;Pop!&lt;/em&gt; created something great. The fact that we didn&apos;t have to support IE8 helped a lot in the implementation. Below is a screenshot of how My Dashboard looked when we completed the project.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904354347/&quot; href=&quot;http://farm8.staticflickr.com/7380/8904354347_0b38a0bf2d_c.jpg&quot; title=&quot;My Dashboard&quot; rel=&quot;lightbox[makingitpop]&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7380/8904354347_0b38a0bf2d.jpg&quot; width=&quot;500&quot; height=&quot;338&quot; alt=&quot;My Dashboard&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Angular isn&apos;t mentioned much in this article. That&apos;s because we didn&apos;t have to do much to the existing Angular code to implement the new design. It was just a matter of writing/modifying some CSS as well as introducing some JavaScript for colored rows and show more. If you know how these features could be written in a more Angular Way, I&apos;d love to hear about it.&lt;/p&gt;
&lt;p&gt;If you&apos;d still like to learn more about Angular and why it&apos;s good to integrate it little by little, I encourage you to read &lt;a href=&quot;http://oscarvillarreal.com/2013/05/07/5-reasons-to-use-angularjs-in-the-corporate-app-world/&quot;&gt;5 reasons to use AngularJS in the corporate app world&lt;/a&gt;.&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii</id>
        <title type="html">Developing with AngularJS - Part III: Services</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii"/>
        <published>2013-06-25T07:03:26-06:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="rest" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="taleo" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="dwr" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;This is the 3rd article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I used AngularJS for several months to create a &quot;My Dashboard&quot; feature for a client and learned a whole bunch of Angular goodness along the way. For previous articles, please see &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt; and &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Angular offers several ways to interact with data from the server. The easiest way is to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngResource.$resource&quot;&gt;$resource factory&lt;/a&gt;, which lets you interact with &lt;a href=&quot;http://en.wikipedia.org/wiki/Representational_State_Transfer&quot;&gt;RESTful&lt;/a&gt; server-side data sources. When we started the My Dashboard project, we were hoping to interact with a REST API, but soon found out that it didn&apos;t have all the data we needed. Rather than loading the page and then making another request to get its data, we decided to embed the JSON in the page. For communication back to the server, we used our tried-and-true Ajax solution: &lt;a href=&quot;http://directwebremoting.org/&quot;&gt;DWR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Angular-speak, &lt;em&gt;services&lt;/em&gt; are &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.understanding_services&quot;&gt;singletons that carry out specific tasks common to web apps&lt;/a&gt;. In other words, they&apos;re any &lt;em&gt;$name&lt;/em&gt; object that can be injected into a controller or directive. However, as a Java Developer, I tend to think of services as objects that communicate with the server. Angular&apos;s documentation on &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.creating_services&quot;&gt;Creating Services&lt;/a&gt; shows you various options for registering services. I used the angular.Module api method.&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;
&lt;a href=&quot;http://angularjs.org/&quot; title=&quot;AngularJS&quot;&gt;&lt;img src=&quot;//farm8.staticflickr.com/7445/9137074888_9d3bb13d32_s.jpg&quot; width=&quot;75&quot; height=&quot;75&quot; alt=&quot;AngularJS&quot; class=&quot;picture&quot;&gt;&lt;/a&gt;
This is the 3rd article in a series on my experience developing with &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt;. I used AngularJS for several months to create a &quot;My Dashboard&quot; feature for a client and learned a whole bunch of Angular goodness along the way. For previous articles, please see &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;Part I: The Basics&lt;/a&gt; and &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;Part II: Dialogs and Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Angular offers several ways to interact with data from the server. The easiest way is to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngResource.$resource&quot;&gt;$resource factory&lt;/a&gt;, which lets you interact with &lt;a href=&quot;http://en.wikipedia.org/wiki/Representational_State_Transfer&quot;&gt;RESTful&lt;/a&gt; server-side data sources. When we started the My Dashboard project, we were hoping to interact with a REST API, but soon found out that it didn&apos;t have all the data we needed. Rather than loading the page and then making another request to get its data, we decided to embed the JSON in the page. For communication back to the server, we used our tried-and-true Ajax solution: &lt;a href=&quot;http://directwebremoting.org/&quot;&gt;DWR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Angular-speak, &lt;em&gt;services&lt;/em&gt; are &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.understanding_services&quot;&gt;singletons that carry out specific tasks common to web apps&lt;/a&gt;. In other words, they&apos;re any &lt;em&gt;$name&lt;/em&gt; object that can be injected into a controller or directive. However, as a Java Developer, I tend to think of services as objects that communicate with the server. Angular&apos;s documentation on &lt;a href=&quot;http://docs.angularjs.org/guide/dev_guide.services.creating_services&quot;&gt;Creating Services&lt;/a&gt; shows you various options for registering services. I used the angular.Module api method.&lt;/p&gt;

&lt;p&gt;When I last worked on the project, there were only two services in My Dashboard: Widget and Preferences.&lt;/p&gt;

&lt;h3 id=&quot;widget&quot;&gt;Widget Service&lt;/h3&gt;
&lt;p&gt;The Widget service is used to retrieve the visible widgets for the user. It has two functions that are exposed to controllers: &lt;code&gt;getUserWidgets(type)&lt;/code&gt; and &lt;code&gt;getHiddenWidgets(type)&lt;/code&gt;. The former function is used at the top of &lt;code&gt;WidgetController&lt;/code&gt;, while the latter is used for the configuration dialog mentioned in the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code for this service is in &lt;em&gt;services.js&lt;/em&gt;. The bulk of the logic is in its &lt;code&gt;filterData()&lt;/code&gt; function, where it goes through a 4-step process:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Get all the widgets by type, ensuring they&apos;re unique.&lt;/li&gt;&lt;li&gt;Remove the widgets that are &lt;em&gt;hidden&lt;/em&gt; by the user&apos;s preferences.&lt;/li&gt;&lt;li&gt;Build an array that&apos;s &lt;em&gt;ordered&lt;/em&gt; by user&apos;s preferences.&lt;/li&gt;&lt;li&gt;Add any new widgets that aren&apos;t &lt;em&gt;hidden&lt;/em&gt; or &lt;em&gt;ordered&lt;/em&gt;.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;The code for the Widget object is as follows:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;dashboard.services&apos;, &amp;#91;&amp;#93;).
    factory(&apos;Widget&apos;,function ($filter, Preferences) {
        var filter = $filter(&apos;filter&apos;);
        var unique = $filter(&apos;unique&apos;);

        function filterData(array, query) {
            // get all possible widgets for a particular type
            var data = filter(array, query);
            data = unique(data);

            // remove widgets that are hidden by users preference
            var hidden = Preferences.getHiddenWidgets(query.type);
            for (var i = 0; i &amp;lt; hidden.length; i++) {
                var w = filter(data, {id: hidden&amp;#91;i&amp;#93;});
                $.each(w, function (index, item) {
                    var itemId = item.id;
                    if (hidden.indexOf(itemId) &amp;gt; -1) {
                        data.splice(data.indexOf(item), 1);
                    }
                });
            }

            // build an array that&apos;s ordered by users preference
            var ordered = &amp;#91;&amp;#93;;
            var visible = Preferences.getUserWidgets(query.type);
            for (var j = 0; j &amp;lt; visible.length; j++) {
                var v = filter(data, {id: visible&amp;#91;j&amp;#93;});
                $.each(v, function (index, item) {
                    var itemId = item.id;
                    if (visible.indexOf(itemId) &amp;gt; -1) {
                        ordered.push(item)
                    }
                });
            }

            // loop through data again and add any new widgets not in ordered
            $.each(data, function (index, item) {
                if (ordered.indexOf(item) === -1) {
                    ordered.push(item);
                }
            });

            return ordered;
        }

        return {
            getUserWidgets: function (type) {
                return filterData(widgetData, {type: type})
            },

            getHiddenWidgets: function (type) {
                var hidden = Preferences.getHiddenWidgets(type);
                var widgetsForType = filter(widgetData, {type: type});
                widgetsForType = unique(widgetsForType);
                var widgets = &amp;#91;&amp;#93;;
                for (var j = 0; j &amp;lt; hidden.length; j++) {
                    var v = filter(widgetsForType, {id: hidden&amp;#91;j&amp;#93;});
                    $.each(v, function (index, item) {
                        if (widgetsForType.indexOf(item) &amp;gt; -1) {
                            widgets.push(item)
                        }
                    });
                }
                return widgets;
            }
        }
    })
&lt;/pre&gt;
&lt;p&gt;Once you have a service configured like this, you can inject it by name. For example, &lt;code&gt;WidgetController&lt;/code&gt; has &lt;code&gt;Widget&lt;/code&gt; injected into its constructor:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function WidgetController($dialog, $scope, Widget, Preferences) {
&lt;/pre&gt;
&lt;h3 id=&quot;preferences&quot;&gt;Preferences Service&lt;/h3&gt;
&lt;p&gt;The Preferences service is used to get and save user preferences. It&apos;s pretty straightforward and the bulk of its code is interacting with DWR. This service has 5 methods:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;getHiddenWidgets(type) - used by Widget service&lt;/li&gt;&lt;li&gt;getUserWidgets(type) - used by Widget service&lt;/li&gt;&lt;li&gt;saveBarOrder(bars) - called from WidgetController&lt;/li&gt;&lt;li&gt;saveWidgetOrder(type, widgets) - called from WidgetController&lt;/li&gt;&lt;li&gt;saveWidgetPreferences(type, widgets) - called from WidgetController&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;First, let&apos;s take a look at the &lt;code&gt;save*Order()&lt;/code&gt; functions. There are two parts of the page that use the &lt;em&gt;ui-sortable&lt;/em&gt; directive to initialize drag-and-drop functionality. The first is on the main &amp;lt;ul&amp;gt; that holds the 3 bars on the left.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;ul class=&quot;widgets&quot; ui-sortable=&quot;{handle:&apos;.heading&apos;, update: updateBars}&quot;&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &quot;update&quot; property in the configuration JSON indicates which method to call in the controller. Similarly, the tasks and summary items call an &lt;code&gt;updateOrder&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;ul class=&quot;summary-items&quot; ng-model=&quot;summaryWidgets&quot; ui-sortable=&quot;{update: updateOrder}&quot;&amp;gt;
...
&amp;lt;ul class=&quot;task-items&quot; ng-model=&quot;taskWidgets&quot; ui-sortable=&quot;{update: updateOrder}&quot;&amp;gt;
&lt;/pre&gt;
&lt;p&gt;These functions are in &lt;code&gt;WidgetController&lt;/code&gt; and build an array of widget ids to pass to the Preferences service.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.updateBars = function(event, ui) {
    var bars = &amp;#91;&amp;#93;;
    $.each($(ui.item).parent().children(), function (index, item) {
        bars.push(item.id.substring(0, item.id.indexOf(&apos;-&apos;)))
    });
    Preferences.saveBarOrder(bars);
};

$scope.updateOrder = function(event, ui) {
    var parentId = $(ui.item).parent().parent().attr(&apos;id&apos;);
    var type = parentId.substring(0, parentId.indexOf(&apos;-&apos;));
    var items = &amp;#91;&amp;#93;;
    $.each($(ui.item).parent().children(), function (index, item) {
        items.push(item.id.substring(item.id.indexOf(&apos;-&apos;) + 1))
    });
    Preferences.saveWidgetOrder(type, {items: items});
};
&lt;/pre&gt;
&lt;p&gt;The bar order is used when the page is loaded. The following scriptlet code exists at the bottom of the app&apos;s page, in its $(document).ready:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&amp;lt;%  String barOrder = user.getDashboardBarSortOrder();
    if (barOrder != null) { %&amp;gt;
    sortBars(&amp;#91;&apos;&amp;lt;%= barOrder %&amp;gt;&apos;&amp;#93;);
&amp;lt;% } %&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;sortBars()&lt;/code&gt; function is in a &lt;em&gt;dashboard.js&lt;/em&gt; file (where we put all non-Angular functions):&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function sortBars(barOrder) {
    // Sort bars according to user preferences
    $.each(barOrder, function(index, item) {
        var bar = $(&apos;#&apos; + item + &apos;-bar&apos;);
        if (bar.index() !== index) {
            if (index === 0) {
                bar.insertBefore($(&apos;.widgets&amp;gt;li:first-child&apos;));
            } else if (index === (barOrder.length - 1)) {
                bar.insertAfter($(&apos;.widgets&amp;gt;li:last-child&apos;));
            } else {
                bar.insertBefore($(&apos;.widgets&amp;gt;li:eq(&apos; + index + &apos;)&apos;));
            }
        }
    });
}
&lt;/pre&gt;
&lt;p&gt;Now that you&apos;ve seen where Preferences is called from, let&apos;s take a look at the code for the service.&lt;/p&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
The checks for undefined and uniqueness in the code below shouldn&apos;t be necessary, but I prefer defensive coding.
&lt;/div&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
factory(&apos;Preferences&apos;, function ($filter) {
    var unique = $filter(&apos;unique&apos;);

    return {
        // Get in-page variable: hiddenWidgets
        getHiddenWidgets: function (type) {
            var items = hiddenWidgets&amp;#91;type&amp;#93;;
            return (angular.isUndefined(items) ? &amp;#91;&amp;#93; : unique(items));
        },

        // Get in-page variable: userWidgets
        getUserWidgets: function (type) {
            var items = userWidgets&amp;#91;type&amp;#93;;
            return (angular.isUndefined(items) ? &amp;#91;&amp;#93; : unique(items));
        },

        // Save main bar (task, summary, chart) order
        saveBarOrder: function (bars) {
            DWRFacade.saveDashboardBarSortOrder(bars, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            })
        },

        // Save order of widgets from sortable
        saveWidgetOrder: function (type, widgets) {
            userWidgets&amp;#91;type&amp;#93; = widgets.items;
            DWRFacade.saveDashboardWidgetPreference(type, widgets, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        },

        // Save hidden and visible (and order) widgets from config dialog
        saveWidgetPreferences: function (type, widgets) {
            // widgets is a map of hidden and visible
            var hiddenIds = &amp;#91;&amp;#93;;
            $.each(widgets.hidden, function (index, item) {
                hiddenIds.push(item.id);
            });
            var visibleIds = &amp;#91;&amp;#93;;
            $.each(widgets.items, function (index, item) {
                visibleIds.push(item.id);
            });
            var preferences = {
                hidden: hiddenIds,
                items: visibleIds
            };
            // reset local variables in page
            hiddenWidgets&amp;#91;type&amp;#93; = hiddenIds;
            userWidgets&amp;#91;type&amp;#93; = visibleIds;
            DWRFacade.saveDashboardWidgetPreference(type, preferences, {
                errorHandler: function (errorString) {
                    alert(errorString);
                }
            });
        }
    }
})
&lt;/pre&gt;
&lt;h3 id=&quot;http&quot;&gt;Using $http and Receiving Data&lt;/h3&gt;
&lt;p&gt;In this particular application, we didn&apos;t do any reading from the server with Angular. We simply wrote preferences to the server, and updated embedded variables when data changed. Real-time functionality of the app wouldn&apos;t be noticeable if a write failed. 
&lt;/p&gt;
&lt;p&gt;
In my current Angular project, it&apos;s more of a full-blown application that does as much reading as writing. For this, I&apos;ve found it useful to either 1) pass in callbacks to services or 2) use Angular&apos;s event system to publish/subscribe to events.
&lt;/p&gt;
&lt;p&gt;The first method is the easiest, and likely the most familiar to JavaScript developers. For example, here&apos;s the controller code to remove a profile picture:
&lt;/p&gt;

&lt;pre class=&quot;brush: js&quot;&gt;
Profile.removePhoto($scope.user, function (data) {
    // close the dialog
    $scope.close(&apos;avatar&apos;);
    // success message using toastr: http://bit.ly/14Uisgm
    Flash.pop({type: &apos;success&apos;, body: &apos;Your profile picture was removed.&apos;});
})
&lt;/pre&gt;
&lt;p&gt;And the &lt;code&gt;Profile.removePhoto()&lt;/code&gt; method:
&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
removePhoto: function (user, callback) {
    $http.post(&apos;/profile/removePhoto&apos;, user).success(function (response) {
        return callback(response);
    });
}
&lt;/pre&gt;
&lt;p&gt;The second, event-driven method works equally as well, but can easily suffer from typos in event names.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
// controller calling code
Profile.getUser();

// service code
getUser: function () {
    $http.get(&apos;/profile&apos;).success(function (data) {
        if (data.username) {
            $log.info(&apos;Profile for &apos; + data.username + &apos; retrieved!&apos;);
            $rootScope.$broadcast(&apos;event:profile&apos;, data);
        }
    });
}

// controller receiving code
$rootScope.$on(&apos;event:profile&apos;, function (event, data) {
    $scope.user = data;
});
&lt;/pre&gt;
&lt;p&gt;I like both methods, but the event-driven one seems like it could offer more extensibility in the future.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Using in-page variables and DWR doesn&apos;t seem to be recommended by the Angular Team. However, it worked well for us and seems like a good way to construct Angular services. Even if a REST API becomes available to get all the data, I think using in-page variables to minimize requests is a good idea. &lt;/p&gt;
&lt;p&gt;When retrieving data, you can use callbacks or Angular&apos;s pub/sub event system ($broadcast and $on) to get data in your controllers. If you want to learn more about this technique, see Eric Terpstra&apos;s &lt;a href=&quot;http://ericterpstra.com/2012/09/angular-cats-part-3-communicating-with-broadcast/&quot;&gt;Communicating with $broadcast&lt;/a&gt;. In his article, Eric mentions &lt;a href=&quot;https://groups.google.com/d/msg/angular/M0SHItdgBqg/R1t_17cR0pYJ&quot;&gt;Thomas Burleson&apos;s pub/sub module&lt;/a&gt; that acts as a message queue. If you&apos;ve used Thomas&apos;s MessageQueue (or something similar) with Angular, I&apos;d love to hear about your experience. 
&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iv&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how we redesigned My Dashboard and used CSS3 and JavaScript to implement new ideas.&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii</id>
        <title type="html">Developing with AngularJS - Part II: Dialogs and Data</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii"/>
        <published>2013-06-20T08:45:13-06:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="angularui" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jqueryui" scheme="http://roller.apache.org/ns/tags/" />
        <category term="jquery" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="taleo" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;A couple of days ago, I wrote an article on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;how I started developing with AngularJS&lt;/a&gt;. I used AngularJS for several months to develop a &quot;My Dashboard&quot; feature for a client&apos;s product and learned a whole bunch of stuff along the way. &lt;/p&gt;
&lt;p&gt;This article provides an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. After finishing the prototype work in January, we started moving bits and pieces into the main application. We kept the same file names for our Angular-related files and copied them into the project.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.flickr.com/photos/mraible/8904352343/&quot; title=&quot;Directory Structure&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2856/8904352343_20beb87da8_o.png&quot; width=&quot;280&quot; height=&quot;268&quot; alt=&quot;Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;All these files are packaged up into a &lt;code&gt;dashboard.js&lt;/code&gt; file that&apos;s included at the bottom of our Dashboard page. While our prototype used jQuery 1.9 and jQuery UI 1.10, the application&apos;s codebase used jQuery 1.7.1 and jQuery UI 1.8.3. Luckily, this didn&apos;t present a problem as everything continued to work as expected.&lt;/p&gt;
&lt;p&gt;Around this time, we also had many discussions with the Product Team about charts. Since Highcharts required we purchase a license, we took at look at &lt;a href=&quot;http://www.anychart.com/&quot;&gt;AnyChart&lt;/a&gt;, which we were already using. We were able to get AnyChart to work with our existing &lt;em&gt;chart&lt;/em&gt; directive with minimal changes. Most changes were in the JSON itself. &lt;/p&gt;
&lt;p&gt;We committed the first pass (with sample data still hard-coded) in mid-February.&lt;/p&gt;</summary>
        <content type="html">&lt;p&gt;A couple of days ago, I wrote an article on &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_i&quot;&gt;how I started developing with AngularJS&lt;/a&gt;. I used AngularJS for several months to develop a &quot;My Dashboard&quot; feature for a client&apos;s product and learned a whole bunch of stuff along the way. &lt;/p&gt;
&lt;p&gt;This article provides an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. After finishing the prototype work in January, we started moving bits and pieces into the main application. We kept the same file names for our Angular-related files and copied them into the project.
&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.flickr.com/photos/mraible/8904352343/&quot; title=&quot;Directory Structure&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2856/8904352343_20beb87da8_o.png&quot; width=&quot;280&quot; height=&quot;268&quot; alt=&quot;Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;All these files are packaged up into a &lt;code&gt;dashboard.js&lt;/code&gt; file that&apos;s included at the bottom of our Dashboard page. While our prototype used jQuery 1.9 and jQuery UI 1.10, the application&apos;s codebase used jQuery 1.7.1 and jQuery UI 1.8.3. Luckily, this didn&apos;t present a problem as everything continued to work as expected.&lt;/p&gt;
&lt;p&gt;Around this time, we also had many discussions with the Product Team about charts. Since Highcharts required we purchase a license, we took at look at &lt;a href=&quot;http://www.anychart.com/&quot;&gt;AnyChart&lt;/a&gt;, which we were already using. We were able to get AnyChart to work with our existing &lt;em&gt;chart&lt;/em&gt; directive with minimal changes. Most changes were in the JSON itself. &lt;/p&gt;
&lt;p&gt;We committed the first pass (with sample data still hard-coded) in mid-February.&lt;/p&gt;
&lt;h3 id=&quot;angular-ui-carousel&quot;&gt;Angular UI&apos;s Carousel&lt;/h3&gt;
&lt;p&gt;While finishing our initial prototype, I learned about Angular UI&apos;s &lt;a href=&quot;https://github.com/angular-ui/bootstrap/tree/master/src/carousel&quot;&gt;Carousel&lt;/a&gt;, an implementation of Bootstrap&apos;s Carousel. It required a 70% less HTML and is quite a bit easier to read. Below is the refactored carousel section.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;carousel interval=&quot;-1&quot; class=&quot;oneup&quot;&amp;gt;
    &amp;lt;slide ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot; active=&quot;slide.active&quot; class=&quot;chart&quot;&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
    &amp;lt;/slide&amp;gt;
&amp;lt;/carousel&amp;gt;
&amp;lt;carousel interval=&quot;-1&quot; class=&quot;twoup&quot;&amp;gt;
    &amp;lt;slide ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot; active=&quot;slide.active&quot; class=&quot;chart&quot;&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;0&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;0&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
        &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;1&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;1&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
    &amp;lt;/slide&amp;gt;
&amp;lt;/carousel&amp;gt;
&lt;/pre&gt;
&lt;p&gt;I was also able to remove the JavaScript that once initialized the carousel.&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
-    var carousel = $(&apos;.carousel&apos;);
-    $(carousel).carousel({
-        interval: 0
-    });
&lt;/pre&gt;
&lt;p&gt;While integrating this directive, I found a way to improve it by &lt;a href=&quot;https://github.com/angular-ui/bootstrap/commit/aedc05654b19342d96eabc4fc08d6a090765a48b&quot;&gt;hiding navigation indicators if there&apos;s only one slide&lt;/a&gt;. And thus my first contribution to Angular UI was born.&lt;/p&gt;
&lt;h3 id=&quot;angular-ui-sortable&quot;&gt;Angular UI&apos;s Sortable&lt;/h3&gt;
&lt;p&gt;Next, I switched from using JavaScript to Angular UI&apos;s &lt;a href=&quot;https://github.com/angular-ui/ui-sortable&quot;&gt;&lt;em&gt;sortable&lt;/em&gt;&lt;/a&gt;&lt;em&gt; &lt;/em&gt;to initialize drag-and-drop functionality. This was as simple as removing 5 lines of JavaScript and adding &quot;ui-sortable&quot; as an attribute to HTML tags.&lt;/p&gt;
&lt;pre class=&quot;brush: diff&quot;&gt;
&amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
     &amp;lt;div class=&quot;row-fluid&quot;&amp;gt;
         &amp;lt;div class=&quot;span9&quot;&amp;gt;
-                &amp;lt;ul class=&quot;widgets&quot;&amp;gt;
+                &amp;lt;ul class=&quot;widgets&quot; ui-sortable=&quot;{handle:&apos;.heading&apos;}&quot;&amp;gt;
                 &amp;lt;li id=&quot;summary-bar&quot;&amp;gt;
                     &amp;lt;div class=&quot;heading&quot;&amp;gt;Summary &amp;lt;a href=&apos;#&apos; class=&apos;configure&apos;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
-                        &amp;lt;ul class=&quot;tiles&quot;&amp;gt;
+                        &amp;lt;ul class=&quot;tiles&quot; ui-sortable&amp;gt;
                         &amp;lt;li class=&quot;span3&quot; ng-repeat=&quot;widget in widgets | filter:{type: &apos;summary&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                             &amp;lt;h3&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
@@ -36,7 +36,7 @@
                 &amp;lt;/li&amp;gt;
                 &amp;lt;li id=&quot;task-bar&quot;&amp;gt;
                     &amp;lt;div class=&quot;heading&quot;&amp;gt;My Tasks &amp;lt;a href=&apos;#&apos; class=&apos;configure&apos;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
-                        &amp;lt;ul class=&quot;tasks&quot;&amp;gt;
+                        &amp;lt;ul class=&quot;tasks&quot; ui-sortable&amp;gt;
@@ -151,55 +131,44 @@
-    $(&apos;.widgets&apos;).sortable({
-        cursor: &quot;move&quot;,
-        handle: &quot;.heading&quot;
-    }).disableSelection();
-    $(&apos;.tiles,.tasks&apos;).sortable();
&lt;/pre&gt;
&lt;p&gt;This directive uses jQuery UI under the covers, so it accepts all the same arguments you&apos;d normally use.&lt;/p&gt;
&lt;h3 id=&quot;dialogs-with-jquery&quot;&gt;Dialogs with jQuery&lt;/h3&gt;
&lt;p&gt;The next feature I implemented was a dialog that allowed end users to add/remove widgets from their dashboard. Since I knew how to do this with jQuery, that&apos;s the path I started down. I figured it&apos;d be easier to get something working quickly and then refactor to Angular later. I put the HTML for the dialog at the bottom of the page:
&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;js/move.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&quot;text/javascript&quot; src=&quot;js/upDown.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;div id=&quot;configure-widgets&quot; class=&quot;hidden&quot; data-title=&quot;Bar Configuration&quot; data-close=&quot;Close&quot;&amp;gt;
    &amp;lt;div class=&quot;row-fluid center&quot;&amp;gt;
        &lt;p&gt;Decide which items you would like to display and set the display order.&lt;/p&gt;
        &amp;lt;div class=&quot;span5&quot; style=&quot;margin-left: 25px&quot;&amp;gt;
            &amp;lt;label for=&quot;available-widgets&quot; class=&quot;bold&quot;&amp;gt;Available Widgets&amp;lt;/label&amp;gt;
            &amp;lt;select size=10 id=&quot;available-widgets&quot; multiple class=&quot;width100pr&quot;&amp;gt;&amp;lt;/select&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;span1 arrows&quot; style=&quot;padding-top: 50px&quot;&amp;gt;
            &amp;lt;img src=&quot;images/arrow_up.png&quot; class=&quot;arrow arrow-up&quot; alt=&quot;Move Up&quot; onclick=&quot;return moveUp($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_right.png&quot; class=&quot;arrow arrow-right&quot; alt=&quot;Move Right&quot; onclick=&quot;return move($(&apos;#available-widgets&apos;)&amp;#91;0&amp;#93;, $(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_left.png&quot; class=&quot;arrow arrow-left&quot; alt=&quot;Move Left&quot; onclick=&quot;return move($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;, $(&apos;#available-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
            &amp;lt;img src=&quot;images/arrow_down.png&quot; class=&quot;arrow arrow-down&quot; alt=&quot;Move Down&quot; onclick=&quot;return moveDown($(&apos;#assigned-widgets&apos;)&amp;#91;0&amp;#93;);&quot;/&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;span5&quot;&amp;gt;
            &amp;lt;label for=&quot;assigned-widgets&quot; class=&quot;bold&quot;&amp;gt;Assigned Widgets&amp;lt;/label&amp;gt;
            &amp;lt;select size=10 id=&quot;assigned-widgets&quot; multiple class=&quot;width100pr&quot;&amp;gt;&amp;lt;/select&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Then I changed the &lt;code&gt;WidgetController&lt;/code&gt; to split the &lt;code&gt;widgets&lt;/code&gt; variable into variables for each widget type.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
+    var filter = $filter(&apos;filter&apos;);
+    var orderBy = $filter(&apos;orderBy&apos;);
+
+    $scope.summaryWidgets = filterData($scope.widgets, {type: &apos;summary&apos;}, filter, orderBy);
+    $scope.taskWidgets = filterData($scope.widgets, {type: &apos;task&apos;}, filter, orderBy);
+    $scope.chartWidgets = filterData($scope.widgets, {type: &apos;chart&apos;}, filter, orderBy);
+}
+
+function filterData(array, query, filter, orderBy) {
+    var data = filter(array, query);
+    return orderBy(data, &apos;order&apos;);
+}
&lt;/pre&gt;
&lt;p&gt;Finally, I added a new &lt;em&gt;config&lt;/em&gt; directive and hooked it into the page by adding &lt;em&gt;config=&quot;type&quot; &lt;/em&gt;to each heading (e.g. &lt;code&gt;&amp;lt;div class=&quot;heading&quot; config=&quot;task&quot;&amp;gt;My Tasks&amp;lt;/div&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;

.directive(&apos;config&apos;, function ($sanitize, $filter) {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            var configBtn = $(&apos;&amp;lt;a href=&quot;#&quot;/&amp;gt;&apos;).addClass(&apos;configure&apos;);
            configBtn.appendTo(element);
            var widgets = scope&amp;#91;attrs.config + &apos;Widgets&apos;&amp;#93;;
             
            // availableWidgets is defined as a global variable embedded in the page
            var allWidgets = availableWidgets; 
            $(configBtn).on(&apos;click&apos;, function (e) {
                e.preventDefault();
                var configDialog = $(&apos;#configure-widgets&apos;);
                var availableWidgets = $(&apos;#available-widgets&apos;);
                availableWidgets.empty();
                var filter = $filter(&apos;filter&apos;);
                var orderBy = $filter(&apos;orderBy&apos;);
                allWidgets = filter(allWidgets, {type: attrs.config});
                allWidgets = orderBy(allWidgets, &apos;order&apos;);
                var unselectedWidgets = jQuery.grep(allWidgets, function(item) {
                    return jQuery.inArray(item, widgets) &amp;lt; 0;
                });
                $.each(unselectedWidgets, function(index, item) {
                    var title = (item.title) ? item.title.replace(/&amp;amp;quot;/g, &apos;&quot;&apos;) : &apos;No Title&apos;;
                    availableWidgets.append(new Option(title, item.id));
                });
                var assignedWidgets = $(&apos;#assigned-widgets&apos;);
                assignedWidgets.empty();
                $.each(widgets, function(index, item) {
                    var title = (item.title) ? item.title.replace(/&amp;amp;quot;/g, &apos;&quot;&apos;) : &apos;No Title&apos;;
                    assignedWidgets.append(new Option(title, item.id));
                });
                configDialog.dialog({
                    title: $(element).text() + &apos; &apos; + configDialog.attr(&apos;data-title&apos;),
                    width: 600,
                    modal: true,
                    buttons: &amp;#91;{
                            text: configDialog.attr(&apos;data-close&apos;),
                            &apos;class&apos;: &apos;btn&apos;,
                            click: function() {
                                $(this).dialog(&quot;close&quot;);
                            }
                        }&amp;#93;
                });
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;As you can tell, this is quite a bit of code, and it doesn&apos;t even show you the JavaScript in move.js and upDown.js (included at the top of the dialog HTML). While writing this code, I could tell that I was not doing things the &lt;em&gt;Angular Way&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To refactor, I did some research, found the &lt;a href=&quot;https://github.com/angular-ui/bootstrap/tree/master/src/dialog&quot;&gt;$dialogProvider&lt;/a&gt; service and went to work.&lt;/p&gt;
&lt;h3 id=&quot;dialogs-with-angular&quot;&gt;Dialogs with Angular&lt;/h3&gt;
&lt;p&gt;I started by refactoring the &lt;em&gt;config&lt;/em&gt; directive to be much shorter.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;config&apos;, function() {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            var configBtn = $(&apos;&amp;lt;a href=&quot;&quot;/&amp;gt;&apos;).addClass(&apos;configure&apos;);
            configBtn.appendTo(element);
            configBtn.bind(&apos;click&apos;, function(e) {
                e.preventDefault();
                e.stopPropagation();
                scope.$apply(scope.configureDialog(attrs.config, $(element).text()))
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;Line #10 that starts with &lt;code&gt;scope.$apply&lt;/code&gt; is what makes the magic happens.This calls the &lt;code&gt;configureDialog()&lt;/code&gt; function in &lt;code&gt;WidgetController&lt;/code&gt;. The &lt;code&gt;$dialog&lt;/code&gt; service you see below is injected by adding it as a parameter to the &lt;code&gt;WidgetController&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;

$scope.configureDialog = function (type, title) {
    var dialog = $dialog.dialog({
        dialogClass: &apos;modal draggable&apos;,
        modalFade: false,
        backdropClick: true,
        controller: &apos;ConfigController&apos;,
        template: $(&apos;#configure-widgets&apos;).html(),
        resolve: {
            title: function() {
                return title;
            },
            hiddenItems: function () {
                return Widget.getHiddenWidgets(type);
            },
            items: function () {
                return angular.copy($scope&amp;#91;type + &apos;Widgets&apos;&amp;#93;);
            }
        }
    });
    dialog.open().then(function(results) {
        if (!angular.isUndefined(results)) {
            $scope&amp;#91;type + &apos;Widgets&apos;&amp;#93; = results.items;
            Preferences.saveWidgetPreferences(type, results);
        }
    });
};
&lt;/pre&gt;
&lt;div class=&quot;alert alert-info&quot;&gt;
&lt;p style=&quot;margin-top: 0&quot;&gt;&lt;strong&gt;draggable directive&lt;/strong&gt;&lt;br/&gt;
If you look closely, you&apos;ll see this dialog is initialized with a &apos;draggable&apos; class. I created a &lt;em&gt;draggable &lt;/em&gt;directive that gives draggability using jQuery UI to any elements with this class.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;draggable&apos;, function() {
    return {
        restrict: &apos;C&apos;,
        link: function(scope, elem, attr, ctrl) {
            elem.draggable({handle: &apos;.modal-header&apos;});
        }
    };
});
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The interesting parts of this file are the &lt;strong&gt;controller&lt;/strong&gt;, &lt;strong&gt;template&lt;/strong&gt;, and &lt;strong&gt;resolve&lt;/strong&gt; parameters. The &lt;strong&gt;controller&lt;/strong&gt; is just another function in &lt;em&gt;controllers.js&lt;/em&gt;, the &lt;strong&gt;template&lt;/strong&gt; is HTML in the page (so text could be i18n-ized) and &lt;strong&gt;resolve&lt;/strong&gt; has the variables to pass to the controller. &lt;code&gt;Widget&lt;/code&gt; and &lt;code&gt;Preferences&lt;/code&gt; are services I&apos;ll talk about in the Part 3. The reason angular.copy() is used is so the widget arrays aren&apos;t modified while the dialog is displayed (we want to wait until the user clicks &quot;Save&quot;).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ConfigController&lt;/code&gt; function started quite simply:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
function ConfigController($scope, $sanitize, dialog, title, hiddenItems, items) {
    $scope.title = title;
    $scope.hiddenItems = hiddenItems;
    $scope.items = items;
}
&lt;/pre&gt;
&lt;p&gt;The HTML (defined by the aforementioned &lt;strong&gt;template&lt;/strong&gt; parameter) is as follows. You can see that &lt;em&gt;title&lt;/em&gt;, &lt;em&gt;hiddenItems&lt;/em&gt; and &lt;em&gt;items&lt;/em&gt; are the variables defined in $scope and used to render the data.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div id=&quot;configure-widgets&quot; class=&quot;modal hidden&quot;&amp;gt;
    &amp;lt;div class=&quot;modal-header&quot; style=&quot;border: 0&quot;&amp;gt;
        &amp;lt;button type=&quot;button&quot; class=&quot;close&quot; ng-click=&quot;close(false)&quot; aria-hidden=&quot;true&quot; style=&quot;margin-top: -3px&quot;&amp;gt;&amp;amp;times;&amp;lt;/button&amp;gt;
        &amp;lt;h4&amp;gt;{{title}} Bar Configuration&amp;lt;/h4&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;modal-body&quot;&amp;gt;
        &lt;p&gt;Decide which items you would like to display and set the display order.&lt;/p&gt;
        &amp;lt;div class=&quot;row-fluid center&quot;&amp;gt;
            &amp;lt;div class=&quot;span4&quot; style=&quot;width: 320px; margin-left: 25px&quot;&amp;gt;
                &amp;lt;label for=&quot;available-widgets&quot; class=&quot;bold&quot;&amp;gt;Available Widgets&amp;lt;/label&amp;gt;
                &amp;lt;select size=10 id=&quot;available-widgets&quot; ng-model=&quot;items.available&quot; ng-options=&quot;i.title for i in hiddenItems&quot;
                        class=&quot;width100pr&quot; multiple=&quot;multiple&quot;&amp;gt;&amp;lt;/select&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span1 arrows&quot; style=&quot;padding-top: 50px&quot;&amp;gt;
                &amp;lt;img src=&quot;images/arrow_up.png&quot; class=&quot;arrow arrow-up&quot; alt=&quot;Move Up&quot; ng-click=&quot;moveUp(items.selected, items)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_right.png&quot; class=&quot;arrow arrow-right&quot; alt=&quot;Move Right&quot; ng-click=&quot;moveItem(items.available, hiddenItems, items)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_left.png&quot; class=&quot;arrow arrow-left&quot; alt=&quot;Move Left&quot; ng-click=&quot;moveItem(items.selected, items, hiddenItems)&quot;/&amp;gt;
                &amp;lt;img src=&quot;images/arrow_down.png&quot; class=&quot;arrow arrow-down&quot; alt=&quot;Move Down&quot; ng-click=&quot;moveDown(items.selected, items)&quot;/&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span4&quot; style=&quot;width: 320px&quot;&amp;gt;
                &amp;lt;label for=&quot;assigned-widgets&quot; class=&quot;bold&quot;&amp;gt;Assigned Widgets&amp;lt;/label&amp;gt;
                &amp;lt;select size=10 id=&quot;assigned-widgets&quot; ng-model=&quot;items.selected&quot; ng-options=&quot;i.title for i in items&quot;
                        class=&quot;width100pr&quot; multiple=&quot;multiple&quot;&amp;gt;&amp;lt;/select&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;modal-footer&quot;&amp;gt;
        &amp;lt;button ng-click=&quot;close(true)&quot; class=&quot;btn btn-primary&quot;&amp;gt;Save and Close&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;There&apos;s a couple of new directives introduced by this code: &lt;em&gt;ngOptions&lt;/em&gt;&amp;nbsp;and &lt;em&gt;ngClick&lt;/em&gt;. The former is used to display options in a &amp;lt;select&amp;gt;, the latter to call functions in the controller. These functions are defined in &lt;code&gt;ConfigController&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$scope.moveUp = function(items, list) {
    angular.forEach(items, function(item) {
        var idx = list.indexOf(item);
        if (idx != -1) {
            list.splice(idx - 1, 0, list.splice(idx, 1)[0]);
        }
    });
};

$scope.moveDown = function(items, list) {
    angular.forEach(items, function(item) {
        var idx = list.indexOf(item);
        if (idx != -1) {
            list.splice(idx + 1, 0, list.splice(idx, 1)[0]);
        }
    });
};

$scope.moveItem = function(items, from, to) {
    angular.forEach(items, function(item) {
        var idx = from.indexOf(item);
        if (idx != -1) {
            from.splice(idx, 1);
            to.push(item);
        }
    });
};

$scope.close = function(save) {
    if (save) {
        dialog.close({
            hidden: $scope.hiddenItems,
            items: $scope.items
        });
    } else {
        dialog.close();
    }
};
&lt;/pre&gt;
&lt;p&gt;As you can see, Angular allows you to easily access and manipulate the data. Its two-way binding feature is great because when you modify the object in JavaScript, it auto-updates the displayed HTML.The only thing you need to do to the HTML is to add the &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngModel&quot;&gt;&lt;em&gt;ngModel&lt;/em&gt;&lt;/a&gt; directive.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;ul class=&quot;tasks&quot; ng-model=&quot;taskWidgets&quot; ui-sortable&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The dialog&apos;s &lt;code&gt;close()&lt;/code&gt; method is called in the header (where it passes false) and in the footer (where it passes true). The &lt;code&gt;configureDialog()&lt;/code&gt; function handles saving if the user indicates they wanted to do so. The 3rd line (starts with $scope) is all that&apos;s needed to update the UI. The Preferences service is covered in the next article.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
dialog.open().then(function(results) {
    if (!angular.isUndefined(results)) {
        $scope[type + &apos;Widgets&apos;] = results.items;
        Preferences.saveWidgetPreferences(type, results);
    }
});
&lt;/pre&gt;
&lt;h3 id=&quot;modals-with-data&quot;&gt;Modals with Data&lt;/h3&gt;
&lt;p&gt;Displaying the widgets in a consolidated dashboard is a nice product feature, but we wanted to take it a step further and allow users to &quot;click through&quot; to see the data. To do this, I added an &quot;event&quot; directive that could read from our JSON data and act upon it accordingly. We decided on 2 types of events: &lt;strong&gt;function&lt;/strong&gt; and &lt;strong&gt;href&lt;/strong&gt;. The &lt;strong&gt;href&lt;/strong&gt; event type is for report widgets, because we want to allow users to click on the widget and it takes them directly to the report. For &lt;strong&gt;function&lt;/strong&gt;, we simply &lt;em&gt;eval()&lt;/em&gt; what&apos;s passed in. The function is expected to have a &lt;em&gt;container&lt;/em&gt; argument that it can use to render data in a modal window.&lt;/p&gt;
&lt;p&gt;Using the &lt;em&gt;event&lt;/em&gt; directive, you can attach this behavior to a widget simply by adding a class.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;h3 class=&quot;events&quot;&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;em&gt;event&lt;/em&gt; directive that attaches click behavior is below:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
directive(&apos;events&apos;, function () {
    // This is necessary because widgets may be chunked when charts are displayed 2-up
    function getWidget(element, widget, scope) {
        if (element.hasClass(&apos;chart-title&apos;)) {
            if (element.parent().hasClass(&apos;first&apos;)) {
                widget = scope.widget[0];
            } else if (element.parent().hasClass(&apos;second&apos;)) {
                widget = scope.widget[1];
            } else {
                widget = scope.widget;
            }
        } else {
            widget = scope.widget;
        }
        return widget;
    }
    return {
        restrict: &apos;C&apos;,
        link: function (scope, element, attrs) {
            var widget = getWidget(element, widget, scope);
            if (angular.isUndefined(widget)) {
                return;
            }
            var events = widget.events;
            if (!angular.isUndefined(events)) {
                for (var e in events) {
                    if (e === &apos;function&apos;) {
                        if ($(&apos;#dialog-frame&apos;).length === 0) {
                            $(&apos;&amp;lt;div id=&quot;dialog-frame&quot; class=&quot;modal hide&quot;/&amp;gt;&apos;).appendTo(&apos;body&apos;);
                            var header = $(&apos;&amp;lt;div class=&quot;modal-header&quot;&amp;gt;&apos;);
                            header.append($(&apos;&amp;lt;button type=&quot;button&quot; class=&quot;close&quot; data-dismiss=&quot;modal&quot;&amp;gt;&amp;times;&amp;lt;/button&amp;gt;&apos;));
                            header.append($(&apos;&amp;lt;h4/&amp;gt;&apos;).append(scope.widget.title));
                            header.appendTo(&apos;#dialog-frame&apos;);
                            $(&apos;&amp;lt;div class=&quot;modal-body&quot;/&amp;gt;&apos;).appendTo(&apos;#dialog-frame&apos;);
                        }
                        element.bind(&apos;click&apos;, function(event) {
                            event.preventDefault();
                            event.stopPropagation();
                            var dialog = $(&apos;#dialog-frame&apos;);
                            var title = widget.title;
                            dialog.find(&apos;h4&apos;).html(title);
                            var dialogBody = dialog.find(&apos;.modal-body&apos;);
                            dialogBody.empty();
 
                            // display a checking for new data message when widget&apos;s value is 0
                            var checkingMessage = $(&apos;#wait-checking&apos;).html();
                            if (scope.widget.value === &quot;0&quot;) {
                                dialogBody.html(checkingMessage);
                            // otherwise, display a loading message
                            } else {
                                dialogBody.html($(&apos;#wait-loading&apos;).html());
                            }
 
                            // center the dialog on the page
                            dialog.css({
                                width: &apos;560px&apos;,
                                &apos;margin-left&apos;: function() {
                                    return -($(this).width() / 2);
                                }
                            });
 
                            dialog.modal(&apos;show&apos;);
                            var container = dialogBody;
                            eval(events[e]);
                        });
                    } else if (e === &quot;href&quot;) {
                        element.bind(&apos;click&apos;, function() {
                            location.href = events[e];
                        })
                    } else {
                        console.log(&apos;Event type &quot;&apos; + e + &apos;&quot; not supported.&apos;);
                    }
                }
            }
        }
    }
})
&lt;/pre&gt;
&lt;h3 id=&quot;empty-widgets-message&quot;&gt;Empty Widgets Message&lt;/h3&gt;
&lt;p&gt;When there are no widgets for a particular type, we wanted to display a message telling the end user. To do this, I used the &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngHide&quot;&gt;&lt;em&gt;ngHide&lt;/em&gt;&lt;/a&gt; directive and passed in the array&apos;s length as an expression. I originally had this on a &amp;lt;li&amp;gt; in the respective widget list, but noticed it causes issues when dragging and dropping.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-hide=&quot;summaryWidgets.length&quot; class=&quot;widgets-empty&quot;&amp;gt;
    No Summary Bar Widgets currently visible
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;This article has provided an overview of how I changed some of My Dashboard&apos;s features to use Angular instead of jQuery. I hope it&apos;s helped to show how powerful directives can be and how MVC works in Angular. I particularly enjoyed learning how to use the $dialog service. As a word of warning, its usage might change in future releases since it is currently being &lt;a href=&quot;https://github.com/angular-ui/bootstrap/issues/441&quot;&gt;rewritten to be more maintainable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_iii&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how I developed Services and integrated DWR. If you see any code that can be improved upon, or issues with the code/architecture in this article, please leave a comment.&lt;/p&gt;</content>
    </entry>
    <entry>
        <id>https://raibledesigns.com/rd/entry/developing_with_angularjs_part_i</id>
        <title type="html">Developing with AngularJS - Part I: The Basics</title>
        <author><name>Matt Raible</name></author>
        <link rel="alternate" type="text/html" href="https://raibledesigns.com/rd/entry/developing_with_angularjs_part_i"/>
        <published>2013-06-18T09:06:52-06:00</published>
        <updated>2014-05-08T19:47:19-06:00</updated> 
        <category term="/The Web" label="The Web" />
        <category term="jquery" scheme="http://roller.apache.org/ns/tags/" />
        <category term="highcharts" scheme="http://roller.apache.org/ns/tags/" />
        <category term="javascript" scheme="http://roller.apache.org/ns/tags/" />
        <category term="angularjs" scheme="http://roller.apache.org/ns/tags/" />
        <category term="bootstrap" scheme="http://roller.apache.org/ns/tags/" />
        <category term="taleo" scheme="http://roller.apache.org/ns/tags/" />
        <summary type="html">&lt;p&gt;There&apos;s &lt;a href=&quot;http://hop.ie/blog/angularjs-introduction/&quot;&gt;many&lt;/a&gt;, &lt;a href=&quot;http://www.raweng.com/blog/2013/01/30/introduction-to-angularjs-part-1/&quot;&gt;many&lt;/a&gt; different &lt;a href=&quot;http://www.webdesignerdepot.com/2013/04/an-introduction-to-angularjs/&quot;&gt;introductions&lt;/a&gt; to &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; available on the internet. This article is not another introduction, but rather a story about my learning experience. It all started way back in January of this year. I was working as a UI Architecture Consultant at Taleo/Oracle, my client for the last 21 months. My gig there ended last month, but they agreed to let me publish a series of articles about the knowledge I gained.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Project Background&lt;/h3&gt;
&lt;p&gt;The Director of Product Management had been working on the concepts for a new project - codenamed &quot;Visual MyView&quot;. Below is a mockup he created for our kickoff meeting on January 4th.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352595/&quot; href=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab_c.jpg&quot; title=&quot;My Dashboard - Original Mockup&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab.jpg&quot; width=&quot;500&quot; height=&quot;296&quot; alt=&quot;My Dashboard - Original Mockup&quot; style=&quot;border: 1px solid silver; box-shadow: 5px 5px 10px #888&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
From his original email about the above mockup:
&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;
    &lt;p&gt;The intent here is that one of the columns has rows that have a similar width. The rows could be dragged and dropped into a different order &#8211; or potentially the two columns could also be reordered. The rows will basically be comprised of similar widgets. You can see in the mockup how the first two rows might look &#8211; and sample widgets. The widgets shown can be configured by the end user, as well as the order in which they are displayed. Other requirements given to us were the following.
    &lt;/p&gt;
    &lt;ul&gt;&lt;li&gt;Row 1 is comprised of &apos;summary&apos; widgets that are &apos;todo&apos; items. Reviews needing done &#8211; approvals required &#8211; etc.&lt;/li&gt;
        &lt;li&gt;Row 2 will be a graph row &#8211; having graphs and charts to display information &#8211; larger squares will build this row.&lt;/li&gt;
        &lt;li&gt;Row 3&apos;s content was not determined yet.&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;I started the initial layout with static HTML and CSS and had a wireframe to show by mid January.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969226/&quot; href=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_c.jpg&quot; title=&quot;Wireframe&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_n.jpg&quot; width=&quot;320&quot; height=&quot;268&quot; alt=&quot;Wireframe&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;By the end of January, we&apos;d renamed the project to My Dashboard and had a working prototype using &lt;a href=&quot;http://randomibis.com/coolclock/&quot;&gt;CoolClock&lt;/a&gt; and &lt;a href=&quot;http://momentjs.com/&quot;&gt;moment.js&lt;/a&gt; for the clock in the top right, &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; to display widget data, &lt;a href=&quot;http://jqueryui.com/&quot;&gt;jQuery UI&lt;/a&gt; for drag-n-drop of rows and widgets, Bootstrap&apos;s &lt;a href=&quot;http://twitter.github.com/bootstrap/javascript.html#carousel&quot;&gt;Carousel&lt;/a&gt; for holding charts and &lt;a href=&quot;http://www.highcharts.com/&quot;&gt;Highcharts&lt;/a&gt; for rendering charts.&lt;/p&gt;
</summary>
        <content type="html">&lt;p&gt;There&apos;s &lt;a href=&quot;http://hop.ie/blog/angularjs-introduction/&quot;&gt;many&lt;/a&gt;, &lt;a href=&quot;http://www.raweng.com/blog/2013/01/30/introduction-to-angularjs-part-1/&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;http://www.codeproject.com/Articles/607873/Extending-HTML-with-AngularJS-Directives&quot;&gt;different&lt;/a&gt; &lt;a href=&quot;http://www.webdesignerdepot.com/2013/04/an-introduction-to-angularjs/&quot;&gt;introductions&lt;/a&gt; to &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; available on the internet. This article is not another introduction, but rather a story about my learning experience. It all started way back in January of this year. I was working as a UI Architecture Consultant at Taleo/Oracle, my client for the last 21 months. My gig there ended last month, but they agreed to let me publish a series of articles about the knowledge I gained.&lt;/p&gt;

&lt;h3 id=&quot;background&quot;&gt;Project Background&lt;/h3&gt;
&lt;p&gt;The Director of Product Management had been working on the concepts for a new project - codenamed &quot;Visual MyView&quot;. Below is a mockup he created for our kickoff meeting on January 4th.
&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352595/&quot; href=&quot;http://farm3.staticflickr.com/2872/8904352595_1678cfd1ab_c.jpg&quot; title=&quot;My Dashboard - Original Mockup&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2872/8904352595_1678cfd1ab.jpg&quot; width=&quot;500&quot; height=&quot;296&quot; alt=&quot;My Dashboard - Original Mockup&quot; style=&quot;border: 1px solid silver; box-shadow: 5px 5px 10px #888&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;
From his original email about the above mockup:
&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;
    &lt;p&gt;The intent here is that one of the columns has rows that have a similar width. The rows could be dragged and dropped into a different order &#8211; or potentially the two columns could also be reordered. The rows will basically be comprised of similar widgets. You can see in the mockup how the first two rows might look &#8211; and sample widgets. The widgets shown can be configured by the end user, as well as the order in which they are displayed. Other requirements given to us were the following.
    &lt;/p&gt;
    &lt;ul&gt;&lt;li&gt;Row 1 is comprised of &apos;summary&apos; widgets that are &apos;todo&apos; items. Reviews needing done &#8211; approvals required &#8211; etc.&lt;/li&gt;
        &lt;li&gt;Row 2 will be a graph row &#8211; having graphs and charts to display information &#8211; larger squares will build this row.&lt;/li&gt;
        &lt;li&gt;Row 3&apos;s content was not determined yet.&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;I started the initial layout with static HTML and CSS and had a wireframe to show by mid January.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969226/&quot; href=&quot;http://farm3.staticflickr.com/2885/8904969226_c33d020e07_c.jpg&quot; title=&quot;Wireframe&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2885/8904969226_c33d020e07_n.jpg&quot; width=&quot;320&quot; height=&quot;268&quot; alt=&quot;Wireframe&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;By the end of January, we&apos;d renamed the project to My Dashboard and had a working prototype using &lt;a href=&quot;http://randomibis.com/coolclock/&quot;&gt;CoolClock&lt;/a&gt; and &lt;a href=&quot;http://momentjs.com/&quot;&gt;moment.js&lt;/a&gt; for the clock in the top right, &lt;a href=&quot;http://angularjs.org/&quot;&gt;AngularJS&lt;/a&gt; to display widget data, &lt;a href=&quot;http://jqueryui.com/&quot;&gt;jQuery UI&lt;/a&gt; for drag-n-drop of rows and widgets, Bootstrap&apos;s &lt;a href=&quot;http://twitter.github.com/bootstrap/javascript.html#carousel&quot;&gt;Carousel&lt;/a&gt; for holding charts and &lt;a href=&quot;http://www.highcharts.com/&quot;&gt;Highcharts&lt;/a&gt; for rendering charts. For this prototype, we included 4 types of widgets:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Summary&lt;/li&gt;&lt;li&gt;Tasks&lt;/li&gt;&lt;li&gt;Charts&lt;/li&gt;&lt;li&gt;Reports&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;To create widgets, we had to decide on a common schema for them.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&quot;id&quot;: 1, // not necessary for display, but likely needed if we modify and save preferences
&quot;title&quot;: &quot;Appointments Today&quot;,
&quot;type&quot;: &quot;summary&quot;, // others include: task, chart, report
&quot;value&quot;: 3, 
&quot;description&quot;: &quot;10:30 Jim Smith&quot;,
&quot;events&quot;: &quot;url&quot;, // this can have click events
&quot;order&quot;: 1 // used to determine order
&lt;/pre&gt;
&lt;p&gt;Below is a screenshot of our wireframe with some sample widgets.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904969540/&quot; href=&quot;http://farm9.staticflickr.com/8556/8904969540_1cfe0e56c7_c.jpg&quot; title=&quot;Wireframe with Data&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm9.staticflickr.com/8556/8904969540_1cfe0e56c7.jpg&quot; width=&quot;500&quot; height=&quot;342&quot; alt=&quot;Wireframe with Data&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h3 id=&quot;basics&quot;&gt;Angular Basics&lt;/h3&gt;
&lt;p&gt;The decision to use AngularJS came early on in the project, after I read Tyler Renelle&apos;s &lt;a href=&quot;https://gist.github.com/lefnire/4454814&quot;&gt;Rant: Backbone, Angular, Meteor, Derby&lt;/a&gt;. To learn AngularJS, I briefly looked at its homepage documentation and played with some examples. Then I stumbled upon Misko Hevery and Igor Minar&apos;s &lt;a href=&quot;http://parleys.com/play/5148922b0364bc17fc56c91b/about&quot;&gt;AngularJS Presentation from Devoxx 2012&lt;/a&gt;. At that time, the video wasn&apos;t publicly available (it&apos;s free now), so I had to buy a Parley&apos;s subscription ($79). It was well worth the money because that one hour video greatly contributed to my understanding of how AngularJS works. Another resource I used frequently to figure out how to do things was John Lindquist&apos;s &lt;a href=&quot;http://www.egghead.io/&quot;&gt;egghead.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To begin with, we wrote the JSON for a bunch of sample widgets and embedded them into the page as a &lt;code&gt;widgetData&lt;/code&gt; JavaScript variable.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var widgetData = [
    {&quot;id&quot;: 1, &quot;title&quot;: &quot;Appointments Today&quot;, &quot;type&quot;: &quot;summary&quot;, &quot;value&quot;: 3, &quot;description&quot;: &quot;10:30 Jim Smith&quot;, &quot;events&quot;: {&quot;click&quot;: &quot;alert(&apos;foo&apos;);&quot;}, &quot;order&quot;: 1},
    {&quot;id&quot;: 12, &quot;order&quot;: 2, &quot;title&quot;: &quot;Offer Approvals&quot;, &quot;type&quot;: &quot;task&quot;, &quot;class&quot;: &quot;sticky-note&quot;, &quot;value&quot;: 1},
    {&quot;id&quot;: 103, &quot;title&quot;: &quot;Browser market shares at a specific website, 2010&quot;, &quot;order&quot;: 1, &quot;type&quot;: &quot;chart&quot;, &quot;chartType&quot;: &quot;pie&quot;, 
         &quot;tooltip&quot;: {&quot;pointFormat&quot;: &quot;{series.name}: &amp;lt;b&gt;{point.percentage}%&amp;lt;/b&gt;&quot;, &quot;percentageDecimals&quot;: 1}, &quot;series&quot;: [
         {&quot;type&quot;: &quot;pie&quot;, &quot;name&quot;: &quot;Browser share&quot;, &quot;data&quot;: [
             [&quot;Firefox&quot;, 45.0],
             [&quot;IE&quot;, 26.8],
             {&quot;name&quot;: &quot;Chrome&quot;, &quot;y&quot;: 12.8, &quot;sliced&quot;: true, &quot;selected&quot;: true},
             [&quot;Safari&quot;, 8.5],
             [&quot;Opera&quot;, 6.2],
             [&quot;Others&quot;, 0.7]
         ]}
    ]},
    ...
];
&lt;/pre&gt;
&lt;p&gt;I used &lt;a href=&quot;https://github.com/angular/angular-seed&quot;&gt;angular-seed&lt;/a&gt; to create the initial structure of the prototype, and continued using the same JavaScript file names when we moved it into the product I worked on. Since the application takes a while to login and render the My Dashboard page (when working remotely), I decided not to use the &lt;a href=&quot;http://karma-runner.github.io/0.8/index.html&quot;&gt;Karma&lt;/a&gt; testing framework that ships with Angular. Below is what our directory structure looked like for our prototype.&lt;/p&gt;
&lt;p style=&quot;text-align: center&quot;&gt;
&lt;a data-href=&quot;http://www.flickr.com/photos/mraible/8904352305/&quot; href=&quot;http://farm3.staticflickr.com/2860/8904352305_e3b3d40f80_c.png&quot; title=&quot;Angular Seed Directory Structure&quot; rel=&quot;lightbox[angular-dashboard1]&quot;&gt;&lt;img src=&quot;//farm3.staticflickr.com/2860/8904352305_e3b3d40f80_o.png&quot; width=&quot;327&quot; height=&quot;459&quot; alt=&quot;Angular Seed Directory Structure&quot;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The JavaScript files in the &quot;js&quot; folder are the most important for Angular. The first file, &lt;code&gt;app.js&lt;/code&gt;, loads the other files:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
angular.module(&apos;dashboard&apos;, [&apos;dashboard.filters&apos;, &apos;dashboard.services&apos;, &apos;dashboard.directives&apos;]);
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;controllers.js&lt;/code&gt; file contains the Controllers (functions) that get the data and make it available to the page. Here&apos;s the code for our first controller:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
&apos;use strict&apos;;

/* Controllers */
function WidgetController($scope) {
    $scope.widgets = widgetData;
}
&lt;/pre&gt;
&lt;p&gt;This puts the widgets in scope and then we were able to render them using Angular&apos;s &lt;a href=&quot;http://docs.angularjs.org/api/ng.directive:ngRepeat&quot;&gt;ngRepeat&lt;/a&gt; directive and the following HTML:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-app=&quot;dashboard&quot; class=&quot;dashboard&quot;&amp;gt;
    &amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
        &amp;lt;div class=&quot;row-fluid&quot;&amp;gt;
            &amp;lt;div class=&quot;span9&quot;&amp;gt;
                &amp;lt;ul class=&quot;widgets&quot;&amp;gt;
                    &amp;lt;li id=&quot;summary-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;Summary&amp;lt;/div&amp;gt;
                        &amp;lt;ul class=&quot;tiles&quot;&amp;gt;
                            &amp;lt;li class=&quot;span3&quot; ng-repeat=&quot;widget in widgets | filter:{type: &apos;summary&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                                &amp;lt;h3 class=&quot;events&quot;&amp;gt;{{widget.value}}&amp;lt;/h3&amp;gt;
                                &amp;lt;div class=&quot;title&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;desc&quot;&amp;gt;{{widget.description}}&amp;lt;/div&amp;gt;
                            &amp;lt;/li&amp;gt;
                        &amp;lt;/ul&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li id=&quot;task-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;My Tasks&amp;lt;/div&amp;gt;
                        &amp;lt;ul class=&quot;tasks&quot;&amp;gt;
                            &amp;lt;li class=&quot;task {{widget.class}}&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;task&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
                                &amp;lt;div class=&quot;title events&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;value&quot;&amp;gt;{{widget.value}}&amp;lt;/div&amp;gt;
                            &amp;lt;/li&amp;gt;
                        &amp;lt;/ul&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li id=&quot;chart-bar&quot;&amp;gt;
                        &amp;lt;div class=&quot;heading&quot;&amp;gt;Charts&amp;lt;/div&amp;gt;
                        &amp;lt;div id=&quot;chartCarousel&quot; class=&quot;carousel slide&quot;&amp;gt;
                            &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
                                &amp;lt;li data-target=&quot;#chartCarousel&quot;
                                    ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                                    data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
                            &amp;lt;/ol&amp;gt;
                            &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
                                &amp;lt;div class=&quot;item chart&quot;
                                     ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                                     ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                                    &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                            &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
                            &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;span3&quot;&amp;gt;
                &amp;lt;!-- clock and reports --&amp;gt;
            &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The beginning of this HTML shows how Angular is instantiated: &lt;strong&gt;ng-app&lt;/strong&gt; matches the name defined in &lt;code&gt;app.js&lt;/code&gt;, &lt;strong&gt;ng-controller&lt;/strong&gt; instantiates the &lt;code&gt;WidgetController&lt;/code&gt; and &lt;strong&gt;ng-cloak&lt;/strong&gt; is used to hide everything until its processed.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div ng-app=&quot;dashboard&quot; class=&quot;dashboard&quot;&amp;gt;
    &amp;lt;div class=&quot;container-widgets&quot; ng-controller=&quot;WidgetController&quot; ng-cloak&amp;gt;
&lt;/pre&gt;
&lt;p&gt;If you take a closer look at the way ng-repeat attributes, you&apos;ll see how &lt;strong&gt;filters&lt;/strong&gt; are used to filter data. There&apos;s &lt;a href=&quot;http://docs.angularjs.org/api/ng.filter:filter&quot;&gt;&lt;em&gt;filter&lt;/em&gt;&lt;/a&gt;&lt;em&gt; &lt;/em&gt;and &lt;a href=&quot;http://docs.angularjs.org/api/ng.filter:orderBy&quot;&gt;&lt;em&gt;orderBy&lt;/em&gt;&lt;/a&gt; filters that are built in and allow you to filter data. The &lt;em&gt;filter &lt;/em&gt;filter allows you to query arrays by strings, objects and even functions. In the following code block, &quot;task&quot; widgets are filtered, ordered and displayed.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;li class=&quot;task {{widget.class}}&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;task&apos;} | orderBy: &apos;order&apos;&quot;&amp;gt;
    &amp;lt;div class=&quot;title events&quot;&amp;gt;{{widget.title}}&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;value&quot;&amp;gt;{{widget.value}}&amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This was pretty straightforward, but we quickly noticed that if a widget had HTML in its title, it didn&apos;t display correctly (rendering the raw HTML). To process the HTML, we had to use the &lt;a href=&quot;http://docs.angularjs.org/api/ngSanitize.directive:ngBindHtml&quot;&gt;ngBindHtml&lt;/a&gt; directive (tip: directives are camelCase, but written with dashes in HTML).&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;title events&quot; ng-bind-html=&quot;widget.title&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;After getting this to work, we noticed that some titles weren&apos;t fully rendered because they were hidden with overflow: hidden. We tried adding a tooltip with &lt;code&gt;title=&quot;{{widget.title}}&quot;&lt;/code&gt;, but ran into the same issue. I &lt;a href=&quot;https://groups.google.com/d/topic/angular/hG-T1bsmlnk/discussion&quot;&gt;sent an email &lt;/a&gt;to the AngularJS Google Group and received a solution: create an htmlTitle directive:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
.directive(&apos;htmlTitle&apos;, function ($sanitize) {
    return {
        restrict: &apos;A&apos;,
        link: function (scope, element, attrs) {
            attrs.$observe(&apos;htmlTitle&apos;, function (title) {
                // convert &amp;amp;value; to HTML
                var html = angular.element(&apos;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&apos;).html($sanitize(title)).text();
                element.attr(&apos;title&apos;, html);
                element.html(html);
            });
        }
    }
})
&lt;/pre&gt;
&lt;p&gt;Usage:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div class=&quot;title events&quot; ng-bind-html=&quot;widget.title&quot; html-title=&quot;{{widget.title}}&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;h3 id=&quot;dragndrop&quot;&gt;Drag-and-Drop&lt;/h3&gt;
&lt;p&gt;To implement drag-and-drop functionality, I originally used jQuery UI&apos;s &lt;a href=&quot;http://jqueryui.com/sortable/&quot;&gt;sortable&lt;/a&gt;. At the bottom of the page, the following code initialized sorting for the various lists:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
$(document).ready(function() {
    $(&apos;.widgets&apos;).sortable({
        cursor: &quot;move&quot;,
        handle: &quot;.heading&quot;
    }).disableSelection();
    $(&apos;.tiles,.tasks&apos;).sortable();
    var carousel = $(&apos;.carousel&apos;);
    $(carousel).carousel({
        interval: 0
    });
};
&lt;/pre&gt;
&lt;p&gt;As you can see, it also initializes the carousel and stops it from cycling automatically.&lt;/p&gt;
&lt;h3 id=&quot;carousel-issues&quot;&gt;Carousel Issues&lt;/h3&gt;
&lt;p&gt;The first problem I ran into with Bootstrap&apos;s Carousel was a strange error from Highcharts. If you look in the above HTML, you&apos;ll see there&apos;s a &lt;code&gt;&amp;lt;chart&amp;gt;&lt;/code&gt; element. This is processed by a &lt;a href=&quot;https://github.com/rootux/angular-highcharts-directive/blob/master/src/directives/highchart.js&quot;&gt;highcharts directive&lt;/a&gt;. When I tried to use this directive for Highcharts in a carousel, it results in the following error:&lt;/p&gt;
&lt;p class=&quot;alert alert-error&quot;&gt;
TypeError: Cannot read property &apos;length&apos; of undefined at Object.ob.setMaxTicks
&lt;/p&gt;
&lt;p&gt;This seemed to be caused by the following css in Bootstrap:&lt;/p&gt;
&lt;pre class=&quot;brush: css&quot;&gt;
.carousel-inner &gt; .item { display: none }
&lt;/pre&gt;
&lt;p&gt;When I added an override with &quot;display: block&quot; to my stylesheet, everything worked, but the charts were stacked instead of in a carousel. To fix this, I modified the directive to show/hide the &quot;item&quot; element so Highcharts was able to write to it. I also &lt;a href=&quot;https://github.com/rootux/angular-highcharts-directive/issues/1&quot;&gt;logged an issue&lt;/a&gt; for this.&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
if (element.parent().not(&apos;:visible&apos;)) {
    element.parent().show();
}
var chart = new Highcharts.Chart(newSettings);
element.parent().attr(&apos;style&apos;, &apos;&apos;);
&lt;/pre&gt;
&lt;h3 id=&quot;grouping&quot;&gt;ngRepeat and Grouping&lt;/h3&gt;
&lt;p&gt;The last thing I accomplished in our end-of-January prototype was rendering 2 charts side-by-side. I got it working with plain HTML, created a &quot;groupBy&quot; filter for Angular and tried to get it to work with the following:&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;div id=&quot;chartCarousel&quot; class=&quot;carousel slide&quot;&amp;gt;
    &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
        &amp;lt;li data-target=&quot;#chartCarousel&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | groupBy&quot;
            data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
        &amp;lt;div class=&quot;item&quot; ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | groupBy&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
            &amp;lt;div class=&quot;widget&quot;&amp;gt;{{widget&amp;#91;0&amp;#93;.title}}&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;widget&quot;&amp;gt;{{widget&amp;#91;1&amp;#93;.title}}&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
    &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;
&lt;p&gt;This all worked like I expected it to in Chrome, but I the following errors showed in my console.&lt;/p&gt;
&lt;p class=&quot;alert alert-error&quot;&gt;
Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations:
&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;https://groups.google.com/d/topic/angular/gEv1-YV-Ojg/discussion&quot;&gt;sent an email&lt;/a&gt; to the Angular Google Group and received a link to &lt;a href=&quot;https://groups.google.com/d/msg/angular/IEIQok-YkpU/oKuLvzCnAcoJ&quot;&gt;a discussion&lt;/a&gt; where I found a &quot;chunk&quot; filter that solved the problem. This worked great, but I wanted to make it more responsive.&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;If the user has a screen size big enough to fit 2 charts, show 2 charts and paginate by 2.&lt;/li&gt;&lt;li&gt;If the user has a small screen size that only fits 1 chart, show 1 and paginate by 1.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;To solve #1 and #2, I ended up rendering two different sections (with classes .oneup and .twoup) and displayed them based on screen size.&lt;/p&gt;
&lt;pre class=&quot;brush: html&quot;&gt;
&amp;lt;li id=&quot;chart-bar&quot;&amp;gt;
    &amp;lt;div class=&quot;heading&quot;&amp;gt;Charts&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;chartCarousel1&quot; class=&quot;carousel slide oneup&quot; style=&quot;display: none&quot;&amp;gt;
        &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
            &amp;lt;li data-target=&quot;#chartCarousel1&quot;
                ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
        &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
            &amp;lt;div class=&quot;item chart&quot;
                 ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | orderBy: &apos;order&apos;&quot;
                 ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget}}&quot; type=&quot;{{widget.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel1&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
        &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel1&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;chartCarousel2&quot; class=&quot;carousel slide twoup&quot; style=&quot;display: none&quot;&amp;gt;
        &amp;lt;ol class=&quot;carousel-indicators&quot;&amp;gt;
            &amp;lt;li data-target=&quot;#chartCarousel2&quot;
                ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot;
                data-slide-to=&quot;{{$index}}&quot; ng-class=&quot;{active: $index == 0}&quot;&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ol&amp;gt;
        &amp;lt;div class=&quot;carousel-inner&quot;&amp;gt;
            &amp;lt;div class=&quot;item chart&quot;
                 ng-repeat=&quot;widget in widgets | filter: {type: &apos;chart&apos;} | chunk: 2 | orderBy: &apos;order&apos;&quot;
                 ng-class=&quot;{active: $index == 0}&quot;&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;0&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;0&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
                &amp;lt;chart class=&quot;widget&quot; value=&quot;{{widget&amp;#91;1&amp;#93;}}&quot; type=&quot;{{widget&amp;#91;1&amp;#93;.chartType}}&quot;&amp;gt;&amp;lt;/chart&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;a class=&quot;left carousel-control&quot; href=&quot;#chartCarousel2&quot; data-slide=&quot;prev&quot;&amp;gt;&#8249;&amp;lt;/a&amp;gt;
        &amp;lt;a class=&quot;right carousel-control&quot; href=&quot;#chartCarousel2&quot; data-slide=&quot;next&quot;&amp;gt;&#8250;&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/li&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The JavaScript to show the correct number of charts is below:&lt;/p&gt;
&lt;pre class=&quot;brush: js&quot;&gt;
var chartBar = $(&apos;#chart-bar&apos;);
function showCharts() {
    if (chartBar.width() &amp;lt; 960) {
        chartBar.find(&apos;.oneup&apos;).show();
        chartBar.find(&apos;.twoup&apos;).hide();
    } else {
        chartBar.find(&apos;.twoup&apos;).show();
        chartBar.find(&apos;.oneup&apos;).hide();
    }
}

$(document).ready(function () {
    showCharts();
});

$(window).resize(showCharts);
&lt;/pre&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Even though I got everything to work for our initial prototype using Angular and jQuery, it didn&apos;t quite feel like I was taking full advantage of Angular&apos;s power. In particular, I learned that Angular UI Bootstrap had their own carousel and Angular UI had a &lt;em&gt;sortable&lt;/em&gt; directive. My suspicions were confirmed when I read &lt;a href=&quot;http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background&quot;&gt;How do I &quot;think in AngularJS&quot; if I have a jQuery background?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;http://raibledesigns.com/rd/entry/developing_with_angularjs_part_ii&quot;&gt;next article&lt;/a&gt;, I&apos;ll talk about how I migrated to use Angular UI&apos;s &lt;a href=&quot;http://angular-ui.github.io/bootstrap/#/carousel&quot;&gt;carousel&lt;/a&gt; and &lt;a href=&quot;https://github.com/angular-ui/ui-sortable&quot;&gt;sortable&lt;/a&gt; directives, as well as integrating dialogs.&lt;/p&gt;</content>
    </entry>
</feed>

