-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
executable file
·689 lines (313 loc) · 42.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js" lang="en"><!--<![endif]-->
<head>
<meta charset="utf-8">
<title>Zach Lendon's Blog</title>
<meta name="author" content="Zach Lendon">
<meta name="description" content="Starbucks stores have traditionally been both an individual workhaven and a meeting place for professionals. However, with limited seating, …">
<!-- http://t.co/dKP3o1e -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="canonical" href="http://zachlendon.github.com">
<link href="/favicon.png" rel="icon">
<link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
<script src="/javascripts/modernizr-2.0.js"></script>
<script src="/javascripts/ender.js"></script>
<script src="/javascripts/octopress.js" type="text/javascript"></script>
<link href="/atom.xml" rel="alternate" title="Zach Lendon's Blog" type="application/atom+xml">
<!--Fonts from Google"s Web font directory at http://google.com/webfonts -->
<link href="http://fonts.googleapis.com/css?family=PT+Serif:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=PT+Sans:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css">
</head>
<body >
<header role="banner"><hgroup>
<h1><a href="/">Zach Lendon's Blog</a></h1>
<h2>Mobile, iOS, Grails and more</h2>
</hgroup>
</header>
<nav role="navigation"><ul class="subscription" data-subscription="rss">
<li><a href="/atom.xml" rel="subscribe-rss" title="subscribe via RSS">RSS</a></li>
</ul>
<form action="http://google.com/search" method="get">
<fieldset role="search">
<input type="hidden" name="q" value="site:zachlendon.github.com" />
<input class="search" type="text" name="q" results="0" placeholder="Search"/>
</fieldset>
</form>
<ul class="main-navigation">
<li><a href="/">Blog</a></li>
<li><a href="/blog/archives">Archives</a></li>
</ul>
</nav>
<div id="main">
<div id="content">
<div class="blog-index">
<article>
<header>
<h1 class="entry-title"><a href="/blog/2014/02/12/starbucks-big-missed-opportunity/">Starbucks’ Big Missed Opportunity</a></h1>
<p class="meta">
<time datetime="2014-02-12T00:10:00-06:00" pubdate data-updated="true">Feb 12<span>th</span>, 2014</time>
</p>
</header>
<div class="entry-content"><p>Starbucks stores have traditionally been both an individual workhaven and a meeting place for professionals. However, with limited seating, uncomfortable chairs, an often loud environment, and limited outlets, Starbucks is not in the business of providing their customers a coworking location. However, they should be.</p>
<p>Coworking is a trend that is growing in strength and revenue for companies such as <a href="http://www.regus.com">Regus</a>. Professionals are <a href="http://www.fastcompany.com/3004788/future-coworking-and-why-it-will-give-your-business-huge-edge#4">not only reporting improved performance, creativity and self-confidence in these workspaces, they are reporting that they are feeling healthier in them as well</a>. Coworking spaces provide not only desk areas for individuals to work at, but often meeting spaces, copier/printer services, and more. <a href="http://www.mycafeinc.com">Cafe Inc</a>, a Minneapolis-based startup, joins a growing trend nationally of “proworking” locations - coworking locations that provide additional amenities such as a lounge, cafe, and coffee shop area alongside traditional coworking amenities (<a href="https://www.facebook.com/mycafeinc/photos_stream?ref=br_tf">photos</a>).</p>
<p>Most Starbucks stores as currently constructed do not lend themselves to coworking or proworking from their customers. However, Starbucks has shown an interest in branching into the “cafe” space as they continue to expand their food offerings. Starbucks also already offers limited lounge areas in many of their locations. Having stores that have traditional seating areas as they exist now, but also premium spaces where Starbucks could upsell professional coworking services, especially on a day pass basis, can meet a market need that is currently going largely unfulfilled. Many current Starbucks locations could not support this model, requiring either new locations to be built with this in mind, or retrofitting existing locations (one’s that are not standalone where accompanying space could be acquired). Starbucks has shown a willingness to tweak their brand and innovate of late, and this is an opportunity for them to become even more ingrained in their customers’ daily lives. Workers who show a willingness, at the spur of the moment (or even planned in advance) to pay to have their business meeting in private, or use a secured wifi in a more private, comfortable setting for their own work, also would be likely to buy more beverages and food from the Starbucks store adjacent to their short-term professional work area. Shouldn’t Starbucks be in the business of meeting their customers’ needs in a way that also maximizes Starbucks’ potential profit? If they don’t, I believe other businesses will step into their place and not only cement a missed opportunity for Starbucks, but slowly start to erode at portions of their core business.</p>
<p>Professionals already are going into Starbucks to conduct business and to work - and Starbucks wants that, to a point. Starbucks should consider embracing what their customers are already doing and better execute at profiting off of it by providing proworking services in the not too distant future.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/07/22/grails-event-push-real-world-notes/">Grails Event Push Real-world Notes</a></h1>
<p class="meta">
<time datetime="2013-07-22T14:32:00-05:00" pubdate data-updated="true">Jul 22<span>nd</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p>Today’s excellent talk by Colin Harrington at <a href="gr8conf.us">GR8ConfUS</a> on ASync and in particular the Events Push plugin has me wanting to note some thoughts I’ve developed through using the plugin at my current client.</p>
<p>First off, I use the following BuildConfig.groovy definition, so that I can exclude the resources plugin version used by the plugin:</p>
<pre><code> compile("org.grails.plugins:events-push:1.0.M7") {
excludes 'resources', 'org.atmosphere:atmosphere-runtime'
}
</code></pre>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/07/01/a-sencha-dirty-store-records-quirk/">A Sencha Dirty Store Records Quirk</a></h1>
<p class="meta">
<time datetime="2013-07-01T10:07:00-05:00" pubdate data-updated="true">Jul 1<span>st</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p>Sencha provides some pretty slick out of the box grid capabilities, one of which, editing, is demonstrated in the ExtJS 4 example <a href="http://dev.sencha.com/deploy/ext-4.0.1/examples/grid/cell-editing.html">shown here</a>.</p>
<p>One example of how Sencha does a great job making the easy hard and the difficult easy (just an opinion I’ve fashioned over the past several months) is the handling of editing empty cell values. For one of the apps I work on, I read XML (yes, awesome I know) from a web service data using <a href="http://groovy.codehaus.org/modules/http-builder/">Groovy’s HTTPBuilder</a>, which under the covers uses <a href="http://groovy.codehaus.org/api/groovy/util/XmlSlurper.html">XmlSlurper</a> to parse said data. When the data is empty, it is by default parsed and represented as “”. When you edit a field in the Sencha grid however, it gets changed to be represented as null. This therefore marks a record as modified in the Sencha store, when it really hasn’t been. Probably the proper approach to deal with this is to write logic in the Sencha <a href="http://docs.sencha.com/extjs/4.1.3/#!/api/Ext.data.Store-event-update">store update event</a> to check modified records for this “”/null shenanigans and not allow the update on the modified record to occur. Once you start making modifications like this for several store objects you’ll probably find it best just to get your OOP on and create your own Store object that extends Sencha’s with goodies like this.</p>
<p>Ultimately Sencha should in my opinion not mark these records as modified for you. But it is just another example how Sencha is great at doing heavy lifting but leaves lots of little pieces around for you to deal with - many which you probably shouldn’t have to.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/04/02/updated-extjs4-mock-ajax-library-for-jasmine/">Updated ExtJs4 Mock Ajax Library for Jasmine</a></h1>
<p class="meta">
<time datetime="2013-04-02T14:17:00-05:00" pubdate data-updated="true">Apr 2<span>nd</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p><a href="https://twitter.com/kenspirit">@kenspirit</a> does a nice job in this series (<a href="http://www.thinkingincrowd.me/blog/2012/08/13/extjs-jasmine-unit-test-part-1-philosophy-and-test-for-store/">part 1</a> and <a href="http://www.thinkingincrowd.me/blog/2012/08/30/extjs-jasmine-unit-test-part-2-ajax-behavior-2/">part 2</a>) of blog posts talking about some of the pain points (Stores, Ajax handling) of unit testing ExtJS applications. While <a href="https://twitter.com/kenspirit">@kenspirit</a> provides a nice adaptation of <a href="https://github.com/pivotal/jasmine-ajax">jasmine-ajax</a> for ExtJs (as jasmine-ajax only supports JQuery and Prototype currently), that adaptation does not work with ExtJS4. The following <a href="https://gist.github.com/zachlendon/5295365">updated version should work - at least for basic Ajax mocking</a>. Let me know if you have issues and I can (futher) update and improve upon this latest adaptation.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/03/27/extjs4-fun-not-really-with-styling-checkboxes-in-grids/">ExtJS4: Fun (Not Really) With Styling Checkboxes in Grids</a></h1>
<p class="meta">
<time datetime="2013-03-27T23:57:00-05:00" pubdate data-updated="true">Mar 27<span>th</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p><a href="http://www.sencha.com/blog/optimizing-ext-js-4-1-based-applications">‘boxready’</a> is a good event to listen to to change checkbox CSS styles programmatically when using <a href="http://docs.sencha.com/ext-js/4-1/#!/api/Ext.selection.CheckboxModel">CheckboxModels</a> in an extJS4 panel where the ‘grid’ is defined as an ‘item’ in a panel. When you are defining an actual ‘grid’ component though (i.e., when you are doing more than simply tying an extJS ‘store’ to a standard grid component via an item) and want to change checkboxes within a grid listener, you’ll want to use the ‘viewready’ event instead - with a ‘defer’ to boot. I admit that it seems that there should be another event that one could use and not have to do a ‘defer’ timing hack, but so far my attempts at trying other events have proven fruitless (I’d love to be advised differently). All of these events in question are shown in <a href="http://docs.sencha.com/ext-js/4-1/source/AbstractView.html#Ext-view-AbstractView">AbstractView’s source</a>, and when using extJS4+ it’s worthwhile to understand all that goes on in this class, especially event-wise.</p>
<p>The reason one needs to do this styling logic within these events is that the style changes must be applied after everything in your component is visible and any styles are calculated and applied to elements within that component. While you would think you could do these style settings when you instantiate the CheckboxModel and have them honored, and there are random illusions to this working online - you will find that it will fail you under certain scenarios. As an example, ‘headerConfig’ in CheckboxModel has a headerWidth property. Howevever, if you look at <a href="https://code.google.com/p/extjs4/source/browse/trunk/ext4/extjs/src/selection/CheckboxModel.js?r=3">CheckboxModel’s source</a>, setting the config does not appear to actually change it’s value (and in practice this is what I’ve seen). In certain grid scenarios, depending especially on the ‘flex’ property of other columns, you may (ok - will) find ExtJS4+ re-sizing your checkbox columns to excessive sizes - usually on the big end, but potentially too small based on your requirements.</p>
<p>While it is possible and in practice probably better ‘code quality’ to actually get the grid or panel component (using Ext.getCmp() - by id) and then access the CheckboxModel using a ComponentQuery selector - and then change it’s width, another approach is to do it in a more JQuery-like fashion (in Sencha syntax of course). I quite honestly find this to be a bit easier to do, and when working with Sencha, sometimes easy is really welcomed. This is also satisfactorily safe to do in my opinion if you don’t have lots of elements on a page such that the querying performance of these operations will impact the usability of your app. With a pretty complex app I’ve not seen the below queries suffer performance-wise:</p>
<div><script src='https://gist.github.com/5260802.js?file='></script>
<noscript><pre><code></code></pre></noscript></div>
<p>The above code would change the checkboxes in the first row of a grid - the first item finding the styled header checkbox (so you can select all rows) and the second loop finding all rows and grabbing the first element from that row. Yes, you don’t need the ‘each’ necessarily in the first scenario, but it doesn’t hurt anything either.</p>
<p>While extJS4 and Sencha Touch continue to prove to be very powerful frameworks, understanding their nuances and pain points continues to be an interesting journey to be embarking upon.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/03/26/quick-thoughts-on-testem-vs-testacular-karma/">Thoughts on Testem vs Testacular (Karma)</a></h1>
<p class="meta">
<time datetime="2013-03-26T00:25:00-05:00" pubdate data-updated="true">Mar 26<span>th</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p>Both <a href="http://karma-runner.github.com/0.8/index.html">Testacular</a> - recently renamed ‘Karma’ and <a href="https://github.com/airportyh/testem">Testem</a> are great test runners for improving your javascript unit testing workflow. While Karma was developed as part of AngularJS, it is certainly useful as a test runner for javascript unit tests regardless of frameworks and/or libraries leveraged. Key features that both Testem and Karma have include:</p>
<ul>
<li>Support for running/driving multiple browsers simultaneously, including headless browsers (i.e., PhantomJS)</li>
<li>Support for the main testing libraries out there - QUnit, Jasmine, Mocha, etc.</li>
<li>Both will watch the source/test files (that you selectively configure) for changes and automatically re-run tests</li>
<li>Both provide support for local and CI use</li>
<li>Both are terminal focused</li>
</ul>
<p>For me I have found that I have a slight preference for Testem, for a few reasons. One is the Text User Interface:</p>
<p><img src="/images/green.png"></p>
<p>which allows you to better visually see the test results by browsers, whereas Testacular provides this as straight output text lines:</p>
<p><img src="/images/output.png"></p>
<p>Secondly, the ability to also run tests from a browser is a nice alternative when the terminal is not providing you as much flexibility as you want in certain testing scenarios and development workflows. Configuring this option alongside testem is much more doable than alongside Karma. The minimal configuration that is required is described in the ‘Node Travelers’ Toolchain blogpost (in the <a href="http://blog.nodetraveller.com/Javascript/My-javascript-testing-toolchain.html">icing on the cake section</a>). As is the case in the terminal, the main benefit over a normal Jasmine/browser TDD workflow is that with this testem integration we get watching of changes and automatic reload of the browser on changes. As a small aside, I’ve always found the <a href="https://github.com/esbie/jasmine-bootstrap">Jasmine Bootstrap Reporter</a> to be a great reporter for browser Jasmine reports and significantly better than the default Jasmine Html reporters.</p>
<p><img src="/images/jasmineHtml.png"></p>
<p> Do note that when using testem with Jasmine that you may have to slightly modify your Jasmine javascript source to ensure that the #testem hashtag is honored when choosing to run a suite of tests or individual tests from the browser.</p>
<p>On the Karma side, I must admit that Karma does seem to have nice debugging integration into Webstorm IDE, an IDE that I’ve tried but never really used extensively. I also know Sublime Text 2 has <a href="http://sokolovstas.github.com/SublimeWebInspector/">similar javascript debugging support available of late</a>, so it’s possible that Karma’s debugging support is stronger than Testem and that leveraging it on an ongoing basis would prove its value. You’ll note in the Karma documentation “video” that support for “dumping” object state, console logging to the terminal, etc. provides a pretty strong workflow from Karma for those who want their javascript development TDD workflow view to look like a Sublime Text editor on one side of the screen and a terminal window on the other. This editor/terminal workflow is philosophically inline with what Testem is aiming for as well though, so I’m not sure you’re really losing this if your workflow fits one IDE/coding/execution paradigm vs. any other.</p>
<p>Ultimately both Karma and Testem will work for your javascript unit testing workflows and they are better than the alternative - no javascript unit tests and/or no javascript unit test runner. I have found evidence that helps confirm my impression that Testem seems to have been around longer as a project (and thus be a bit more mature), have a bit better documentation, and be more widely used. I’d also be remiss not to say that I have also found it mildly annoying that Google Search Result links to “Testacular” Google Groups posts are “dead ends”, as the group has been renamed (to Karma). When there is ultimately such a small difference between a set of frameworks and/or libraries, it is to me these little things that can add up and - for now - would make me lean towards and recommend Testem. That being said, hopefully we’ll continue to see innovation from both of these tools and therefore hopefully the story on their usefulness and viability is only in the early stages.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2013/01/15/give-in-to-javascript/">Embrace Your Javascript Overlords</a></h1>
<p class="meta">
<time datetime="2013-01-15T22:53:00-06:00" pubdate data-updated="true">Jan 15<span>th</span>, 2013</time>
</p>
</header>
<div class="entry-content"><p>My first job out of college was working on OfficeMax.com (for OfficeMax) in 1999, where I primarily wrote a combination of client-side code and server-side javascript, run on Netscape Enterprise Server. For several years thereafter, I attempted to run far away from this ‘javascript’ world, as javascript at the time seemed to be a mess to deal with (this is well pre-JQuery, let alone all the other javascript libraries/frameworks of today), and languages such as Java were where it was happening. I can remember going to Java One for a few years in the early 2000’s and the buzz there was very WWDC-like. It seems hard to believe in this day and age I’m sure. But it’s indicative of the tech industry being both cyclical and the fact that today’s hot technologies are tomorrow’s not quite so cool (but still widely used) technologies.</p>
<p>In the past few years I’ve strived to leverage the Java and object-oriented knowledge I gained from several years prior with other dynamic JVM languages and frameworks, as well as pried my way onto native and mobile web initiatives/projects. Aside from native mobile application work, I’ve found that working with javascript has been best at providing me with an ever-increasing amount of development enjoyment. The innovation in the space is often mind-boggling, and many of the solutions I run across are amongst some of the most elegant libraries and frameworks around today.</p>
<p>That being said, integrating client-side javascript libraries and frameworks with non-javascript-friendly (more on that in a moment) back-ends produces a set of challenges. There’s state synchronization, rather manual synchronizing of changes, wiring together script packages/packaging, CSS compilers, code minifiers, client-side MV+ frameworks, templating engines, client-side history, ORM, database, etc. And that’s just for starters. While this certainly can be managed by seasoned developers, after doing the work of adding all these pieces, wiring them together, testing them and maintaining them, one at <em>some point</em> has to ask themselves: “is this the best way to be doing this?”</p>
<p>I’ve long since asked the questioned and told myself “no” many times. That being said, few web projects are greenfield and rarely - or basically <em>never</em> - are decisions that drive technologies used at companies politics-free. Certainly though I’ve reached a level of exasperation with it. Frameworks such as <a href="http://derbyjs.com/">Derby</a>, <a href="https://github.com/socketstream/socketstream">Socketstream</a> and <a href="http://meteor.com/">Meteor</a> are either built upon or provide out-of-the-box (or optional yet rather easy) integration with popular libraries such as Node.js, Express, Socket.IO, Browserify and MongoDB. And many more. One of the challenges I see in the midwest as a developer is that there has been <em>so</em> much investment made my organizations and developers in the Java stack, and to a lesser extent Rails, that moving to these other stacks is an enormous challenge. There’s misperceptions out there I’m sure that provide excuses for resistance: performance issues, documentation issues, SEO issues, maturity, etc. As I similarly alluded to earlier, these are the same stories that get thrown out in the early part of any adoption cycle for impending technology trends. Some of them have validity to a degree, but they are widely overblown. From my vantage point, being that I’m a strong believer in the “realtime” web replacing the “dynamic” web we see today, platforms such as Node.js - or Vert.x - are our web application platforms of - at the very least - the not too distant future. And what language works on all these platforms, and all of the frameworks I mentioned above? Javascript. That’s why I say embrace it. That’s why I pushed in some talks I gave last year to “learn it” - to understand it - and most importantly, to know how to use it properly.</p>
<p>I’m hopeful in 2013 that I can - at the very least - help push the conversation at local companies and with local developers in my area forward on the types of technology stacks I’ve mentioned above. I have ideas for talks, blog posts and demo apps (not chat apps…) ready to be explored, to excite others, to help show the possibilities and dispel the myths. In short, I’m looking forward to helping others embrace our javascript overlords. If the interest and ideas are out there, I would certainly be very interested in joining forces with other local developers in this fight as well. We can either whine about the state of affairs at clients and companies (a trap I personally at times fall into), or we can actively work to show why there is a better way.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2012/11/16/using-tincr-with-grails-for-live-reloading/">Using Tincr With Grails for Live Client-Side Reloading</a></h1>
<p class="meta">
<time datetime="2012-11-16T23:32:00-06:00" pubdate data-updated="true">Nov 16<span>th</span>, 2012</time>
</p>
</header>
<div class="entry-content"><p>There’s various solutions out there for seeing client-side changes quickly in a browser. One such solution, <a href="http://livereload.com/">livereload.com</a>, was mentioned by Ted Naleid in <a href="https://twitter.com/tednaleid/status/269105419274813440">this tweet</a>. While I don’t have much experience with livereload, I’m not completely convinced I’m doing it wrong (as he suggests somewhat tongue-in-cheek) either (though it wouldn’t be the first time I’ve been wrong). I have been using another solution, and I wanted to share with you how to start using it with your own Grails application, if you so choose. The solution I have been leveraging for live-reload-“like” functionality is the Chrome extension <a href="http://tin.cr/">Tincr</a>. This post attempts to give you a quick guide to leveraging Tincr with your Grails 2.x application and talk briefly about how it helps you iterate your client-side development efforts quicker.</p>
<p>Once you install the Chrome extension, it will show up as a tab in your Chrome Developer Tools view. <img src="http://s8.postimage.org/5eas13ujn/Screen_Shot_2012_11_16_at_11_38_02_PM.png" alt="your Chrome Developer tools, like so:" /></p>
<p>You’ll notice that I choose the Configuration File Option in Tincr:</p>
<p><img src="http://s13.postimage.org/oop700vtv/Screen_Shot_2012_11_16_at_11_41_23_PM.jpg?noCache=1353130755" alt="'Configuration File option'" />.</p>
<p>This allows me to customize the mapping, ideally through regular expressions, between the project’s resource files and where they are located in my project. I do this mapping because I have had issues with it working simply with an http web server, though others might have better success with one of the other pre-configured options.</p>
<p>Nevertheless, here’s an example tincr.json file, which you need to put right under the web-app folder of your project.</p>
<div><script src='https://gist.github.com/4093607.js?file='></script>
<noscript><pre><code>{
"toFile" : [
{"from": "/js/(.+\\.js)",
"to": "/js/$1"},
{"from": "/css/(.+\\.css)",
"to": "/css/$1"}
]
}</code></pre></noscript></div>
<p>As you can see, this basic JSON simply maps js and css resources under the web-app folder, which I set as the Tincr ROOT folder in Chrome, to the project’s js/ and cs/ folders. It works recursively for those directories as well. You can of course get more fancy depending on how your project’s resources are defined.</p>
<p>One ‘gotcha’ to watch out for is resource bundling. To get this to work (at least without major pains), I turn resource bundling off in the Grails 2.x apps I use Tincr in by adding:</p>
<p>grails.resources.debug=true</p>
<p>in the development environment config section of my project’s Config.groovy file. While this adds additional parameters to my resource files, it breaks them out of Grails’ default bundling strategy and more easily allows Tincr to do its magic.</p>
<p>As for that magic, Tincr allows me to make changes to javascript or CSS files in the browser (in Chrome Developer Tools), use Cmd+S (save shortcut, this being the Mac version of a save shortcut), and have the changes be saved back to the file system (and viewable instantly in my IDE). On the reverse side, as you can see in the <a href="http://tin.cr/docs.html">Tincr documentation</a>, you can define a ‘fromFile’ JSON attribute, which will allow you to save a file in your favorite IDE and have Chrome bring in the changes <em>without</em> reloading the page in the browser. Luckily, in this simple configuration example, Tincr is smart enough to reverse-engineer the ‘fromFile’ mapping, so defining it is redundant, and I have therefore not done so.</p>
<p>So hopefully this provides you an impetus and a guide to start to “tinkering” (you knew it was coming…) with <a href="http://tin.cr/">Tincr</a> in your Grails application!</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2012/11/14/end-user-advantages-of-html5-apps-for-mobile-devices/">Four End User Advantages of HTML5 Apps for Mobile Devices</a></h1>
<p class="meta">
<time datetime="2012-11-14T22:45:00-06:00" pubdate data-updated="true">Nov 14<span>th</span>, 2012</time>
</p>
</header>
<div class="entry-content"><p>Rarely a week goes by where there is not another article about “HTML5 Mobile Webapps vs. Native Apps.” Like criticizing Apple, these articles are great at generating traffic (and money?) for the hosting website but often settle little and rarely provide much value for either audience. That being said, seemingly everyone has an opinion on these topics, so don’t expect the articles to end anytime soon. Before I take a stand on one side of the aisle - in order to rebutt points in a specific article I’ll mention shortly, I should preface this post by saying that I hate the ‘vs.’ argument of mobile apps - I believe they both have their time and place, and for enterprise customers I often think “both” is the correct answer. With that out of the way - the latest in the line of these ‘vs.’-style articles that was brought to my attention today was Jeffrey Sambells post: <a href="http://jeffreysambells.com/2012/11/14/on-building-html5-apps-for-mobile-devices">“On Building HTML5 Apps for Mobile Devices”</a>. While discussing the article point by point is <em>very tempting</em> - such as the incorrect summarization of Facebook’s current stance on HTML5 (they still get much more non-native mobile traffic than native, and HTML5 is still very much in play at Facebook) - the point in the article I want to address is:</p>
<ul>
<li>Where’s the end user advantages (for mobile web)?</li>
</ul>
<p>Well here they are - a list of 4 of the top “end user advantages” for mobile web applications:</p>
<ol>
<li>Mobile browsers crash less frequently than your native app. Users get pretty annoyed when apps crash.</li>
<li>Not everyone wants to download an app. ~30% of mobile users have <em>never</em> downloaded <em>any</em> app. If they don’t want to download your app, but want to use your product on their mobile device, having a mobile web app <em>is</em> your other option.</li>
<li>Some native applications will not work on your device - or don’t exist for your device. If you are using an older iOS version, or certain Android devices/OS’s (for example) - or are part of the <a href="https://twitter.com/search?q=%23wearethe3percent">#wearethe3percent Window Phone</a> crowd or 1 of the 30 people still using Blackberry devices, then mobile web apps are often your only option to reach these users.</li>
<li>Some use cases are better suited for mobile web applications. This Mashable article <a href="http://mashable.com/2012/06/06/mobile-site-mobile-app-infographic/">under the Content Usage section</a> does a decent job of summarizing such use cases. Additionally, people who are travelling, especially in slower bandwith areas, will often be able to more quickly access the information this type of information via a mobile web app.</li>
</ol>
<p>There are definitely points in Jeremy’s article that I very much agree with - including the mythical fallacy: “I can just generate a native app from my mobile webapp using product X and it’ll be great!” In the end, mobile and native aren’t going away anytime soon, and there are very strong arguments behind, and reasons for, leveraging each approach as part of an overall mobile strategy.</p>
</div>
</article>
<article>
<header>
<h1 class="entry-title"><a href="/blog/2012/11/12/log4javascript-local-storage-custom-appender/">Log4Javascript - Quick Intro and a LocalStorage Custom Appender</a></h1>
<p class="meta">
<time datetime="2012-11-12T20:52:00-06:00" pubdate data-updated="true">Nov 12<span>th</span>, 2012</time>
</p>
</header>
<div class="entry-content"><p>With the continuing shift to the client-side for more and more processing in today’s web applications, be they mobile-specific or not, effective logging of the running state of the client-side part of your application is critical. There are a bevy of different solutions to your client-side logging needs, and I present this customization of <a href="http://log4javascript.org/">Log4Javascript</a> as not an endorsement of <a href="http://log4javascript.org/">Log4Javascript</a> as any sort of holy grail - but it is inevitably a demonstration that it is a viable, customizable solution that you should consider if you are working on a project that has needs in this area.</p>
<p><a href="http://log4javascript.org/">Log4Javascript</a> comes with a collection of appenders and a grouping of logging levels that gives you the type of logging you’ve probably grown accustomed to in your server-side development efforts. Hop over to this JSFiddle and look at a sample Hello World type example that would output a log message to your <a href="http://jsfiddle.net/QpK4t/10/">Browser Console</a></p>
<p>Beyond having appenders that write to the console, out of the box, <a href="http://log4javascript.org/">Log4Javascript</a> includes appenders that write to popups, alert, and submit ajax requests to the server. Using any combination of them are pretty simple operations - they include different options, and can be combined to provide a flexible yet powerful logging strategy.</p>
<p>The proposed LocalStorageAppender I reference adds to this toolkit by providing the ability to store log messages in a browser’s LocalStorage, if available. This can be an effective way to store messages for later use, if needed. For example, if you get an error later in the running of your application, wouldn’t it be nice to upload a set of log messages that happened before the error on the client side, along with the actual error? And if you didn’t have an error, not to post anything?</p>
<p>For the proposed LocalStorageAppender, I leverage <a href="https://github.com/marcuswestin/store.js">Store.js</a>, a “stupid simple” micro javascript framework for interacting with LocalStorage. As with the introductory example earlier, let’s first look at the core, working code that logs to LocalStorage in JSFiddle. To see it working, check out in your browser development tools (Firebug/Chrome Developer Tools/etc) your LocalStorage resource pane to see the “Hello World” log message <a href="http://jsfiddle.net/QpK4t/12/">jsfiddle.jshell.net logging “Hello World” under a timestamped key</a></p>
<p>Let’s break down the ‘running example’ code a bit here.</p>
<div><script src='https://gist.github.com/4063813.js?file='></script>
<noscript><pre><code> LocalStorageAppender.prototype = new log4javascript.Appender();
LocalStorageAppender.prototype.layout = new log4javascript.NullLayout();
LocalStorageAppender.prototype.threshold = log4javascript.Level.DEBUG;</code></pre></noscript></div>
<p>Here we set up our appender object with some pretty self-explanatory functions that all log4javascript appenders need to implement.</p>
<p>The nuts and bolts of our appender is in the append method, so let’s look at that</p>
<div><script src='https://gist.github.com/4063826.js?file='></script>
<noscript><pre><code>LocalStorageAppender.prototype.append = function(loggingEvent) {
var appender = this;
var getFormattedMessage = function() {
var layout = appender.getLayout();
var formattedMessage = layout.format(loggingEvent);
if (layout.ignoresThrowable() && loggingEvent.exception) {
formattedMessage += loggingEvent.getThrowableStrRep();
}
return formattedMessage;
};
if (store.enabled) {
var formattedMessage = getFormattedMessage();
store.set(JSON.stringify(new Date().getTime()), formattedMessage);
if (loggingEvent.level == Level.FATAL) {
var allStoreMessages = store.getAll();
var logMessageStr = loggingEvent.messages[0] + " - previous javascript log messages:";
for(var prop in allStoreMessages) {
if(allStoreMessages.hasOwnProperty(prop)) {
var storeMessage = allStoreMessages[prop];
if (storeMessage instanceof Array) {
logMessageStr += storeMessage[0] + "\n";
} else {
logMessageStr += storeMessage + "\n";
}
}
}
$.ajax({
type: 'POST',
url: '/logging',
data: 'level=' + loggingEvent.level + '&message=' + logMessageStr,
async: true,
success: function(r){
store.clear();
}
});
}
}
};</code></pre></noscript></div>
<p>The getFormattedMessage function is the same as the one used for the BrowserConsoleAppender - nothing particularly special for our use case. You’ll see that I then use Store.js to see if the browser supports LocalStorage, and if it does I store the message in LocalStorage with a timestamped key. You could certainly be more fancy with how you determine what your ‘keys’ are but this assures them to be unique (obviously important) and provides a simple, (hopefully) understandable example.</p>
<p>You’ll then see an example use case where one could, if the loggingEvent has a level that meets a certain threshhold (you could also choose to log events that exceed a certain threshhold), one can get all messages, and format them into a string, separated by newlines, with the last log message presented in the beginning of the string, and a “log stack” of messages for all the messages scooped up from LocalStroage. Those messages are then posted to the server, and upon our success handler the LocalStorage store is cleared.</p>
<p>Additional items to think about when using this type of logging appender includes cleaning up the LocalStorage data you have added when it is no longer useful - for example, upon entry/exit of portions of your application.</p>
<p>I think using LocalStorage is a nice approach for local through production environment client-side logging as it provides a way to consistenly log the client portion of your application and selectively report back to the server when you have issues. It especially hits a nice sweet spot for mobile web applications where client-side code execution/handling can unexpectedly vary across OS’s and their various browsers.</p>
</div>
</article>
<div class="pagination">
<a class="prev" href="/blog/page/2/">← Older</a>
<a href="/blog/archives">Blog Archives</a>
</div>
</div>
<aside class="sidebar">
<section>
<h1>Recent Posts</h1>
<ul id="recent_posts">
<li class="post">
<a href="/blog/2014/02/12/starbucks-big-missed-opportunity/">Starbucks’ Big Missed Opportunity</a>
</li>
<li class="post">
<a href="/blog/2013/07/22/grails-event-push-real-world-notes/">Grails Event Push real-world notes</a>
</li>
<li class="post">
<a href="/blog/2013/07/01/a-sencha-dirty-store-records-quirk/">A Sencha Dirty Store Records Quirk</a>
</li>
<li class="post">
<a href="/blog/2013/04/02/updated-extjs4-mock-ajax-library-for-jasmine/">Updated ExtJs4 Mock Ajax Library for Jasmine</a>
</li>
<li class="post">
<a href="/blog/2013/03/27/extjs4-fun-not-really-with-styling-checkboxes-in-grids/">ExtJS4: Fun (not really) with styling checkboxes in grids</a>
</li>
</ul>
</section>
</aside>
</div>
</div>
<footer role="contentinfo"><p>
Copyright © 2014 - Zach Lendon -
<span class="credit">Powered by <a href="http://octopress.org">Octopress</a></span>
</p>
</footer>
<script type="text/javascript">
(function(){
var twitterWidgets = document.createElement('script');
twitterWidgets.type = 'text/javascript';
twitterWidgets.async = true;
twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
})();
</script>
</body>
</html>