WPF-Blogger.com [Complete Feed] http://www.wpf-blogger.com All news and information about Windows Presentation Foundation and Silverlight at one place. Montag, 6. Februar 2012 Marketplace now open in 5 new markets <p>I’m happy to report that the Windows Phone Marketplace is now officially open for business in 5 new markets around the world—Argentina, Indonesia, Malaysia, Peru, and the Philippines. We can’t wait to see all the great new apps you’ll create and publish for customers there.</p> <p><a href="http://windowsteamblog.com/windows_phone/b/wpdev/archive/2012/01/05/new-markets-for-windows-phone-developers.aspx">As I mentioned</a> a few weeks ago, if you’d like your existing apps to reach Windows Phone owners in these new places, just log into App Hub and select them from the list—or choose “worldwide” if you want your app to appear in all 41 of the markets Marketplace is now available in. (You’ll need to do this even if you’ve previously opted to make your app available globally.) <a href="http://create.msdn.com/en-us/news/cross-submit-apps">Here’s</a> a quick walkthrough.</p> <p>Just a reminder that apps submitted to the Marketplaces for Indonesia, Malaysia and China (which is accepting apps but not yet open to consumers) will be subject to additional requirements due to local laws, regulations, or norms. For more details, see the <a href="http://msdn.microsoft.com/en-us/library/hh184842(v=VS.92).aspx">Application Certification Requirements</a>. If your app fails certification because of these additional requirements, you’ll need to update and resubmit it, or uncheck the submission boxes for those markets.</p> <p>That’s it for now. </p><div style="clear:both;"></div><img src="http://windowsteamblog.com/aggbug.aspx?PostID=575519" width="1" height="1"> http://windowsteamblog.com/windows_phone/b/wpdev/archive/2012/02/03/marketplace-now-open-in-5-new-markets.aspx Windows Phone Dev Blog 3579 2012-02-03T18:02:28 Complimentary Copies <p> When an author gets a book published, the publisher generally gives the author 10 complimentary copies of the book. Usually the author signs these books and gives them to all his friends, but then he has 7 or 8 copies still left over, and these tend to gather dust on the bookshelves of the author's home. </p><p>... more ...</p> http://www.charlespetzold.com/blog/2012/01/Complimentary-Copies.html Charles Petzold 3578 2012-02-02T18:00:00 [WP7] Usando los servicios de Live Preparando una idea para un juego que tengo entre manos se me ha ocurrido que podría intentar conectar con los servicios que ofrece Live para ofrecer una experiencia mas social en el juego a parte de lo que es el propio juego. El SDK de Live nos permite hacer uso de Skydrive, los contactos y el [...] http://labloguera.net/cesar/2012/02/02/wp7-usando-los-servicios-de-live/ llamo César Reneses [MVP] 3577 2012-02-02T01:20:22 Windows 8 Touch Events Interactions <p> During the week in September that I was attending the Microsoft Build conference and learning all about Windows 8, only one topic made me gloomy: This was the Windows 8 implemention of the <i>Manipulation</i> events. These events provide a high-level application programming interface to touch input, so obviously they are very important. </p><p>... more ...</p> http://www.charlespetzold.com/blog/2012/02/Windows-8-Touch-Events-Interactions.html Charles Petzold 3576 2012-02-01T13:00:00 Cell·SDK: El mismo código en Windows Phone, iOS y Android ¿Es posible ejecutar el mismo juego o aplicación en diferentes dispositivos móviles? La respuesta es: ahora sí. Me llena de orgullo y satisfacción anunciaros el lanzamiento del proyecto en el que hemos estado trabajando desde hace ya un tiempo en Syderis Technologies: Cell·SDK Podéis descargarlo de manera totalmente gratuita y empezar a probarlo. Os cuento, <a href='http://speakingin.net/2012/02/01/cell%c2%b7sdk-el-mismo-codigo-en-windows-phone-ios-y-android/'>[...]</a> http://speakingin.net/2012/02/01/cell·sdk-el-mismo-codigo-en-windows-phone-ios-y-android/ Juan María Laó Ramos 3575 2012-02-01T13:07:59 Event Handler Memory Leaks, Unwiring Events, and the WeakEventManager in WPF 4.5 <p>Many developers don't realize that a common source of memory leaks in .NET applications is the event handler. WPF 4.5 includes built-in support for weak events to help us do the right thing and not eat up all the available memory.</p> <h3>The Problem</h3> <p>Events are ubiquitous in .NET. When you add an event handler using the <em>EventName += &lt;my event handler delegate&gt;</em> syntax, the event source acquires a reference to the listener. This situation is a circular reference, as the listener had a reference to the source to begin with.</p> <p>These references are "strong references". That is, they are actual pointers to resources.</p> <p><img src="http://10rem.net/media/83227/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_9.png" width="650" height="151" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></p> <p>In the .NET framework, circular references aren't usually a problem. If the set of objects can't be traced back to the root object in the application, they are garbage collected. That's great, but in this case, due to the references from the event source to the event listener, as long as the event source is reachable, the listener will also stay around, as the event source can be traced back to a valid object root.</p> <p>This can be a real problem if the event source is long-lived, as is the case with things like application configuration objects, or other static or singleton objects. Event handlers are often on the UI side of things, so the listener can be a resource and memory heavy Window object.</p> <p>Take this scenario: Over the course of an application's lifetime, the user opens and closes document windows. The document windows each have a reference to some other object: it could be the parent window, it could be a static configuration class, it could be a shared singleton data provider somewhere up the chain. The important thing is that the windows are listening to an event from that object: CollectionChanged, some custom DataLoaded or other event.</p> <p><img src="http://10rem.net/media/83232/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_17.png" width="650" height="228" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></p> <p>Now, when the parent window (and your code) releases all reference to the window instance, the event source still has a reference to them. And, because the event source also has a reference from the application (implicit if it's static - man that lifetime gets ugly), the windows stay around:</p> <p><img src="http://10rem.net/media/83237/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_20.png" width="650" height="228" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></p> <p>No GC or disposal for those window instances. Windows tend to have a lot of resources tied up just to paint (control templates, controles themselves, data even) so they're not cheap resources. It gets even nastier, though. If those instances have references to, say, individual ViewModel instances, and those ViewModels have references to a cache of data, you can end up with pretty enormous memory leaks. Open up enough windows and, eventually, your app will run out of resources.</p> <h3>Basis for Comparison</h3> <p>Before we look at the problem, here's a profile of the application opening 200 windows, closing the 200 windows, and then forcing a GC. I did this three and then closed the app. Each window allocates a bunch of memory in an array just to give it a payload. The window code looks like this:</p> <pre class="brush: csharp;"> public partial class TestChildWindow : Window<br /> {<br /> long[] _memoryHog;<br /> <br /> public TestChildWindow()<br /> {<br /> InitializeComponent();<br /> <br /> Random rnd = new Random();<br /> <br /> string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";<br /> NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));<br /> <br /> _memoryHog = new long[1024];<br /> <br /> Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);<br /> }<br /> <br /> void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)<br /> {<br /> }<br /> } </pre> <p>&nbsp;</p> <p>In the upcoming examples, the TestChildWindow will have a reference to the SettingsManager. This is a stub class meant to represent pretty much any long-lived instance in your application.</p> <pre class="brush: csharp;"> namespace WpfWeakEventManager<br /> {<br /> class SettingsManager<br /> {<br /> public event EventHandler SettingsChanged;<br /> <br /> private static SettingsManager _current;<br /> public static SettingsManager Current<br /> {<br /> get<br /> {<br /> if (_current == null)<br /> _current = new SettingsManager();<br /> <br /> return _current;<br /> }<br /> }<br /> }<br /> }<br /> </pre> <p>Finally, the Main Window has three buttons that I used to test the application. The first opens 200 windows. The second closes all windows (except the main one) being tracked by the application. The third forces a GC collection. It also has a listbox to show diagnostic information when I'm not running with an attached debugger.</p> <pre class="brush: csharp;"> namespace WpfWeakEventManager<br /> {<br /> /// &lt;summary&gt;<br /> /// Interaction logic for MainWindow.xaml<br /> /// &lt;/summary&gt;<br /> public partial class MainWindow : Window<br /> {<br /> private SettingsManager _current;<br /> <br /> public MainWindow()<br /> {<br /> InitializeComponent();<br /> <br /> // make sure SettingsManager is referenced outside<br /> // just the child windows. Normally this would happen<br /> // just do to general use of the class. Forcing it here.<br /> <br /> _current = SettingsManager.Current;<br /> }<br /> <br /> private void OpenWindowsButton_Click(object sender, RoutedEventArgs e)<br /> {<br /> int openedCount = 0;<br /> DebugList.Items.Add("Total Memory Before Opening Windows: " + GC.GetTotalMemory(true));<br /> <br /> for (int i = 0; i &lt; 200; i++)<br /> {<br /> var window = new TestChildWindow();<br /> window.Show();<br /> openedCount++;<br /> }<br /> <br /> DebugList.Items.Add("Opened " + openedCount + " windows.");<br /> DebugList.Items.Add("Total Memory After Opening Windows: " + GC.GetTotalMemory(true));<br /> }<br /> <br /> private void CloseWindowsButton_Click(object sender, RoutedEventArgs e)<br /> {<br /> int closedCount = 0;<br /> foreach (Window window in Application.Current.Windows)<br /> {<br /> if (window != this)<br /> {<br /> window.Close();<br /> closedCount++;<br /> }<br /> }<br /> <br /> DebugList.Items.Add("Closed " + closedCount + " windows.");<br /> DebugList.Items.Add("App has " + Application.Current.Windows.Count + " window(s) it's holding on to.");<br /> }<br /> <br /> private void CollectButton_Click(object sender, RoutedEventArgs e)<br /> {<br /> GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);<br /> //GC.Collect();<br /> }<br /> }<br /> } </pre> <p>I experimented with a few variations on GC.Collect and some of the other notifications before the code that is here. A plain old GC.Collection will likely work just as well in any case.</p> <p>Here's what the window looks like after I opened 200 windows and then closed them and then opened another 200. <strong>200</strong> windows is actually a lot of windows, as it turns out, but if you consider an application where your user opens up 1 customer record every 5 minutes (for a phone support person, this is conservative), over the course of an 8 hour day, that could be as many as 160 forms, and that's assuming they close the application every day when they leave.</p> <p><a href="http://10rem.net/media/83242/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_32.png" target="_blank"><img src="http://10rem.net/media/83247/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_thumb_12.png" width="650" height="360" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>Now I needed to find a good way to track memory usage. The profiler in Visual Studio is really good for finding performance bottlenecks, but not really meant for the real-time memory usage graphing I wanted to do. So, I downloaded the 10 day trial of .NET Memory Profiler (an app I used a lot back when I was consulting) and had it capture data while I performed the above actions.</p> <p><a href="http://10rem.net/media/83252/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_22.png" target="_blank"><img src="http://10rem.net/media/83257/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_thumb_7.png" width="660" height="254" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>In this graph, the pink line is the active memory in use in bytes, the green line is the number of root references. (note: none of the memory graphs include memory from on-window images. I ran without the cute images, but I left the code in the form and the images in the screenshot just because)</p> <p>Remember, this is without any of the circular reference problems discussed so far. Now, let's take a look at the same actions when I have an event handler wired up in the constructor of the window.</p> <p><a href="http://10rem.net/media/83262/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_24.png" target="_blank"><img src="http://10rem.net/media/83267/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_thumb_8.png" width="660" height="253" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>Notice something about that graph? Yep. Memory just isn't being returned. It's leaking, and the root references keep on climbing. The only difference between this profiled version and the previous was that I added the SettingsManager event handler wireup in the constructor:</p> <pre class="brush: csharp;"> public partial class TestChildWindow : Window<br /> {<br /> long[] _memoryHog;<br /> <br /> public TestChildWindow()<br /> {<br /> InitializeComponent();<br /> <br /> Random rnd = new Random();<br /> <br /> string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";<br /> NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));<br /> <br /> _memoryHog = new long[1024];<br /> <br /> SettingsManager.Current.SettingsChanged += Current_SettingsChanged;<br /> <br /> Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);<br /> }<br /> <br /> void Current_SettingsChanged(object sender, EventArgs e)<br /> {<br /> // do something<br /> }<br /> <br /> void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)<br /> {<br /> }<br /> } </pre> <p>The Unloaded handler is empty, but it is wired up. The key change here is the SettingsManager.Current.SettingsChanged wire-up.</p> <p>There are a multitude of considerations in this example, but let's deal with just the topic of this post: the event handlers.</p> <h3>Solution 1: Unwire Your Event Handlers</h3> <p>This generally doesn't work if you use anonymous delegates (one reason to be careful about using them with objects that live outside the scope of a single method), but in cases where you have a real event handler method, unhooking them when you're done with them is definitely a best practice.</p> <p>You can remove event handlers in Dispose methods, or in the case of a window, in the Unload event handler. That's what I did here.&nbsp; The code is a simple change:</p> <pre class="brush: csharp;"> void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)<br /> {<br /> SettingsManager.Current.SettingsChanged -= Current_SettingsChanged;<br /> } </pre> <p>All I did was remove the event handler inside the Unloaded handler. Take a look at the impact it had on memory:</p> <p><a href="http://10rem.net/media/83272/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_26.png" target="_blank"><img src="http://10rem.net/media/83277/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_thumb_9.png" width="660" height="254" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>That's much better. The root references and memory both drop when I hit the "collect" button. Compare this to the previous example where the memory never went down. As to the differences between this graph and the first one, chalk it up to sample timing and my own non-deterministic button pushing.</p> <p>So, that's a great approach. In fact, if you can do this, you should do this. It's simple and highly effective.</p> <p>For other situations, perhaps where you don't control all the code, or when unwiring events is difficult or inconvenient, there's the Weak Event Pattern.</p> <h3>Solution 2: Weak Events</h3> <p>It's not always possible or practical to unwire event handlers. <strong>Actually, let me rephrase that</strong>: sometimes the level of effort and thought that needs to go into *where* you put event handler unwiring is more than developers have time to put into the app. :)</p> <p>Enter the Weak Event Pattern</p> <p>The concept of a Weak Event is nothing new. Many others have created their own various weak event solutions for use with WPF. Previous versions of .NET and WPF included the WeakEventManager, but required you to derive your own class and implement IWeakEventListener interfaces. That was a fair bit of work for developers who just need to get stuff done. The real news here is that WPF 4.5 includes the <strong>generic</strong> WeakEventManager type to make it easier for you to implement the weak event pattern in your own code, without all the other ceremony. You can still derive from WeakEventManager (if you can do the work, it's actually a good practice due to the performance benefits you'll get from it via having actual events and not reflection look-ups), but the new generic class makes it easy enough so that there's no excuse *not* to do it.</p> <p>Here's the memory usage of the application using Weak Events</p> <p><a href="http://10rem.net/media/83282/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_30.png" target="_blank"><img src="http://10rem.net/media/83287/Windows-Live-Writer_WeakEventManager-in-WPF-4.5_EF5D_image_thumb_11.png" width="660" height="254" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>And here's the code change. I removed the code to unwire the event, and did the event wireup using WeakEventManager rather than regular old events.</p> <pre class="brush: csharp;"> namespace WpfWeakEventManager<br /> {<br /> /// &lt;summary&gt;<br /> /// Interaction logic for TestChildWindow.xaml<br /> /// &lt;/summary&gt;<br /> public partial class TestChildWindow : Window<br /> {<br /> long[] _memoryHog;<br /> <br /> public TestChildWindow()<br /> {<br /> InitializeComponent();<br /> <br /> Random rnd = new Random();<br /> <br /> string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";<br /> NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));<br /> <br /> _memoryHog = new long[1024];<br /> <br /> // this version wires up an event handler which causes a hard reference<br /> // and, unless it is unwired, a memory leak<br /> // SettingsManager.Current.SettingsChanged += Current_SettingsChanged;<br /> <br /> // unwire the event handler. This is a good practice for avoiding leaks<br /> // Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);<br /> <br /> // this version avoids memory leaks without requiring you to<br /> // unwire the event.<br /> WeakEventManager&lt;SettingsManager, EventArgs&gt;<br /> .AddHandler(SettingsManager.Current,<br /> "SettingsChanged",<br /> Current_SettingsChanged);<br /> }<br /> <br /> void Current_SettingsChanged(object sender, EventArgs e)<br /> {<br /> // do something<br /> }<br /> <br /> void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)<br /> {<br /> // SettingsManager.Current.SettingsChanged -= Current_SettingsChanged;<br /> }<br /> }<br /> }<br /> </pre> <p>This new WeakEventManager is much easier to use than anything we had before. You take a hit for relying on reflection to resolve the event name, but in all but the most performance-sensitive cases (where you probably wouldn't bother with events anyway), it's going to be worth it.</p> <h3>Summary</h3> <p>Event handlers and long-lived objects can be the source of memory leaks in any .NET application. Whenever you can, you should unwire your event handlers to help avoid these leaks. When that is impractical, the Weak Event Pattern using the WPF 4.5 WeakEventManager&lt;TObject, TArgs&gt; is a great way to help save you from the grief of memory leaks without requiring all the ceremony we had with the regular WeakEventManager and IWeakEventListener in the previous versions.</p> <p>Now there's no excuse to have leaky event handlers.</p> <p><a href="http://feedads.g.doubleclick.net/~a/vWEdpAaAZUJIGK0nCSdtRm-jICo/0/da"><img src="http://feedads.g.doubleclick.net/~a/vWEdpAaAZUJIGK0nCSdtRm-jICo/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/vWEdpAaAZUJIGK0nCSdtRm-jICo/1/da"><img src="http://feedads.g.doubleclick.net/~a/vWEdpAaAZUJIGK0nCSdtRm-jICo/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cNCfpxx_N5Y:OFmCXGG8D3g:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cNCfpxx_N5Y:OFmCXGG8D3g:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cNCfpxx_N5Y:OFmCXGG8D3g:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cNCfpxx_N5Y:OFmCXGG8D3g:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/cNCfpxx_N5Y" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/cNCfpxx_N5Y/event-handler-memory-leaks-unwiring-events-and-the-weakeventmanager-in-wpf-45 Pete Brown 3574 2012-02-01T05:27:14 12 Horas de Visual Studio - Exprime las vistas en ASP.NET MVC <p>Este es el material que voy a utilizar sobre mi charla sobre &ldquo;Exprime las vista en ASP.NET MVC&rdquo;</p> <p><a href="http://www.luisguerrero.net/downloads/mcvhtml5.pptx"><img style="background-image:none;border-right-width:0px;padding-left:0px;padding-right:0px;display:block;float:none;border-top-width:0px;border-bottom-width:0px;margin-left:auto;border-left-width:0px;margin-right:auto;padding-top:0px;" title="clip_image002" border="0" alt="clip_image002" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/luisguerrero/clip_5F00_image002_5F00_1AEF13FC.jpg" width="244" height="184" /></a></p> <p>El c&oacute;digo de ejemplo os lo pod&eacute;is descargar de aqu&iacute;, <a href="http://bit.ly/12HorasVSWindowsPhone">http://bit.ly/12HorasMVCCode</a></p><div style="clear:both;"></div><img src="http://geeks.ms/aggbug.aspx?PostID=203094" width="1" height="1"><img src="http://feeds.feedburner.com/~r/LuisGuerreroGeeks/~4/idkQoclac_w" height="1" width="1"/> http://feedproxy.google.com/~r/LuisGuerreroGeeks/~3/idkQoclac_w/12-horas-de-visual-studio-exprime-las-vistas-en-asp-net-mvc.aspx Luis Guerrero 3573 2012-01-31T19:57:00 Aliasing en Texturas Nueva entrega de la serie sobre técnicas de antialiasing,  esta vez en DesarrolloWeb.com. Espero que os sirva http://speakingin.net/2012/01/31/aliasing-en-texturas/ Juan María Laó Ramos 3572 2012-01-31T20:30:32 Memory Profiling for Application Performance <p>Performance is a concern for all but the most trivial of applications, and in a constrained environment like the phone it is all the more so; performance issues directly manifest themselves in the user experience. Memory usage is one such source of performance issues that can degrade user experience, and the Windows Phone Marketplace enforces a <a href="http://msdn.microsoft.com/en-us/library/hh184840(v=VS.92).aspx">technical certification requirement</a> that an application must not exceed 90 MB of RAM usage, except on devices that have more than 256 MB of memory.</p> <p>What could cause 90 MB of usage in a garbage-collected runtime? There could be several causes:</p> <ul> <li>Allocating too much.</li> <li>Long living objects that bloat working set.</li> <li>Holding on to references unintentionally leading to memory leaks.</li> </ul> <p>Applications allocating too much could end up with working sets breaching this threshold; applications with a large working set might eventually exit with an “Out of Memory” exception; and holding on to references unintentionally can lead to memory leaks causing working set to increase steadily and stealthily, eventually leading to the application exiting with an “Out of Memory” exception.</p> <p>Up until the Windows Phone SDK 7.1 release, application developers had to guard against this by programmatically instrumenting their code with specific framework method calls and track memory usage. These methods included the following:</p> <ul> <li><a href="http://msdn.microsoft.com/en-us/library/system.gc.gettotalmemory(v=vs.95).aspx">System.GC.GetTotalMemory</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/microsoft.phone.info.devicestatus.devicetotalmemory(v=VS.92).aspx">Microsoft.Phone.Info.DeviceExtendedProperties.DeviceTotalMemory</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/microsoft.phone.info.devicestatus.applicationcurrentmemoryusage(v=VS.92).aspx">Microsoft.Phone.Info.DeviceExtendedProperties.ApplicationCurrentMemoryUsage</a></li> <li><a href="http://msdn.microsoft.com/en-us/library/microsoft.phone.info.devicestatus.applicationpeakmemoryusage(v=VS.92).aspx">Microsoft.Phone.Info.DeviceExtendedProperties.ApplicationPeakMemoryUsage</a></li> </ul> <p>But beyond that there was little help from the tools.</p> <p>The <a href="http://www.microsoft.com/download/en/details.aspx?displaylang=en&amp;id=27570">Windows Phone SDK 7.1</a> addresses this through the introduction to <a href="http://msdn.microsoft.com/en-us/library/hh202934(v=VS.92).aspx">profiling tools</a> to analyze the execution, visual, and memory characteristics of an application.</p> <p>Install the SDK now, and let us walk through the Memory Profiler as we investigate a case of a memory leak caused by unintentionally holding on to references.</p> <h1>A Sample Application</h1> <p>Consider this <a href="http://windowsteamblog.com/windows/m/files/575359/download.aspx">application</a> that has two pages, and where the second page renders several images within a grid, and where one may navigate between pages.</p> <p>Page2 has 4 images controls within a grid control that is <i>bitmapcached</i>. During the initialization of Page2, it binds the 4 image controls to jpeg images and registers for an event handler.</p> <p>The two pages look as follows:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/3808.image_5F00_2FCA31C4.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2376.image_5F00_thumb_5F00_034101DE.png" width="154" height="254" /></a>&#160;&#160;&#160; <a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/6457.image_5F00_2CD4CD11.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/4375.image_5F00_thumb_5F00_062640C4.png" width="154" height="254" /></a></p> <p>As we try out the application going back and forth between the pages a few times, observe that memory usage keeps going up; there is a memory leak.</p> <h1>Visualizing the leak</h1> <p>To delve deeper into this leak further let us run this application through the Memory Profiler using the following steps:</p> <ol> <li>Invoke the Profiler, choose “Memory (managed object allocation and texture usage)”, and launch the application. Note the memory used by the application at the top of the screen.</li> <li>Click on Go To Page2. This will launch Page2, and display the 4 flower images.</li> <li>Click on the back button.</li> <li>Click on ‘Force GC’. Note the memory used by the application at the top of the screen. Observe that it does not go down.</li> <li>Repeat steps 2, 3, 4 four more times.</li> <li>Now click on ‘Stop Profiling’.</li> </ol> <p>We should see a graph like the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/5722.image_5F00_6EAB8C92.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/4617.image_5F00_thumb_5F00_6C225AD4.png" width="508" height="365" /></a></p> <p>As expected memory usage is steadily growing.</p> <h1>Analysis</h1> <p>Select a range, from before the start of the first plateau to just after the after the start of the last plateau, for analysis. Once analysis is complete, using <b>Performance Warnings</b> as the starting point, navigate to the <b>Heap Summary </b>view. We should see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/5187.image_5F00_0294F95C.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/8524.image_5F00_thumb_5F00_74760A5E.png" width="508" height="365" /></a></p> <p>Note that the memory held by <b>Retained Silverlight Visuals at End </b>is large as compared to that held by just managed objects. So, let’s drill down into these retained Silverlight visuals.</p> <p>Select <b>Retained Silverlight Visuals at End </b>and navigate to the <b>Types </b>view. In the <b>Types </b>view, sort the <b>Total Size (Bytes) </b>column in descending order. We should now see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/6355.image_5F00_119BB269.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/1055.image_5F00_thumb_5F00_676C0480.png" width="508" height="365" /></a></p> <p>Note that Grid and Page2 are the heavy consumers. We also notice right away that 5 instances of Page2 are hanging around. In our scenario, we navigated 5 times to Page2, remember?</p> <p>Let’s look at what those 10 instances of the Grid are: click on the Grid and navigate to the <b>Instances </b>view. We should now see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2844.image_5F00_0B44B60E.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2742.image_5F00_thumb_5F00_20DEEEAB.png" width="508" height="365" /></a></p> <p>Note that 5 of the instances are being allocated from Page2. Click on the hyperlink and you can go the line of code in XAML where the Grid is allocated.</p> <p>To see what is the memory being retained by the visual tree rooted at a Grid instance, click on, say, the grid with the ID 33 (this is a grid allocated from Page2), and navigate to the <b>Visual Tree </b>view. We should now a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/7180.image_5F00_249C937B.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/8880.image_5F00_thumb_5F00_48754508.png" width="508" height="365" /></a></p> <p>Note the visual tree rooted at that grid instance. We can see the 4 contained image controls, as well as the Texture memory associated with the grid.<b></b></p> <p>To cross check the association between the grid instance and Page2, we can go back to the Types view, select Page2, and navigate to the <b>Instances </b>view. We should then see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2818.image_5F00_2564D398.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/1715.image_5F00_thumb_5F00_226F6EE5.png" width="508" height="365" /></a></p> <p>Notice that there are 5 instances created and none of them has been destroyed (i.e. garbage collected).</p> <p>Now let’s look at the Visual Tree rooted at a Page2 instance. Select the first instance, and navigate to the <b>Visual Tree</b>. We should see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/8535.image_5F00_718CB779.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/4718.image_5F00_thumb_5F00_0EB25F84.png" width="508" height="365" /></a></p> <p>Again, we see the grid, its associated texture memory, and its contained image controls. Earlier we saw that 5 instances of Page2 were hanging around. That leak was causing the associated grid, images, and texture memory to not get cleaned up.</p> <p>Now let us see why the Page2 instances were hanging around.</p> <p>Go back to the <b>Instances </b>view, select the first Page2 instance, and navigate to the <b>GC Roots </b>view. Collapse the tree structures. We should see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/7824.image_5F00_44D3D7D3.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/7536.image_5F00_thumb_5F00_61F97FDD.png" width="508" height="365" /></a></p> <p>Ignore the weak references (these do not prevent the GC from acting on the references). But look at the TouchFrameEventHandler; expand it and we should see a table similar to the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/4466.image_5F00_7F1F27E7.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/3276.image_5F00_thumb_5F00_4A3222AA.png" width="508" height="365" /></a></p> <p>There are 5 chained event handlers! The first one was allocated from our assembly. The rest were cloned within mscorlib, as each page appended its own TouchFrameEventHandler (the event handler is implemented as a .Net Delegate type). That event handler is holding a reference to the page that in turn is preventing the page from being garbage collected.</p> <p>So the eventhandlers references the Page2 instances, preventing it from being GC’d; and thus assets contained in Page2 are not cleaned up (the grid, associated texture memory, images).</p> <p>Solved!</p> <p>All that remains to be done is to find where that handler is being registered in our assembly.</p> <p>Go back to <b>Heap Summary</b>, select <b>Retained Allocations at End</b>, navigate to the <b>Types </b>view, sort by <b>Type Name</b> column, and scroll down to locate the TouchFrameEventHandler. We should see the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2117.image_5F00_550F03F2.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/1832.image_5F00_thumb_5F00_7CF20351.png" width="508" height="365" /></a></p> <p>To find out where that 1 instance was allocated in our application code, select the first entry, and navigate to the <b>Methods </b>view. You should see the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/1738.image_5F00_193F4572.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/2781.image_5F00_thumb_5F00_2B3B6332.png" width="508" height="365" /></a></p> <p>Click on the <b>Allocating Method </b>hyperlink, and you will jump to the method that did the allocation. You should see the following:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/3582.image_5F00_13547C0C.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/5102.image_5F00_thumb_5F00_57483A56.png" width="508" height="365" /></a></p> <p>There, right at the bottom is the place where we register to the eventhandler! The step of registering stashes away a strong reference to this instance of the page within the event handler delegate. Unless the user unregisters this handler, the Page2 instance will remain alive (and consequently so too will the grid and the images, and the texture memory will not get cleaned up).</p> <p>If you comment out the line that enregisters the handler, and rerun this scenario, here is the graph you would see:</p> <p><a href="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/6165.image_5F00_3B57055E.png"><img style="border: 0px currentcolor; padding-top: 0px; padding-right: 0px; padding-left: 0px; display: inline; background-image: none;" title="image" border="0" alt="image" src="http://windowsteamblog.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-00-53-84-metablogapi/4426.image_5F00_thumb_5F00_664EF363.png" width="508" height="365" /></a></p> <p>Voilà!</p> <h1>Conclusion</h1> <p>Having a garbage collected runtime removes one of the biggest sources of program errors, memory allocation errors; you no longer have to worry about freeing memory which you no longer use. But leaks spring when you unintentionally hold on to object references that you no longer use. By providing a variety of Views on the applications memory usage the Memory Profiler can be a valuable tool to detect and fix such leaks.</p><div style="clear:both;"></div><img src="http://windowsteamblog.com/aggbug.aspx?PostID=575395" width="1" height="1"> http://windowsteamblog.com/windows_phone/b/wpdev/archive/2012/02/01/memory-profiling-for-application-performance.aspx Windows Phone Dev Blog 3571 2012-01-31T19:50:33 12 Horas de Visual Studio - Calidad de Software y patrones de diseño en Windows Phone 7.5 <p>Hoy es el evento de 12 Horas de Visual Studio de Microsoft y Globbtv, podeis ver el evento en directo aqu&iacute; <a href="http://www.globbtv.com/vstudio12horas/">http://www.globbtv.com/vstudio12horas/</a></p> <p>Este es el material que voy a utilizar sobre mi charla sobre &ldquo;Calidad de Software y patrones de dise&ntilde;o de Windows Phone 7.5&rdquo;</p> <p><a href="http://www.luisguerrero.net/downloads/12HorasVSWindowsPhone7.pptx"><img height="174" width="231" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/luisguerrero/clip_5F00_image002_5F00_02414C53.jpg" alt="clip_image002" border="0" title="clip_image002" style="background-image:none;border-right-width:0px;padding-left:0px;padding-right:0px;display:block;float:none;border-top-width:0px;border-bottom-width:0px;margin-left:auto;border-left-width:0px;margin-right:auto;padding-top:0px;" /></a></p> <p>El c&oacute;digo de ejemplo os lo pod&eacute;is descargar de aqu&iacute;, <a href="http://bit.ly/12HorasVSWindowsPhone">http://bit.ly/12HorasVSWindowsPhone</a></p><div style="clear:both;"></div><img src="http://geeks.ms/aggbug.aspx?PostID=203089" width="1" height="1"><img src="http://feeds.feedburner.com/~r/LuisGuerreroGeeks/~4/nRC_4OIFbWE" height="1" width="1"/> http://feedproxy.google.com/~r/LuisGuerreroGeeks/~3/nRC_4OIFbWE/12-horas-de-visual-studio-calidad-de-software-y-patrones-de-dise-241-o-en-windows-phone-7-5.aspx Luis Guerrero 3570 2012-01-31T15:56:00 Yet Another Podcast #58–Julie Lerman and EF/CF http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/KlUXYu6MokM/ Jesse Liberty [MS] 3569 2012-01-31T17:34:48 Happy 75th Birthday, Philip Glass! <p> About 14 months ago I saw the New York premiere of Philip Glass's <i>Concerto for Violin and Orchestra No.2</i> at Carnegie Hall performed by violinist Robert McDuffie and the Venice Baroque Orchestra. McDuffie had commissioned the Glass work (subtitled "The American Four Seasons") for a program that also included the four violin concertos collected by Antonio Vivaldi as <i>The Four Seasons</i> almost three centuries earlier, and McDuffie took the program on a tour of 30 American cities. </p><p>... more ...</p> http://www.charlespetzold.com/blog/2012/01/Happy-75th-Birthday-Philip-Glass.html Charles Petzold 3568 2012-01-31T11:00:00 Jetzt Backbone.js lernen: Konzept WPF-Blogger <p>Bevor es so richtig mit <a title="Backbone.js" href="http://backbonejs.org/" target="_blank">Backbone.js</a> los geht, möchte ich das neue Konzept von <a title="WPF-Blogger" href="http://wpf-blogger.com" target="_blank">WPF-Blogger</a> vorstellen, die grundlegenden Features beschreiben und über den generellen Aufbau als solchen berichten. Es sind zahlreiche Änderungen angedacht. Aus dem Feed-Aggregat soll eine Site mit weit mehr Benutzer-Interaktion entstehen. Einige Funktionalitäten stellen jedoch auch lediglich Überlegungen dar, die ich gerne zur Diskussion freigeben möchte. Aber mehr in dieser Vorstellung.</p> <h2>Status Quo</h2> <p><a title="WPF-Blogger" href="http://wpf-blogger.com" target="_blank">WPF-Blogger</a> war ursprünglich als reines Feed-Aggregat konzipiert. Ziel war es, Feeds zum Thema WPF zu bündeln und Interessierten gesammelt zur Verfügung zu stellen. Folgende Features wurden in der ersten Version inkludiert:</p> <ul> <li>Feed-Aggregation von ausgesuchten Blog-Feeds </li> <li>Mehrsprachigkeit </li> <li>Registrierung von neuen Feeds </li> <li>Übersicht aller registrierten Feeds </li> <li>Archiv inklusive Suche </li> <li>Gesamtfeed und Sprachfeeds </li> <li>Statistiken </li> </ul> <p>Umgesetzt wurde die Website mit ASP.NET und WebForms und ganz, ganz wenig JavaScript.</p> <p><a title="WPF-Blogger" href="http://wpf-blogger.com" target="_blank"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://devtyr.norberteder.com/image.axd?picture=image_127.png" width="604" height="329" /></a></p> <h3>Feed-Aggregat</h3> <p>Für das Aggregieren der Feeds läuft ein eigener Job, der zeitgesteuert sämtliche Feeds hin auf Neuigkeiten prüft bzw. bereits importierte Artikel aktualisiert. Dieser bleibt durch die Neuentwicklung der Website unberührt, da alle wesentlichen Funktionen enthalten sind.</p> <h2>Quo vadis?</h2> <p>Das ursprüngliche “Konzept” war hauptsächlich auf mich persönlich zugeschnitten. Es sollte eine Stelle sein, an der ich alle interessanten Blogs zum Thema WPF sammeln kann. Da dies auch für andere von Interesse sein konnte, hatte ich mich damals kurzer Hand entschlossen, eine eigene Website online zu stellen.</p> <p>Im Laufe der Zeit hat sich gezeigt, dass das Interesse für diese Site da ist, jedoch fehlt vielen (auch mir) die Möglichkeit selbst Informationen einzustellen bzw. gute Inhalte zu teilen - kennt man schließlich auch von anderen Webangeboten. So das Feedback.</p> <p>Nun gut, aber wie sieht es um den geplanten Funktionen aus?</p> <ul> <li><strong>Login über Drittanbieter</strong>. In der ersten Implementierungsphase wird Twitter implementiert. Als Erweiterung sollen Facebook und Google Plus ebenfalls angeboten werden, um möglichst wenige auszuschließen. Gewisse Funktionalitäten sind nur im eingeloggten Zustand möglich. </li> <li><strong>Suche</strong>. Eine Suche war bis dato über das Archiv bereits möglich. Dies wird weiterhin möglich sein, soll aber präsenter platziert werden. </li> <li><strong>Sprachauswahl</strong>. Wie bisher soll auch die Sprachauswahl weiter bestehen bleiben. Durch diese wird bestimmt, aus welcher Sprache Ergebnisse anzuzeigen sind. Die generelle Sprache der Website ergibt sich durch die Browsersprache. </li> <li><strong>Sharing Content</strong>. Es soll möglich sein, einzelne Artikel über unterschiedliche Kanäle zu verteilen, um guten Content bestmöglich verteilen zu können. Angeboten sollen Twitter, Facebook und Google Plus werden. Zusätzlich soll es möglich sein, Artikel für das spätere Lesen zu markieren. Dies kann wahlweise in der Applikation selbst (man muss angemeldet sein) oder über die Anbindung von Drittanbietern (Read It Later etc.) geschehen. </li> <li><strong>Statistiken</strong>. Diese sollen helfen, gute und interessante Inhalte schnell aufzufinden. Diese beziehen sich auf Topics, Autor und Aufrufen. </li> </ul> <p>Nachfolgend findet sich ein Screen-Mock, der den Aufbau im ausgeloggten Zustand näher beschreibt.</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_128.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_80.png" width="604" height="322" /></a></p> <p>Loggt sich der Benutzer ein, erhält er neue Möglichkeiten. Im ersten Schritt ist angedacht, ein Blog bzw. einen neuen Artikel einzustellen. Unter den Aktionen (rechts oben) können dann benutzerspezifische Einstellungen vorgenommen werden. Der Rest der Seite bleibt unverändert.</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_129.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_81.png" width="604" height="322" /></a></p> <h3>Allgemeine Verwendbarkeit</h3> <p>Eines der Ziele dieser Umsetzung ist, dass die entstehende Anwendung breiter eingesetzt werden kann. Sie soll den Grundstock für Feed-Aggregationen bieten, jedoch nicht auf diesen speziellen Anwendungsfall beschränkt bleiben.</p> <h3>Eingestellte Blogs und Artikel</h3> <p>Sämtlicher eingestellter Inhalt, der nicht aus einer vertrauenswürdigen Quelle stammt wird geprüft. D.h.<strong> ein neu eingestelltes Blog wird nicht automatisch aggregiert</strong>, sondern landet auf einer Liste von zu prüfenden Quellen. Damit soll zum Einen vermieden werden, dass Spam in der Auswahl landet, zum Anderen ist es durchaus sinnvoll, sich zuerst die Erlaubnis des Blogbetreibers einzuholen (wenn dies im Internet offensichtlich auch kaum üblich ist).</p> <p>Neue eingestellte Artikel werden ebenfalls geprüft und freigeschalten. Auch hier soll es vermieden werden, dass Spam publiziert wird. Beiträge von Benutzern, die bereits 2 (oder eine andere zu konfigurierende Anzahl) freigeschaltete Artikel eingestellt haben, werden automatisch publiziert, da diese als vertrauenswürdig eingestuft werden. Dies kann über die Administration jederzeit rückgängig gemacht werden.</p> <h3>Administration</h3> <p>Die Administration soll im Wesentlichen folgende Funktionen bieten:</p> <ul> <li>Benutzerverwaltung </li> <li>Verwaltung der Blogs </li> <li>Verwaltung der eingestellten Beiträge (Beiträge via Blog-Feeds werden in dieser Liste nicht behandelt) </li> <li>Verfügbare Sprachen </li> <li>Diverse Einstellungen </li> </ul> <p>Die Administrationsseite wird Benutzern mit entsprechender Rolle über die Aktionen angeboten.</p> <h2>Nichtziel</h2> <p style="background-color: #eee">Es ist kein Ziel von <a title="WPF-Blogger" href="http://wpf-blogger.com" target="_blank">WPF-Blogger</a>, die gesamten Inhalte anzuzeigen. Der Autor soll honoriert werden. Aus diesem Grund werden auch weiterhin nicht die gesamten Beiträge angezeigt, sondern lediglich ein Teaser.</p> <h2>Offene Fragen / Feedback erwünscht</h2> <p>Es gibt meinerseits einige Überlegungen, die grundsätzlich gut zur Neuentwicklung von <a title="WPF-Blogger" href="http://wpf-blogger.com" target="_blank">WPF-Blogger</a> passen würden, bei denen ich mir allerdings nicht zu 100% sicher bin. Ich möchte sie an dieser Stelle zur Diskussion stellen, in der Hoffnung auf Feedback.</p> <h3>Bewertung</h3> <p>Von einschlägigen Seiten (siehe beispielsweise <a title="DotNetKicks" href="http://dotnetkicks.com/default.aspx" target="_blank">DotNetKicks</a> oder der <a title="dotnet-kicks.de" href="http://dotnet-kicks.de/" target="_blank">deutschsprachigen Umsetzung</a>) ist man es gewohnt, Beiträge bewerten zu können, um so die Spreu vom Weizen zu trennen. Das ist grundsätzlich eine gute Sache. Im Endeffekt ist mein Ansatz aber der Aktualität gewidmet. Der Benutzer soll einen schnellen Überblick erlangen, was sich in den abgedeckten Bereichen tut. Nichts desto trotz sollte er - meiner Meinung nach - doch auch eine Rückmeldung erhalten, welcher Inhalt von anderen empfohlen wird. Anstatt einer direkten Bewertung kann dies aber auch durch die eigentliche Statistik-Auswertung geschehen. Links, die häufig geklickt werden, scheinen grundlegend interessanter zu sein, als solche mit wenigen Aufrufen. Daraus könnte eine Bewertung abgeleitet werden, ohne den Benutzer zu einer Bewertung zu nötigen.</p> <h3>Kommentare</h3> <p>Wünschenswert wäre eine Möglichkeit Kommentare zu hinterlassen. Die Ideallösung wäre, dass sämtliche Kommentare an das eigentliche Blog zurückfließen und dem Verfasser des Artikels zu Gute kommen. Allerdings habe ich hierfür noch keine zufriedenstellende Lösung gefunden, wodurch ich die gesamte Funktionalität in Frage stelle. Meinungen?</p> <h2>Ausblick</h2> <p>Der nächste Artikel der <a title="Jetzt Backbone.js lernen" href="http://devtyr.norberteder.com/?tag=/Backbone.js">Serie über Backbone.js</a> beschäftigt sich mit dem Thema der Suchmaschinen. Darin wird hinterfragt, für welche Teile der Website der Einsatz von Backbone.js tatsächlich geeignet ist. Soll heißen, welche Inhalte werden statisch ausgeliefert und welche erst durch den Client erstellt.</p> http://devtyr.norberteder.com/post/Jetzt-Backbonejs-lernen-Konzept-WPF-Blogger.aspx Norbert Eder [MVP] 3567 2012-01-31T12:00:00 The .NET Gadgeteer Diaper Alarm Part 1 (Moisture Sensor) <p>During the .NET Microframework and <a href="http://www.youtube.com/playlist?list=PL7A0F12DEA2A538C2" target="_blank">.NET Gadgeteer code camp talk</a> that Andrew Duthie and I gave several weeks back, we tossed around the idea of using the Gadgeteer and a moisture sensor to alert parents of, ahhhh, soggy diaper issues.</p> <p>Shortly before the holidays, in the mail, I got a package of goodies from Microsoft Research. That package included one of these moisture sensors from Seeed Studio. These are meant for checking to see how wet soil is, but I figure one man's diaper is another man's soil, so, I can't resist. Must…prototype…solution.</p> <blockquote> <p>WARNING: Science in Progress. Also: diaper jokes.</p> </blockquote> <h3>First Iteration</h3> <p>I didn't have any of that handy inoffensive blue liquid available (the stuff they use to represent *any* bodily fluid on commercials; pretty sure that's how Smurfs are grown), so lacking any actual babies (my kids are all potty trained now. Yay!) I'm using plain old water. The real thing is a <a href="http://www.bing.com/search?q=conductivity+of+urine&amp;src=ie9tr" target="_blank">bit more conductive</a>, so you may need to vary some values I use. Also, I'm dealing with liquids only in this post. You're on your own with anything more…substantial, but I might recommend some sort of scale, or for the newborns, perhaps a blast detector <a href="http://www.amazon.com/3M-8979N-Performance-Nuclear-Slate/dp/B000NG3ZKI" target="_blank">held on with this</a>.</p> <h4>Hardware</h4> <p>I'm using the <a href="http://www.seeedstudio.com/depot/moisture-module-net-gadgeteer-compatible-p-973.html?cPath=203" target="_blank">Moisture Module from Seeed Studio</a>. (You can also <a href="http://www.ghielectronics.com/catalog/category/275/" target="_blank">purchase it through GHI</a> as they have a partnership.) This module returns back an analog voltage that is proportional to how wet the area is. As mentioned, it's is really meant to be used in soil, to tell you if you need to water your plants, so we'll have to finagle a few things to make it work here.</p> <p>&nbsp;</p> <p><a href="http://www.seeedstudio.com/depot/moisture-module-net-gadgeteer-compatible-p-973.html?cPath=203" target="_blank"><img src="http://10rem.net/media/83158/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_3.png" width="500" height="259" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>It's wired to socket 9 on my Gadgeteer board. That's one of the two analog sockets on the GHI Electronics Spider. I had one of the extra long ribbon cables which was perfect for this. You don't want the rest of your modules anywhere near what we're doing here. Also, this probably works best with babies who are relatively sedentary, preferably sleeping. Otherwise, you'll end up with a miniature Borg running around the house trailing some sort of dangling box of electronic goodies.</p> <blockquote> <p>Warning: I'm reasonably certain that none of this is actually baby-proof.</p> </blockquote> <h4>Yee Old Test Subject</h4> <p>Next, I need something to test in. No, don't go there as my wife would not appreciate getting that kind of laundry from me, not even in the name of science. I'm pretty sure I could get my almost-6 year old son to cooperate if I offered LEGO or something as a reward, but that'd also get me on the naughty list. Lacking other creative solutions, I'm just going to use a small plate and a paper towel to see how this all works.</p> <p><a href="http://10rem.net/media/83163/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_7.png" target="_blank"><img src="http://10rem.net/media/83168/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_thumb_2.png" width="481" height="448" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>I'll wet the towel, and wait until the moisture starts to hit the sensor. Then I can adjust for sensitivity. The idea is not to make the sensor swim, but to get an idea of how we the towel needs to be for this to work. Remember, the sensor is designed to be stuck in soil, not placed down on a towel.</p> <h4>Software</h4> <p>Be sure to <a href="http://www.tinyclr.com/support" target="_blank">download the latest Gadgeteer SDK and driver package</a>. The latest rev (as of this month) includes support for all the GHI and Seeed modules on the market, including this one. Once you'd done that, it's time to create a new project. Open up Visual Studio and create a new .NET Gadgeteer Project. I named mine DiaperMonitor.</p> <p><a href="http://10rem.net/media/83173/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_5.png" target="_blank"><img src="http://10rem.net/media/83178/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_thumb.png" width="550" height="387" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>Next, add the main board, if it's not already there. By default, mine came up with the FEZ Hydra, presumably because that's the last one I used. I simply removed that and dragged on a FEZ Spider from the list of main boards in the toolbox.</p> <p>Next, I added a Seeed.MoistureSensor module. You can either connect it automatically, or pick the port yourself and drag the connection to create the link. I have the moisture sensor on socket 9 as shown here. I also added one of the brand new GHI LED7R modules just to give us something interesting to light up.</p> <p><a href="http://10rem.net/media/83183/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_11.png" target="_blank"><img src="http://10rem.net/media/83188/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_thumb_3.png" width="650" height="361" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>If you don't have one of the new LED7R, you can use any LED module and simply change the code we'll use for the light up.</p> <h4>Le Code</h4> <p>The first iteration of code is simply going to check the moisture level, and light up the LED7R&nbsp; when the moisture level makes it over a certain threshold. On the Gadgeteer, you don't want to have a tight loop running from the ProgramStarted method - doing so would prevent other event handlers from running.</p> <p>The code for the sensor is really simple. If you look at the sensor itself, it appears to simply send a voltage down one side of the tongs and checks for conductivity with the other side. The driver uses a digital output for sending and an analog input for receiving. I was actually surprised to find a through-hole NPN transistor (it's a TO-932 package <a href="http://en.wikipedia.org/wiki/2N2222" target="_blank">2N2222 amplifier</a> marked 2N2222 H331) in there, as almost everything else gadgeteer-related uses surface mount components (including the two resistors on the board), but I know the 2N2222 itself is difficult (impossible?) to find in an SMT package.</p> <p>I'm far from an electronics expert, but my guess is that one side of the fork sends the regular (apx) 3.3v signal from pin 3 into the dirt/diaper. The other side will get a very faint amount of voltage transferred through the medium which it then amplifies with the transistor in order to make it meaningful to the microcontroller.</p> <p>So, instead of using a polling loop, I kick off a timer to handle polling the sensor. The timer is set to poll once per second and compare the returned value to the minimum moisture level. If the level is passed, then the LED7R animates once and then stays on. After changing the diaper, you reset the Gadgeteer to restart the program. Inelegant, but simple.</p> <pre class="brush: csharp;"> using System;<br /> using System.Threading;<br /> using Microsoft.SPOT;<br /> using Microsoft.SPOT.Presentation;<br /> using Microsoft.SPOT.Presentation.Controls;<br /> using Microsoft.SPOT.Presentation.Media;<br /> using Microsoft.SPOT.Touch;<br /> <br /> using Gadgeteer.Networking;<br /> using GT = Gadgeteer;<br /> using GTM = Gadgeteer.Modules;<br /> using Gadgeteer.Modules.Seeed;<br /> using Gadgeteer.Modules.GHIElectronics;<br /> <br /> namespace DiaperMonitor<br /> {<br /> public partial class Program<br /> {<br /> private GT.Timer _pollingTimer;<br /> private int minimumBadMoistureLevel = 200;<br /> <br /> void ProgramStarted()<br /> {<br /> Debug.Print("Program Started");<br /> <br /> _pollingTimer = new GT.Timer(1000);<br /> _pollingTimer.Tick += OnPollingTimerTick;<br /> <br /> _pollingTimer.Start();<br /> }<br /> <br /> void OnPollingTimerTick(GT.Timer timer)<br /> {<br /> int moisture = moistureSensor.GetMoistureReading();<br /> <br /> Debug.Print(moisture.ToString());<br /> <br /> if (moisture &gt; minimumBadMoistureLevel)<br /> {<br /> AlertParents();<br /> <br /> timer.Stop();<br /> }<br /> }<br /> <br /> private void AlertParents()<br /> {<br /> lED7R.Animate(10, true, true, true);<br /> }<br /> }<br /> } </pre> <p>This approach is nice, but doesn't do another loud or obvious. You'd have to keep looking in the room and checking for the lights. Chances are, the baby's own internal alarm would start sounding before you noticed the lights. In addition, this doesn't allow for any configuration of level, so on a hot summer night (thank's to Sammy Hagar, that song is now stuck in my head), there may be a general level of damp which doesn't quite meet the bar for a burst dam.</p> <p>So we have two problems: one is configuration, the second is for meaningful alerts. Let's look at configuration first.</p> <h3>Second Iteration - Configuration</h3> <p>The next step is to configure that alert level. You can leave it as a fixed value in code, or you can configure it via any number of other external inputs. One approach would be to use a potentiometer to set a threshold. Another would be to sample the current value and say that value is the "normal" value and anything well above it would be the "wet" value.</p> <p>Either approach would require some input device, like a button, to enable the timer and possibly to sample the data.</p> <p>First, I need to know what the possible values are. I couldn't find any documentation, so I simply went to the Gadgeteer codeplex site and <a href="http://gadgeteer.codeplex.com/SourceControl/changeset/view/14112" target="_blank">looked at the source code</a>. As I suspected, the sensor is an extremely simple wrapper around the analog input class. It returns a value in the range of 0 to 1600, as evidenced by this code:</p> <pre class="brush: csharp;"> /// &lt;summary&gt;<br /> /// Gets the current moisture reading.<br /> /// &lt;/summary&gt;<br /> /// &lt;returns&gt;An integer value, where 0 is fully dry and 1000 (or greater) is completely wet. &lt;/returns&gt;<br /> public int GetMoistureReading()<br /> {<br /> return (int) (analogInput.ReadProportion() * 1600.0);<br /> } </pre> <p>It also has a nice comment which isn't surfaced in the compiled version I have, so I assume that's a somewhat later rev. (Actually, I just looked in the Object Browser and there it is. I wonder why intellisense wasn't showing it? Odd.) In any case, it's useful to know you can always go and inspect the source if you have any question about how something is working at the Gadgeteer module driver level.</p> <p>So what can we do with that information? Well, let's look at the potentiometer approach.</p> <h4>Potentiometer approach</h4> <p>One thing most MCUs don't have is a large number of analog inputs. In order for a MCU to enable analog input, it has to have a digital to analog converter channel available. Most MCUs which have on-board ADC support either 10 bit or 12 bit analog conversion. The specs for the EMX module used by the Gadgeteer show that it has 7 10 bit analog inputs total. The FEZ Spider surfaces six of them, but only on two sockets. So, unless you have a multi-input devices that can use the 3 pins on a type A socket (or you create a breakout of some sort), you really end up being able to take input from only two analog modules. That leaves us with our diaper sensor and a potentiometer.</p> <p><a href="http://10rem.net/media/83193/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_13.png" target="_blank"><img src="http://10rem.net/media/83198/Windows-Live-Writer_The-.NET-Gadgeteer-Diaper-Alarm_13CA5_image_thumb_4.png" width="650" height="269" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>For this, I added a potentiometer module to the other analog socket: socket 10. The pot is going to be used to configure the minimum level at which we'll set off the diaper alarm. In addition, I added a button module that we'll use to arm the device.</p> <p>The new code refactors a few things. Explanation after the listing.</p> <pre class="brush: csharp;"> using System;<br /> using System.Threading;<br /> using Microsoft.SPOT;<br /> using Microsoft.SPOT.Presentation;<br /> using Microsoft.SPOT.Presentation.Controls;<br /> using Microsoft.SPOT.Presentation.Media;<br /> using Microsoft.SPOT.Touch;<br /> <br /> using Gadgeteer.Networking;<br /> using GT = Gadgeteer;<br /> using GTM = Gadgeteer.Modules;<br /> using Gadgeteer.Modules.Seeed;<br /> using Gadgeteer.Modules.GHIElectronics;<br /> <br /> namespace DiaperMonitor<br /> {<br /> public partial class Program<br /> {<br /> private GT.Timer _pollingTimer;<br /> <br /> private const int MoistureLevelRangeLow = 50;<br /> private const int MoistureLevelRangeHigh = 800;<br /> private int _minimumBadMoistureLevel = 200;<br /> <br /> void ProgramStarted()<br /> {<br /> Debug.Print("Program Started");<br /> <br /> button.ButtonPressed += button_ButtonPressed;<br /> }<br /> <br /> void button_ButtonPressed(Button sender, Button.ButtonState state)<br /> {<br /> Debug.Print("Button Pressed");<br /> <br /> if (_armed)<br /> {<br /> Disarm();<br /> <br /> // animate counter-clockwise and turn off<br /> lED7R.Animate(10, false, true, false);<br /> }<br /> else<br /> {<br /> Arm();<br /> lED7R.Animate(10, true, false, false);<br /> }<br /> }<br /> <br /> private void SetTriggerLevel()<br /> {<br /> // read from the pot<br /> double pot = potentiometer.ReadPotentiometerPercentage();<br /> <br /> // calculate the moisture level based on the bounds<br /> _minimumBadMoistureLevel = MoistureLevelRangeLow +<br /> (int)(pot * (MoistureLevelRangeHigh - MoistureLevelRangeLow));<br /> <br /> Debug.Print("Minimum trigger level: " + _minimumBadMoistureLevel);<br /> }<br /> <br /> bool _armed = false;<br /> private void Arm()<br /> {<br /> SetTriggerLevel();<br /> <br /> // start the timer<br /> _pollingTimer = new GT.Timer(1000);<br /> _pollingTimer.Tick += OnPollingTimerTick;<br /> <br /> _pollingTimer.Start();<br /> <br /> _armed = true;<br /> <br /> Debug.Print("Armed.");<br /> }<br /> <br /> private void Disarm()<br /> {<br /> // stop the timer<br /> _pollingTimer.Stop();<br /> _pollingTimer.Tick -= OnPollingTimerTick;<br /> <br /> _armed = false;<br /> <br /> Debug.Print("Disarmed.");<br /> }<br /> <br /> <br /> // event handler for the timer that polls the moisture sensor<br /> void OnPollingTimerTick(GT.Timer timer)<br /> {<br /> int moisture = moistureSensor.GetMoistureReading();<br /> <br /> Debug.Print(moisture.ToString());<br /> <br /> if (moisture &gt; _minimumBadMoistureLevel)<br /> {<br /> AlertParents();<br /> <br /> Disarm();<br /> }<br /> }<br /> <br /> // do all the obnoxious alert stuff in here<br /> private void AlertParents()<br /> {<br /> Debug.Print("Diaper Alert!");<br /> <br /> // animate clockwise and leave on<br /> lED7R.Animate(10, true, true, true);<br /> }<br /> }<br /> }<br /> </pre> <p>The new workflow is this:</p> <ul> <li>Set the potentiometer to some minimum trigger level. There's no readout or anything at the moment, so you'd just have to eyeball it.</li> <li>Once the pot is set, click the button to arm the diaper alert. The LEDs will cycle and you know it's armed.</li> <li>Wet the diaper (not you, personally, I hope)</li> <li>Diaper alert and then the system disarms.</li> </ul> <p>When disarming the alarm, to avoid leaks, I remove the event handler. When arming, I wire it back up. In our particular example, this doesn't buy you anything, but if you were to pull this code out into another class, it almost certainly would save you from eating up all the memory on the device over time.</p> <p>This new workflow is pretty good, but the diaper alert leaves something to be desired. You'd have to get in there to notice the LEDs (in a dark room they'd be ok), but there's nothing to really grab your attention. Clearly we can't have a piercing piezo siren or something, as that would wake up baby before the wet does.</p> <p>What other options do we have?</p> <p>Well, how about an alert that is sent to a remote device?</p> <p>That's the topic of the next post :)</p> <h3>Summary</h3> <p>Ok, that was fun :)</p> <p>For most of us, this is probably not how we'll use the Gadgeteer. Kids generally have <a href="http://www.youtube.com/results?search_query=crying+babies&amp;oq=crying+babies&amp;aq=f&amp;aqi=g10&amp;aql=&amp;gs_sm=e&amp;gs_upl=162l1874l0l1978l13l10l0l1l1l0l167l1011l3.6l9l0" target="_blank">pretty good built-in wet diaper alarms</a> as it is. However, you can use this sensor for just about anything from plant moisture detection to water leak detection to more. More importantly, this post shows how to use an analog sensor of any type, including how to configure the range and some other useful NETMF tips.</p> <p><a href="http://feedads.g.doubleclick.net/~a/Sx6v20tMCk4PVkFoe4pcprQO9rk/0/da"><img src="http://feedads.g.doubleclick.net/~a/Sx6v20tMCk4PVkFoe4pcprQO9rk/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/Sx6v20tMCk4PVkFoe4pcprQO9rk/1/da"><img src="http://feedads.g.doubleclick.net/~a/Sx6v20tMCk4PVkFoe4pcprQO9rk/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ly1EjFTELxs:ArD4Ii1KiHE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ly1EjFTELxs:ArD4Ii1KiHE:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ly1EjFTELxs:ArD4Ii1KiHE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ly1EjFTELxs:ArD4Ii1KiHE:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/ly1EjFTELxs" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/ly1EjFTELxs/the-net-gadgeteer-diaper-alarm-part-1-moisture-sensor Pete Brown 3566 2012-01-30T23:22:37 The Last Catfish in the East Village? <p> People who live in New York City often inhabit tiny apartments, and the only reason we tolerate them is that we've exchanged private spaces for public spaces. We can frequently be found not in our apartments but walking around outside, or in a store, or a theater, or a concert hall, or a museum. </p><p>... more ...</p> http://www.charlespetzold.com/blog/2012/01/The-Last-Catfish-in-the-East-Village.html Charles Petzold 3565 2012-01-30T19:40:41 Multisampling Aquí tenéis la sexta entrega de la serie sobre aliasing. En esta entrega veremos qué es el Multisampling Multisampling es la solución para aquellos que quieren usar supersampling, pero no se lo pueden permitir. La idea es sencilla: en lugar de aumentar la resolución de todo el renderizado, ¿qué pasaría si dejamos la renderización de <a href='http://speakingin.net/2012/01/24/multisampling/'>[...]</a> http://speakingin.net/2012/01/24/multisampling/?utm_source=rss&utm_medium=rss&utm_campaign=multisampling Juan María Laó Ramos 3564 2012-01-24T12:40:51 Jetzt Backbone.js lernen: Die Serie <p><a title="Backbone.js" href="http://documentcloud.github.com/backbone/" target="_blank"><img style="margin: 0px 10px 10px 0px; display: inline; float: left" title="Backbone.js" alt="Backbone.js" align="left" src="http://documentcloud.github.com/backbone/docs/images/backbone.png" width="209" height="37" /></a></p> <p>Nach der erfolgreichen <a title="Jetzt Knockout.js lernen: Die Serie" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie über Knockout.js</a> und dem durchwegs positiven Feedback, möchte ich - wie bereits angekündigt - eine neue Serie starten. Diese beschäftigt sich mit <a title="Backbone.js" href="http://documentcloud.github.com/backbone/" target="_blank">Backbone.js</a>. In dieser Vorabinformation möchte ich kurz darauf eingehen, was der Leser erwarten darf. Und das ist eine ganze Menge, da diese Serie einen gänzlich anderen Weg einschlagen wird.</p> <h2>Praxisbeispiel</h2> <p>Im Rahmen der Knockout.js-Serie wurden unterschiedliche Beispiele behandelt. Diese wurden möglichst nahe an der Realität gehalten, aber es mussten auch Teile konstruiert werden. Dies soll in dieser Serie anders sein.</p> <p><a title="WPF-Blogger.com - Entwickler-Informationen zu WPF, Silverlight und Windows Phone" href="http://wpf-blogger.com" target="_blank"><img style="background-image: none; border-right-width: 0px; margin: 0px 10px 10px 0px; padding-left: 0px; padding-right: 0px; display: inline; float: left; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="WPF-Blogger.com - Entwickler-Informationen zu WPF, Silverlight und Windows Phone" border="0" alt="WPF-Blogger.com - Entwickler-Informationen zu WPF, Silverlight und Windows Phone" align="left" src="http://devtyr.norberteder.com/image.axd?picture=image_126.png" width="97" height="60" /></a>Im Rahmen der Backbone.js-Serie wird ein bereits bestehendes Projekt neu implementiert. Konkret geht es um meine Website <a title="WPF-Blogger.com - Entwickler-Informationen zu WPF, Silverlight und Windows Phone" href="http://wpf-blogger.com" target="_blank">http://wpf-blogger.com</a>. Diese aggregiert Feeds von Bloggern zu WPF, Silverlight und Windows Phone in mehreren Sprachen. Damit soll einschlägigen Entwicklern eine zentrale Anlaufstelle für aktuelle Tipps, Tricks und Techniken auf Basis der genannten Technologien geboten werden.</p> <p>Diese Entscheidung hat mehrere Hintergründe: So besteht die aktuelle <a title="WPF-Blogger.com - Entwickler-Informationen zu WPF, Silverlight und Windows Phone" href="http://wpf-blogger.com" target="_blank">WPF-Blogger</a> bereits unverändert seit mehreren Jahren und bedarf zahlreicher Neuerungen. Da mir eine Weiterentwicklung auf der aktuellen Basis wenig zielführend erscheint, habe ich mich dazu entschieden, auf aktuelle Technologien und Techniken zu setzen, auch wenn manches davon als Hype angesehen werden kann.</p> <h2>Was kann der Leser nun erwarten?</h2> <p>Da ein konkretes Projekt zur Umsetzung gelangt, wird die Backbone.js-Serie einen hohen Grad an <strong>Praxiswissen</strong> und - im Rahmen der Umsetzung gemachten - Erfahrungen aufweisen. Ideal, um sich ein Bild über diese Bibliothek zu machen und einen etwaigen eigenen Einsatz besser abschätzen zu können. </p> <p>Ein weiterer Mehrwert besteht sicherlich darin, dass ich - als der Autor dieser Serie - Backbone.js gerade erst lerne. Daher sind zahlreiche <strong>Hinweise auf Fallen und Tücken</strong> zu erwarten, in die ich getreten bin - oder noch treten werde.</p> <p>Großes Augenmerk werde ich auch auf die Themen <strong>Konzept</strong> und <strong>Testing</strong> legen.</p> <h2>Eingesetzte Technologien und Bibliotheken</h2> <p>Das Projekt soll aus aktueller Sicht folgende (Quasi-)Standards, Technologien und Bibliotheken umfassen:</p> <ul> <li>HTML5 </li> <li>CSS3 </li> <li>jQuery </li> <li>Backbone.js </li> <li>.NET </li> </ul> <p>Sollte sich die Notwendigkeit ergeben, weitere Bibliotheken zu verwenden, wird diese Liste entsprechend erweitert.</p> <blockquote> <p><strong>Hinweis</strong>: Die obige Liste setzt auf weit mehr als nur Backbone.js. Auch diese werden in dieser Serie teilweise besprochen/diskutiert. Das Hauptaugenmerk wird jedoch auf Backbone.js gelenkt.</p> </blockquote> <h2>Sourcecode / Sharing</h2> <p>Jeder soll sich den aktuellen Sourcecode ansehen, aber auch für eigene Projekte verwenden können. In der <a title="Jetzt Knockout.js lernen: Die Serie" href="http://devtyr.norberteder.com/?tag=/Knockout.js" target="_blank">Knockout.js-Serie</a> habe ich hierfür teilweise <a title="JSFiddle" href="http://jsfiddle.net/" target="_blank">JSFiddle</a> verwendet, ist aber in diesem Rahmen nicht sehr zielführend. Aus diesem Grund habe ich mich entschieden, das gesamte Projekt auf <a title="WPF-Blogger.com auf GitHub" href="https://github.com/devtyr/wpfblogger" target="_blank">github</a> zu hosten.</p> <h2>Inhalt</h2> <p>Die einzelnen Teile dieser Serie sind nicht im Vorab festgelegt, sondern ergeben sich aus der laufenden Entwicklung des Projektes. Die untenstehende Liste ist daher als flexibel anzusehen und wird ständig erweitert bzw. auf dem aktuellen Stand gehalten.</p> <ul> <li><a title="Jetzt Backbone.js lernen: Die Serie" href="http://devtyr.norberteder.com/post/Jetzt-Backbonejs-lernen-Die-Serie.aspx">Jetzt Backbone.js lernen: Die Serie</a> (dieser Beitrag) </li> <li><a title="Jetzt Backbone.js lernen: Konzept WPF-Blogger" href="http://devtyr.norberteder.com/post/Jetzt-Backbonejs-lernen-Konzept-WPF-Blogger.aspx">Jetzt Backbone.js lernen: Konzept WPF-Blogger</a> </li> <li>Jetzt Backbone.js lernen: Überlegungen zum Thema Suchmaschinen </li> <li>Jetzt Backbone.js lernen: Grundlagen Backbone.js </li> <li>Weitere Teile: TBD </li> </ul> <p>Auf neue Teile wird zusätzlich über <a title="Norbert Eder auf Twitter" href="http://twitter.com/norberteder" target="_blank">Twitter</a> hingewiesen.</p> <h2>Feedback</h2> <p>JavaScript, HTML5 und CSS3 waren bis dato nicht mein Hauptscope. Daher ist es sehr wahrscheinlich, dass jede Menge meines Codes einfacher, schöner und besser geschrieben werden kann. Damit die Leser, die, so wie ich, ebenfalls keine Gurus auf diesem Gebiet sind, sich weiterentwickeln, hoffe ich auf zahlreiches Feedback. Darum bitte ich schon jetzt die Gurus unter meinen Lesern, mit ihrer Meinung nicht hinter den Berg zu halten.</p> http://devtyr.norberteder.com/post/Jetzt-Backbonejs-lernen-Die-Serie.aspx Norbert Eder [MVP] 3563 2012-01-28T14:36:24 BAOC Countdown and WPAppItUp Wrap-Up <p><b>Big App on Campus: Two Weeks Left!</b></p> <p>Back in November, we <a href="http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/11/18/do-you-have-what-it-takes-to-be-the-big-app-on-campus.aspx">announced</a> a developer promotion for US students called “Big App on Campus.” There are exactly 18 days left to enter. Though we think it’s a no brainer, some of you have asked “Why should I enter?” Let’s recap:</p> <p>1. <b>For the experience of a lifetime</b> -&gt; 10 finalists, along with one of their friends, will win a trip to SxSW in March. If selected as a finalist, you and your friend will get to come hang out with <a href="http://www.thegraciousfew.com/">The Gracious Few</a> (featuring members of the bands <a href="http://youtu.be/FNrQOUtXYOo">LIVE</a> and <a href="http://youtu.be/JWhXyNL74yI">Candlebox</a>) and attend a rocking concert.</p> <p>2. <b>$30k in prize money</b> -&gt; the top paid app and the top free app will each win $15,000!</p> <p><em>3. </em><b>Serious bragging rights</b> -&gt; finalist apps will be featured on our Facebook page and the developers behind these creations will have some crazy stories to tell about their time in Austin at SxSW.<em></em></p> <blockquote> <p><em><font size="2">&lt;aside reason=”toAppeaseTheLawyers”&gt;No Purchase Necessary. Open only to US university students 18+ who are legal residents of the U.S. and D.C. Submissions due 02/14/2012 and game ends 03/13/2012. For Official Rules, click </font></em><a href="http://www.microsoft.com/student/bigapponcampus/"><em><font size="2">here</font></em></a><em><font size="2">&lt;/aside&gt;</font></em></p> </blockquote> <p>All Windows Phone applications published since August 1, 2011 are eligible for entry. However all apps must be published to the Marketplace by February 14<sup>th</sup>. Make your Valentine proud and <a href="http://www.zoomerang.com/Survey/WEB22DP5BYJKZH/">enter</a> your app today – I’m sure she/he won’t mind helping you spend the $15k in winnings up for grabs. Check out our Facebook <a href="http://www.facebook.com/MicrosoftTechStudent?sk=app_269100103148944">page</a> for all details and information on how to get started on building your first, or next, Windows Phone app.</p> <p><b>&#160;</b></p> <p><b>#WPAppItUp Wrap-Up</b></p> <p>Starting back in October, we challenged students to create Sketchflow prototypes of their app ideas for Windows Phone. I’m happy to announce the full list of winners:</p> <p><a href="http://www.twitter.com/cbarretopy">@cbarretopy</a></p> <p><a href="http://www.twitter.com/simzzdev">@simzzdev</a></p> <p><a href="http://www.twitter.com/m_naveed4u">@m_naveed4u</a></p> <p><a href="http://www.twitter.com/sp990">@sp990</a></p> <p><a href="http://www.twitter.com/gowtham1337">@gowtham1337</a></p> <p><a href="http://www.twitter.com/3xdev">@3xdev</a></p> <p><a href="http://www.twitter.com/utebolatas">@utebolatas</a></p> <p><a href="http://www.twitter.com/sakarshkumar">@sakarshkumar</a></p> <p><a href="http://www.twitter.com/sriharsha_63">@sriharsha_63</a></p> <p><a href="http://www.twitter.com/luciomsp">@luciomsp</a></p> <p>Congratulations to all the winners!</p> <p>Ben</p> <p><a href="http://twitter.com/benlower">@benlower</a> | <a href="mailto:phoneninja@microsoft.com?subject=Saw%20you%20on%20WPDev%20blog">phoneninja@microsoft.com</a> | +1 (206) 659-NINJA (6465)</p><div style="clear:both;"></div><img src="http://windowsteamblog.com/aggbug.aspx?PostID=575282" width="1" height="1"> http://windowsteamblog.com/windows_phone/b/wpdev/archive/2012/01/27/baoc-countdown-and-wpappitup-wrap-up.aspx Windows Phone Dev Blog 3562 2012-01-27T20:01:43 Playing A Sound, Simplified http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/-dIhlb0Gt-4/ Jesse Liberty [MS] 3561 2012-01-27T17:49:25 Jetzt Knockout.js lernen: Mapping <p>Im letzten Teil meiner <a title="Serie zu Knockout.js" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie zu Knockout.js</a> beschäftigen wir uns mit dem Mapping Plugin. In einigen der letzten Beispiele wurde mit JSON gearbeitet und so Daten simuliert, die vom Server stammen. Damit diese an eine View gebunden wurde, musste das ViewModel (welches Observables enthält) manuell aufgefüllt werden. Das Mapping Plugin bietet hier eine Unterstützung, die in diesem Anwendungsfall sehr viel Aufwand spart. Dieser Artikel zeigt wie es funktioniert.</p> <h2>Voraussetzungen</h2> <p>Damit das Mapping-Plugin verwendet werden kann, muss es via <a title="knockout.mapping auf github" href="https://github.com/SteveSanderson/knockout.mapping/tree/master/build/output" target="_blank">github</a> bezogen werden. Hier der <a title="knockout.mapping.js" href="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js" target="_blank">Direktlink</a> zur aktuellsten Version. Das Script ist dann natürlich entsprechend einzubinden.</p> <h2>Grundlagen / Wiederholung</h2> <p>In den letzten Teilen dieser Serie wurden alle ViewModels manuell erstellt und sahen - mehr oder weniger - so aus:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 400px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> MyViewModel = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.title = ko.observable(); </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.author = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.pages = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Die Daten wurden dann so gesetzt:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 400px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">myViewModel.title(<span style="color: #006080">'Ein Buch'</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">myViewModel.author(<span style="color: #006080">'Ein Autor'</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">myViewModel.pages(500);</pre> <!--CRLF--></div> </div> <p>So musste das für jede Variable vorgenommen werden. Ein recht großer Aufwand, vor allem, wenn die Struktur komplexer wird. </p> <p>Knockout.mapping kann diesen Aufwand für uns stark minimieren.</p> <h2>Beispiel</h2> <p>Nun aber zu einem Beispiel an Hand dessen wir uns ansehen, wie knockout.mapping funktioniert. Dazu benötigen wir eine View, die mit einer Schaltfläche ausgestattet ist. Bei einem Klick darauf werden JSON-Daten vom Server geladen und an die View gebunden. Hier vorerst die View:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 500px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">=&quot;control&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">=&quot;button&quot;</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;click: loadData&quot;</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">=&quot;Load data&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">input</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">=&quot;data&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>Loaded Data<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: userName&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;&lt;</span><span style="color: #800000">br</span><span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: tweetCount&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Soweit noch nichts Neues, aber wenden wir uns der JavaScript-Seite zu.</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 500px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">$(<span style="color: #0000ff">function</span>(){</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> OverallViewModel = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.loadData = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> $.getJSON(<span style="color: #006080">'http://dl.dropbox.com/u/30654117/Demos/JavaScript/Knockout.js/Mapping/data/data.json'</span>, <span style="color: #0000ff">function</span>(data) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">var</span> viewModel = ko.mapping.fromJS(data);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> ko.applyBindings(viewModel, document.getElementById(<span style="color: #006080">'data'</span>));</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> firstViewModel = <span style="color: #0000ff">new</span> OverallViewModel();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.applyBindings(firstViewModel, document.getElementById(<span style="color: #006080">'control'</span>));</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">});</pre> <!--CRLF--></div> </div> <p>Hier wird ein ViewModel namens <font face="Courier New">OverallViewModel</font> definiert und an das DIV mit der Id <font face="Courier New">control</font> gebunden. Darin enthalten ist die Funktion <font face="Courier New">loadData</font>, welche bei einem Klick auf die Schaltfläche ausgelöst wird.</p> <p>Darin wird im ersten Schritt das JSON vom Server bezogen. Im Anschluss wird <font face="Courier New">ko.mapping.fromJS</font> mit den Daten als Parameter aufgerufen. Das Ergebnis ist ein ViewModel, welches die entsprechenden Eigenschaften, welche im JSON definiert sind (als Observables) besitzt.</p> <p>Der Vollständigkeit halber hier das JSON:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">{</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;userName&quot;</span>: <span style="color: #006080">&quot;Norbert Eder&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;tweetCount&quot;</span>: 10000</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Das resultierende ViewModel wird nun via <font face="Courier New">ko.applyBindings</font> an das DIV mit der Id <font face="Courier New">data</font> gebunden und die Daten kommen sofort zur Anzeige. Das einfache Beispiel ist fertig. Aber das Plugin kann noch mehr.</p> <h2>Ins Mapping eingreifen</h2> <p>Meine erste Frage - und das wird vermutlich jedem so gehen, war: </p> <blockquote> <p>So ein einfaches ViewModel ist ganz nett, aber was, wenn ich Funktionen, berechnete Eigenschaften etc. benötige?</p> </blockquote> <p>Aber auch hierfür gibt es eine entsprechende Lösung.</p> <p>Erweitern wir obiges Beispiel ein wenig. Zu den rudimentären Tweet-Informationen sollen nun einzelne Tweets hinzugefügt und angezeigt werden. Jeder Eintrag soll mit einer Schaltfläche bestückt werden, mit deren Hilfe ein Retweet durchgeführt werden kann. Auch das sollte nun recht einfach gehen, haben wir uns doch bereits mit <a title="Jetzt Knockout.js lernen: Templates" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Template-verwenden.aspx">Templates</a> beschäftigt. Die Herausforderung ist jedoch, die Tweets mit einem eigenen ViewModel zu versehen, welches eine entsprechende Funktion <font face="Courier New">retweet</font> anbietet, um eben diese Aktion ausführen zu können.</p> <p>Zuerst aber ein Blick auf die Daten, zwecks Übersicht der durchgeführten Änderungen:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 500px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">{</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;userName&quot;</span>: <span style="color: #006080">&quot;Norbert Eder&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;tweetCount&quot;</span>: 10000,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;tweets&quot;</span>: [</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;content&quot;</span>: <span style="color: #006080">&quot;Ein Testtweet der mit Sicherheit 140 Zeichen nicht überschreitet.&quot;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> },</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;content&quot;</span>: <span style="color: #006080">&quot;Knockout.js ist schon eine sehr feine Sache!&quot;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> },</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">&quot;content&quot;</span>: <span style="color: #006080">&quot;Bin schon gespannt auf Backbone.js. Soll auch nett sein.&quot;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> ]</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Nun muss die View umgebaut und mit einem Template versehen werden, auch noch nicht spektakulär:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 500px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;div id=<span style="color: #006080">&quot;control2&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;input type=<span style="color: #006080">&quot;button&quot;</span> data-bind=<span style="color: #006080">&quot;click: loadData&quot;</span> value=<span style="color: #006080">&quot;Load data&quot;</span>&gt;&lt;/input&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;div id=<span style="color: #006080">&quot;data2&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;h2&gt;Loaded Data&lt;/h2&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;span data-bind=<span style="color: #006080">&quot;text: userName&quot;</span>&gt;&lt;/span&gt;&lt;br/&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;span data-bind=<span style="color: #006080">&quot;text: tweetCount&quot;</span>&gt;&lt;/span&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div data-bind=<span style="color: #006080">&quot;template: { name: 'tweet-template', foreach: tweets }&quot;</span>&gt;&lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;/div&gt; </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;script type=<span style="color: #006080">&quot;text/html&quot;</span> id=<span style="color: #006080">&quot;tweet-template&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;p data-bind=<span style="color: #006080">&quot;text: content&quot;</span>/&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;input type=<span style="color: #006080">&quot;button&quot;</span> value=<span style="color: #006080">&quot;Retweet&quot;</span> data-bind=<span style="color: #006080">&quot;click: retweet&quot;</span>/&gt;&lt;/input&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;/script&gt;</pre> <!--CRLF--></div> </div> <p>Interessant ist das ViewModel, oder besser gesagt, sind die beiden ViewModels, da für die Anzeige eines einzelnen Tweets ein weiteres ViewModel (nennen wir es <font face="Courier New">TweetViewModel</font>) definiert werden muss. Immerhin möchten wir eine Funktion unterbringen. Darin sehen wir nun auch bereits den ersten Trick:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> TweetViewModel = <span style="color: #0000ff">function</span>(data) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> ko.mapping.fromJS(data, {}, <span style="color: #0000ff">this</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.retweet = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> alert(<span style="color: #006080">&quot;retweeted&quot;</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Die Funktion <font face="Courier New">retweet</font> ist einfach und zeigt hier lediglich einen Alert an. Zumindest kann so die Funktionalität getestet werden. Interessanter ist, dass das ViewModel Daten als Parameter übergeben bekommt und diese via <font face="Courier New">ko.mapping.fromJS</font> auf sich selbst appliziert. Damit werden für die in den Daten enthaltenen Eigenschaften korrespondierende Observables am ViewModel erzeugt. Erster Teil erledigt.</p> <p>Nun muss noch ein Weg gefunden werden, eine Instanz von <font face="Courier New">TweetViewModel</font> pro Item zu erzeugen. Dazu muss das Haupt-ViewModel ein wenig adaptiert werden. </p> <blockquote> <p><strong>Hinweis</strong>: Dieses wurde mit einem anderen Namen als im einfachen Beispiel zu sehen, da es im gleichen Beispiel zu finden ist, Download siehe weiter unten.</p> </blockquote> <p>Sehen wir es uns an:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> AnotherViewModel = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.loadData = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> $.getJSON(<span style="color: #006080">'http://dl.dropbox.com/u/30654117/Demos/JavaScript/Knockout.js/Mapping/data/data2.json'</span>, <span style="color: #0000ff">function</span>(data) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">var</span> viewModel = ko.mapping.fromJS(data, mapping);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> ko.applyBindings(viewModel, document.getElementById(<span style="color: #006080">'data2'</span>));</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }; </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Abgesehen davon, dass die Daten nun von einer anderen Quelle bezogen werden, sticht diese Änderung ins Auge:</p> <p><font face="Courier New">var viewModel = ko.mapping.fromJS(data, mapping);</font></p> <p>Es wird ein neuer Parameter übergeben. Eine Konfiguration für das Mapping.</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> mapping = {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #006080">'tweets'</span>: {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> create: <span style="color: #0000ff">function</span>(options) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> TweetViewModel(options.data);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--></div> </div> <p>Darin wird ein <font face="Courier New">create</font>-Callback definiert, der sich auf das Array <font face="Courier New">tweets</font> bezieht (siehe Daten weiter oben) und für alle Einträge aus dem <font face="Courier New">tweets</font>-Array (siehe Daten) aufgerufen. Als Parameter werden <font face="Courier New">options</font> übergeben. Diese enthalten:</p> <ul> <li><strong>data</strong>: Ein JavaScript-Objekt mit den Daten </li> <li><strong>parent</strong>: Das Eltern-Objekt oder das Array zu dem die Daten gehören </li> </ul> <p>Schlussendlich definiert die Zeile</p> <p><font face="Courier New">return new TweetViewModel(options.data);</font></p> <p>dass eine neue Instanz von <font face="Courier New">TweetViewModel </font>instanziiert werden soll. Die Daten werden als Parameter übergeben und innerhalb des ViewModel weiterverarbeitet (siehe weiter oben). </p> <p>Das wunderschön gestaltete Ergebnis:</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_125.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="Knockout.js Mapping Plugin Demo" border="0" alt="Knockout.js Mapping Plugin Demo" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_79.png" width="604" height="316" /></a></p> <h2>Fazit</h2> <p>Diese beiden Beispiele zeigen sehr schön, dass man sich mit Hilfe des Mapping Plugins jede Menge Schreibarbeit - und damit durchaus auch Fehler - sparen kann. Durch das mögliche Eingreifen in die Erstellung der ViewModels sind alle notwendigen Szenarien vorstellbar und keine Grenzen gesetzt.</p> <h2>Download / Showcase</h2> <p>Getestet werden können die beschriebenen Beispiele <a title="Knockout.js Mapping Online Demo" href="http://dl.dropbox.com/u/30654117/Demos/JavaScript/Knockout.js/Mapping/index.html" target="_blank">hier</a>. Gleich untenstehend der Download des gesamten Paketes:</p> <p><iframe style="padding-bottom: 0px; background-color: #fcfcfc; padding-left: 0px; padding-right: 0px; padding-top: 0px" title="Preview" height="120" marginheight="0" src="https://skydrive.live.com/embed?cid=C8D8CB313DB8E795&amp;resid=C8D8CB313DB8E795%21490&amp;authkey=ABZ1KqPCN4Md7f4" frameborder="0" width="98" marginwidth="0" scrolling="no"></iframe></p> <h2>Feedback</h2> <p>Gerne nehme ich jegliches Feedback zu diesem Beitrag, als auch zur gesamten <a title="Serie zu Knockout.js" href="http://knockoutjs.com/">Serie zu Knockout.js</a> entgegen. Hat dir die Serie geholfen? Hast du Anmerkungen, Anregungen? Fehlt ein Teil, sprich, wurde ein wichtiges Thema nicht besprochen? Teile es mir doch einfach mit und verfasse gleich einen Kommentar.</p> <h2>Jetzt Knockout.js lernen: Die Serie</h2> <ul> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Slides-und-Beispiele.aspx">Jetzt Knockout.js lernen: Slides und Beispiele</a> </li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formulare-und-Listen-binden.aspx">Jetzt Knockout.js lernen: Formulare und Listen binden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Template-verwenden.aspx">Jetzt Knockout.js lernen: Template verwenden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx">Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx">Jetzt Knockout.js lernen: Observables erweitern</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formularvalidierung-mit-Undo.aspx">Jetzt Knockout.js lernen: Formularvalidierung mit Undo</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Mapping.aspx">Jetzt Knockout.js lernen: Mapping</a> (dieser Teil)</li> </ul> http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Mapping.aspx Norbert Eder [MVP] 3560 2012-01-27T12:30:00 The n00b filter: Bad UX at its Finest in Eagle PCB <p>Speaking of UX…</p> <p>I finally decided to download and learn to use the industry-standard PCB and schematic layout software: Eagle. I've tried a bunch of others, and quite frankly, got tired of always using "also ran" software. The majority of the info on the web is for Eagle. It certainly doesn't have the best UI or the best workflow, but it has absolutely the most amount of information and the best support from PCB manufacturers. It's the industry standard, and like most industry-leading technical software (ever use industry-leading 3d modeling software? Gak!), it has some crazy issues.</p> <p>Here's the workflow. First, while working on the Schematic, you click this "Add" toolbar button</p> <p><a href="http://10rem.net/media/83112/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_4.png" target="_blank"><img src="http://10rem.net/media/83117/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_thumb_1.png" width="650" height="428" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Next, you're shown a screen with the libraries on the left, and the selected part on the right. You can browse the libraries here, but it is not something I went into with the intent of using it to manage library entries.</p> <p><a href="http://10rem.net/media/83122/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_2.png" target="_blank"><img src="http://10rem.net/media/83127/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_thumb.png" width="649" height="439" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I wanted to add the component, but I wasn't sure which button to press. Silly me, thought maybe "drop" meant to "drop it on the schematic" because, for whatever reason, "OK" didn't seem right.</p> <p><strong>Wrong.</strong></p> <p>"Drop", without so much as a confirmation MessageBox removes the library from the parts libraries on your computer. Yeah, so now I need to go find the library which contains every type of diode, because, umm, I deleted it.</p> <p><strong>At first, I thought I actually deleted it, but it turns out Eagle marks it to make it not show up in the Add dialog.</strong> I'm still going to finish this post anyway, because the brick was already passed painfully through the orifice when the library just went *poof* off the list.</p> <p>To restore it, you have to go into their separate Control Panel application (hint: if your app has a "control panel" app, there's an issue). That's better than having to reinstall the product, or download the library from RapidItsNotReallyATrojanWeSwearShare.com, but it's not entirely intuitive.</p> <p><a href="http://10rem.net/media/83132/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_6.png" target="_blank"><img src="http://10rem.net/media/83137/Windows-Live-Writer_The-n00b-filter-Bad-UX-at-its-Finest_13D66_image_thumb_2.png" width="650" height="432" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p><strong>This type of feature can only be called a n00b filter</strong>, although I'm sure there's a more technical UX antipattern name for it, probably one which includes a four-letter word or two.</p> <p>I have a lot of experience in software. I rarely need to be hand-held, and the last time I took a class to learn how to use a piece of software was to be a Novell 4 CNA in the mid 90s, and I took that class *after* I had already set up the network. I almost never read manuals, and use "Help" in apps maybe 5 times a year total, and only because I'm trying new stuff all the time. All I'm trying to say here, is I'm not afraid of software, and I very rarely do something which destroys work or accidentally does a format c:\ or anything.</p> <p>Seriously. No confirmation. It's a button on the "Add Component" screen.</p> <p>No wonder users hate us.</p> <p><a href="http://feedads.g.doubleclick.net/~a/UM7vrnqiIN3UY0AP7h5tuKbjwVo/0/da"><img src="http://feedads.g.doubleclick.net/~a/UM7vrnqiIN3UY0AP7h5tuKbjwVo/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/UM7vrnqiIN3UY0AP7h5tuKbjwVo/1/da"><img src="http://feedads.g.doubleclick.net/~a/UM7vrnqiIN3UY0AP7h5tuKbjwVo/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=a5Pb2Q2n6uQ:PyydrwzG00M:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=a5Pb2Q2n6uQ:PyydrwzG00M:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=a5Pb2Q2n6uQ:PyydrwzG00M:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=a5Pb2Q2n6uQ:PyydrwzG00M:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/a5Pb2Q2n6uQ" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/a5Pb2Q2n6uQ/the-n00b-filter-bad-ux-at-its-finest-in-eagle-pcb Pete Brown 3559 2012-01-27T03:55:54 MVC / Razor Style-Switcher – Quicky Heute möchte ich zeigen, wie man sich einen einfachen Style-Switcher mittels jQuery in einem MVC / Razor Projekt einbauen kann. Den Basis-Style setzen wir zu Beginn, wenn die Seite zum ersten Mal geöffnet wird. Wichtig ist, das wir eine Id setzen, um in unserer jQuery Funktion dieses Element wieder zu finden. &#60;link id=&#34;jQ&#34; href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; [...] http://feedproxy.google.com/~r/BigglesBlog/~3/IRGH_QW2X14/mvc-razor-style-switcher-quicky Mario Priebe 3558 2012-01-26T13:25:17 Maker Geek Roundup 006 for 1/25/2012 <p>The Maker Geek Roundup aggregates information of interest to makers everywhere. Topics include .NET Micro Framework, CNC, 3d Printing, Robotics, Microsoft Robotics Studio, Electronics, General Maker stuff, and more. If you have something interesting you've done or have run across, or you blog regularly on the topics included here, please send me the URL and brief description via the <a href="http://10rem.net/contact">contact link</a>.</p> <h3>3d Printing and CAD/CAM/CNC</h3> <ul> <li><a href="http://blog.ultimaker.com/2012/01/25/working-3d-printed-helicopter-blades/"> Working 3D printed helicopter blades</a> (Ultimaker)</li> <li><a href="http://richrap.blogspot.com/2012/01/slic3r-is-nicer-part-3-how-low-can-you.html"> Slic3r is Nicer - Part 3 - How low can you go?</a> (RichRap)</li> <li><a href="http://blog.rhino3d.com/2012/01/rhinocam-2012-is-latest-version-of.html"> RhinoCAM 2012 to Release February 6, 2012</a> (Rhino News)</li> <li><a href="http://blog.thingiverse.com/2012/01/25/the-3d-printing-troubadours-of-pocket-factory/"> The 3D Printing Troubadours of Pocket Factory!</a> (Thingiverse)</li> <li><a href="http://arduino.cc/blog/2012/01/10/circuit-milling-with-roland-imodela-now-available-on-arduino-store/"> Circuit Milling With Roland iModela, Now Available on Arduino Store</a> (Arduino Blog)</li> </ul> <h3>.NET Gadgeteer</h3> <ul> <li><a href="http://www.breakcontinue.com/2012/01/arduino-daisylink-module-for-gadgeteer.html"> Take a break: Arduino DaisyLink module for Gadgeteer</a> (Valentin Ivanov)</li> <li><a href="http://devhammer.net/hardware">Devhammer's Garage (Info on the IR LED module Andrew created)</a> (G. Andrew Duthie)</li> </ul> <h3>.NET Micro Framework General (Netduino, GHI FEZ, etc.)</h3> <ul> <li><a href="http://www.pix6t4.com/">PIX-6T4 - The PIX-6T4 Game Console</a> (Fabien Royer, Bertrand LeRoy)</li> <li><a href="http://blogs.msdn.com/b/netmfteam/archive/2012/01/09/new-netmf-book-out-for-v4-1-and-v4-2.aspx"> New NETMF Book out for v4.1 and v4.2</a> (Colin Miller)</li> <li><a href="http://mikedodaro.net/2012/01/21/text-to-speech-for-net-micro-framework/"> Text-To-Speech for .NET Micro Framework</a> (Marco Minerva)</li> <li><a href="http://mikedodaro.net/2011/12/14/xml-configuration-files-for-net-micro-framework-applications-english/"> XML Configuration Files for .NET Micro Framework Applications (English)</a> (Marco Minerva)</li> <li><a href="http://blogs.msdn.com/b/laurelle/archive/2012/01/21/using-a-light-transistor-sensor-and-a-led-to-create-a-detector.aspx"> Using a light transistor sensor and a led to create a detector</a> (Laurent Ellerbach)</li> </ul> <h3>Arduino</h3> <ul> <li><a href="http://arduino.cc/blog/2012/01/16/control-an-lcd-with-a-595-shift-register/"> Control An LCD with a 595 Shift Register</a> (Arduino Blog)</li> </ul> <h3>Other Micro Controllers (PIC, AVR, ARM and more)</h3> <ul> <li><a href="http://www.mikroe.com/eng/news/view/418/easypic-pro-v7-is-released/"> EasyPIC PRO v7 is released!</a> (MikroElektronika)</li> <li><a href="http://dangerousprototypes.com/2012/01/25/breakout-board-for-the-44pin-qfn-atxmega-microcontroller/"> Breakout board for the 44pin QFN ATxmega microcontroller</a> (Dangerous Prototypes)</li> </ul> <h3>General Electronics and Hardware Hacking</h3> <ul> <li><a href="http://blog.ianlee.info/2012/01/quick-tip-making-momentary-buttons.html"> Making Momentary Buttons Breadboard Friendly</a> (Software and Sawdust)</li> <li><a href="http://circuit-zone.com/index.php?electronic_project=659">555 LED Pulsing Breathing Circuit</a> (Circuit Zone)</li> <li><a href="http://citizenscientistsleague.com/2012/01/24/a-curiously-low-noise-amplifier/"> A Curiously Low Noise Amplifier</a> (Citizen Scientists League)</li> <li><a href="http://circuit-zone.com/index.php?electronic_project=658">1.5V FM Broadcast Transmitter</a> (Circuit Zone)</li> <li><a href="http://dangerousprototypes.com/2012/01/25/the-phone-box/">The phone box</a> (Dangerous Prototypes)</li> <li><a href="http://www.electronics-lab.com/blog/?p=16394">LED brightness controller with 64 taps</a> (Electronics Lab)</li> </ul> <h3>Robotics and Robotics Studio</h3> <ul> <li><a href="http://robotgrrl.com/blog/2012/01/23/brrd-brain-board-more-progress-less-zigzags/"> Brrd Brain Board: More progress &amp; less zigzags</a> (RobotGrrl)</li> </ul> <h3>Clocks</h3> <ul> <li><a href="http://hackaday.com/2012/01/25/tutorial-build-a-manifold-clock-for-10/"> Tutorial: Build a manifold clock for $10</a> (Hack a Day)</li> </ul> <h3>Synthesizers, MIDI and Electronic Instruments</h3> <ul> <li><a href="http://highlyliquid.com/blog/?p=852">Meng Qi's Wicki-Hayden MIDI Controller</a> (Highly Liquid)</li> <li><a href="http://highlyliquid.com/blog/?p=839">Trojan Chorus: Pinata as MIDI Controller</a> (Highly Liquid)</li> </ul> <h3>Photography</h3> <ul> <li><a href="http://www.diyphotography.net/diy-small-circular-softboxes">DIY: Small Circular Softboxes</a> (DIY Photography)</li> <li><a href="http://www.diyphotography.net/super-high-speed-footage-of-a-60d-exposure-cycle"> Super High Speed Footage Of A 60D Exposure Cycle</a> (DIY Photography)</li> <li><a href="http://www.diyphotography.net/using-a-double-flash-bracket-to-add-modeling-light-to-your-setup"> Using A Double Flash Bracket To Add Modeling light To Your Setup</a> (DIY Photography)</li> </ul> <h3>General Maker</h3> <ul> <li><a href="http://brando.ie/tweetsweets/">Tweet Sweet</a> (Brando)</li> <li><a href="http://vonkonow.com/wordpress/2012/01/software-for-home-automation/"> Software for web controlled home automation</a> (Johan von Konow)</li> </ul> <h3>Retro Computing and Commodore!</h3> <ul> <li><a href="http://www.mos6502.com/general-blogness/retro-game-fair-the-netherlands/"> Retro Game Fair, The Netherlands</a> (MOS6502)</li> <li><a href="http://www.rgcd.co.uk/2011/12/c64anabalt-c64-2011.html">C64anabalt (C64) (2011)</a> (RGCD)</li> <li><a href="http://www.mos6502.com/friday-commodore/commodore-inside/">Commodore Inside!</a> (MOS6502)</li> <li><a href="http://www.rgcd.co.uk/2012/01/hydra-castle-labyrinth-pc.html"> Hydra Castle Labyrinth (PC)</a> (RGCD)</li> </ul> <h3>Misc Geek</h3> <ul> <li><a href="http://vihart.com/blog/spongebob/">Open Letter to Nickelodeon, Re: SpongeBob's Pineapple under the Sea</a> (Vi Hart)</li> <li><a href="http://this8bitlife.com/this-8-bit-life/2012/1/13/cyberpunk-style-workstation-is-pretty-sexy.html"> Cyberpunk style Workstation is Pretty Sexy</a> (This 8 bit Life)</li> <li><a href="http://this8bitlife.com/this-8-bit-life/2012/1/24/amazing-legend-of-zelda-zora-armor-cosplay.html"> Amazing Legend of Zelda Zora Armor Cosplay</a> (This 8 bit Life)</li> </ul> <h3>Just Awesome</h3> <ul> <li><a href="http://www.youtube.com/watch?feature=player_embedded&amp;v=1wwilCs4Jqg"> You'll either thank me, or burn my house down for this one. Either way, those brain cells aren't coming back.</a> (YouTube)</li> </ul> <p><a href="http://feedads.g.doubleclick.net/~a/Clsbo6NvjY3r5PY3v6WfNJlE3fM/0/da"><img src="http://feedads.g.doubleclick.net/~a/Clsbo6NvjY3r5PY3v6WfNJlE3fM/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/Clsbo6NvjY3r5PY3v6WfNJlE3fM/1/da"><img src="http://feedads.g.doubleclick.net/~a/Clsbo6NvjY3r5PY3v6WfNJlE3fM/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=PVD4L9hWMF4:odUC5g5Bsdw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=PVD4L9hWMF4:odUC5g5Bsdw:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=PVD4L9hWMF4:odUC5g5Bsdw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=PVD4L9hWMF4:odUC5g5Bsdw:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/PVD4L9hWMF4" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/PVD4L9hWMF4/maker-geek-roundup-006-for-1-25-2012 Pete Brown 3557 2012-01-25T20:08:25 Jetzt Knockout.js lernen: Formularvalidierung mit Undo <p>In <a title="Jetzt Knockout.js lernen: Observables erweitern" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx">Jetzt Knockout.js lernen: Observables erweitern</a> der <a title="Serie zu Knockout.js" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie zu Knockout.js</a> wurde gezeigt, wie Observables erweitert werden können. Als Beispiel diente eine Validierung auf Basis von einzelnen Observables. Nun hängen diese in der Regel aber in einem Formular zusammen, wodurch eine Gesamtvalidierung und eine damit verbundene Freischaltung von Schaltflächen bzw. weiteren Interaktionsmöglichkeiten einhergeht. Dieser Beitrag greift das Validierungsbeispiel auf und erweitert es im Rahmen dieser Anforderung.</p> <h2>Ausgangspunkt</h2> <p>Den Ausgangspunkt dieses Beitrags bietet dieses <a title="Beispiel zu Knockout.js - Observables erweitern" href="http://jsfiddle.net/zHFEU/" target="_blank">Beispiel</a>, zu dem Florian einen sinnvollen <a title="Erweiterungswunsch zur Validierung unter Knockout.js" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx#id_ec633037-a724-451b-96c2-0ce6a98d6b1b" target="_blank">Erweiterungswunsch</a> hinterlassen hat:</p> <blockquote> <p>Interessant wäre noch ein Save-Button, der nur aktiv ist, wenn es keine Fehler auf dem gesamten Formular mehr gibt, sowie ein Cancel- bzw. Undo-Button der bei eventuellen Fehlern, die Werte zurücksetzt auf den ursprünglichen Wert.</p> </blockquote> <p>Aus meiner Sicht handelt es sich hierbei um eine gängige Anforderung, die ich natürlich aufgegriffen habe. Wer den Hintergrund des Beispiels erfahren möchte, kann dies <a title="Jetzt Knockout.js lernen: Observables erweitern" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx" target="_blank">hier</a> vollständig nachlesen. Als Zusammenfassung: Das Beispiel zeigt, wie ein eigener Extender für Observables unter <a title="Knockout.js" href="http://knockoutjs.com/" target="_blank">Knockout.js</a> geschrieben werden kann, um einzelne Observables zu validieren. Als Validierung wurde eine Erweiterung hinsichtlich Pflichtfelder implementiert. Eine Gesamtvalidierung war nicht vorgesehen.</p> <h2>Gesamtvalidierung</h2> <p>Aktuell wird jedes Observable einzeln validiert. Entsprechend der Anforderung sollen nun zwei Schaltflächen für das Speichern, als auch das Zurückstellen auf die zuletzt gültigen Werte (im Fehlerfalle) hinzugekommen. Damit diese auch entsprechend freigeschalten sind, muss das ViewModel bekannt geben, in welchem Status es sich gesamt befindet. Dieser ergibt sich aus den Status jedes einzelnen Observables. Dies können wir uns berechnen lassen (<font face="Courier New">ko.computed</font>) und nennen wir <font face="Courier New">hasErrors</font>.</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">self.hasErrors = ko.computed(<span style="color: #0000ff">function</span>() </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">{ </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">return</span> self.title.hasError() || </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.author.hasError() || </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.pages.hasError(); </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">});</pre> <!--CRLF--></div> </div> <blockquote> <p><strong>Hinweis</strong>: <font face="Courier New">self</font> ist eine Referenz auf <font face="Courier New">this</font> und wurde im Vergleich zum originalen Beispiel hinzugefügt, um innerhalb der anonymen Funktion auf die Eigenschaften und Funktionen des ViewModels zugreifen zu können.</p> </blockquote> <p>Damit könnten nun auch bereits die beiden neuen Schaltflächen gesteuert werden:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">=&quot;button&quot;</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">=&quot;Save&quot;</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;disable: hasErrors&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">input</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">=&quot;button&quot;</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">=&quot;Undo&quot;</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;enable: hasErrors&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">input</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Auf die Speichern-Schaltfläche möchte ich an dieser Stelle nicht weiter eingehen, viel spannender ist hier der Undo-Button. Dieser soll ja - im Fehlerfalle - die zuletzt gültigen Werte hinterlegen. Dies bedeutet, dass diese erfasst werden müssen.</p> <p>Nun ist es so, dass die einzelnen Eingabefelder eine <font face="Courier New">value</font>-Bindung besitzen und das Durchschreiben der Werte via <font face="Courier New">valueUpdate</font> auf den Wert <font face="Courier New">afterkeydown</font> gesetzt wurde. Das ViewModel wird also in Echtzeit aktualisiert. Darauf können wir also nicht aufbauen, da der Benutzer beispielsweise per Backspace alle Zeichen von “Beispiel” löschen könnte, durch die Echtzeit-Aktualisierung würde der zuletzt valide Wert durch “B” repräsentiert. Worauf wir uns jedoch hängen können ist das Verlassen des Fokus, also dem <font face="Courier New">OnBlur</font>-Ereignis.</p> <p>Dazu brauchen wir eine Funktion, die uns den beim Verlassen vorhandenen - gültigen - Wert übernimmt:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">self.updateLastValidValue = <span style="color: #0000ff">function</span>(observable) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">if</span> (!observable.hasError()) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> observable.lastValidValue(observable());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> Als Parameter wird das an das Eingabefeld gebundene Observable übergeben. So kann auch auf die durch den Extender geschriebenen Eigenschaften (siehe <font face="Courier New">hasError</font>) zugegriffen werden. Läuft die Validierung ohne Fehler, dann ist ein entsprechender Wert vorhanden und kann übernommen werden. Hierzu wird eine neue Eigenschaft namens <font face="Courier New">lastValidValue</font> mit dem Wert gesetzt. <p>Damit dies nun bei allen Eingabefeldern berücksichtigt wird, ist deren Bindung zu aktualisieren, hier an Hand der Titel-Eingabe:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;input data-bind=<span style="color: #006080">'value: title, valueUpdate: &quot;afterkeydown&quot;, event: { blur: function() { updateLastValidValue(title); }}'</span> /&gt;</pre> <!--CRLF--></div> </div> <p>So werden die Werte nun weiterhin in Echtzeit übernommen und zusätzlich beim Verlassen des Feldes die Funktion <font face="Courier New">updateLastValidValue</font> des ViewModels aufgerufen. Das ist allerdings noch nicht alles, denn ein etwaiger initial gesetzter Wert würde nicht berücksichtig werden. An dieser Stelle bietet es sich an, den bereits existierenden Extender (<font face="Courier New">requireField</font>) zu erweitern:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 500px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.extenders.requiredField = <span style="color: #0000ff">function</span>(target, message) { </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.hasError = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.validationMessage = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.lastValidValue = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">function</span> validate(newValue) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.hasError(newValue ? <span style="color: #0000ff">false</span> : <span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.validationMessage(newValue ? <span style="color: #006080">&quot;&quot;</span> : message || <span style="color: #006080">&quot;* required&quot;</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">function</span> setInitialValueIfValid() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">if</span> (!target.hasError()) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.lastValidValue(target());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> } </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> validate(target());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> setInitialValueIfValid();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.subscribe(validate);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">return</span> target;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Neu ist die Funktion <font face="Courier New">setInitialValueIfValid()</font>. Diese wird beim Erstellen von <font face="Courier New">requiredField</font> durchlaufen (siehe den Aufruf direkt nach der initialen Validierung) und, wenn ein Wert vorhanden ist, <font face="Courier New">lastValidValue</font> mit dem entsprechenden Wert beschrieben.</p> <p>Zu guter Letzt fehlt noch das tatsächliche Zurückschreiben der Werte, wenn die Undo-Schaltfläche geklickt wird. Dazu benötigen wir eine Funktion, nennen wir sie <font face="Courier New">resetToValidValues</font>:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">self.resetToValidValues = <span style="color: #0000ff">function</span>() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.title(self.title.lastValidValue());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.author(self.author.lastValidValue());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.pages(self.pages.lastValidValue());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Außerdem muss die <font face="Courier New">click</font>-Bindung auf der Schaltfläche gesetzt werden:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">=&quot;button&quot;</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">=&quot;Undo&quot;</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;enable: hasErrors, click: resetToValidValues&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">input</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Fertig ist die Gesamtvalidierung des Formulars, inklusive der Möglichkeit, die zuletzt gültigen Werte zurück zu schreiben.</p> <h2>Ergebnis</h2> <p>An dieser Stelle nun zwei Screenshots, die die Implementierung visuell veranschaulichen. Der erste Screenshot zeigt das gesamte Formular ohne Validierungsfehler:</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_123.png"><img style="background-image: none; border-right-width: 0px; margin: 0px 10px 10px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="Knockout.js: Erfolgreiche Validierung eines Formulars" border="0" alt="Knockout.js: Erfolgreiche Validierung eines Formulars" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_77.png" width="604" height="309" /></a></p> <p>Und hier nun im Fehlerfalle:</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_124.png"><img style="background-image: none; border-right-width: 0px; margin: 0px 10px 10px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="Knockout.js: Validierungsfehler eines Formulars" border="0" alt="Knockout.js: Validierungsfehler eines Formulars" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_78.png" width="604" height="306" /></a></p> <h2>Download / Showcase</h2> <p>Wie immer gibt es auch dieses Beispiel vollständig für eigene Tests. Unter <a href="http://jsfiddle.net/VgvuK/" target="_blank">http://jsfiddle.net/VgvuK/</a> findet sich die entsprechende Spielwiese. Wer möchte, kann sich dies auch unterhalb ansehen, sollte hierbei allerdings auf die Nutzung eines IE verzichten:</p> <p><iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/nitronic/VgvuK/embedded/" frameborder="0" allowfullscreen="allowfullscreen"></iframe></p> <h2>Fazit</h2> <p>Die notwendigen Erweiterungen der “Feld-Validierung” konnte in meinen Augen mit sehr wenig Aufwand auf eine Gesamtvalidierung erweitert werden, wodurch für den Benutzer sehr schnell ersichtlich ist, welche Aktionen zur Verfügung stellen. Ein äußerst sauberer Weg, durch die mögliche Auftrennung.</p> <h2>Feedback</h2> <p>Gerne nehme ich - wie immer - konstruktive Kritik, Anregungen und Anmerkungen entgegen. Einfach einen kurzen Kommentar hinterlassen, es findet sich sicherlich die eine oder andere Verbesserungsmöglichkeit.</p> <h2>Jetzt Knockout.js lernen: Die Serie</h2> <ul> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Slides-und-Beispiele.aspx">Jetzt Knockout.js lernen: Slides und Beispiele</a> </li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formulare-und-Listen-binden.aspx">Jetzt Knockout.js lernen: Formulare und Listen binden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Template-verwenden.aspx">Jetzt Knockout.js lernen: Template verwenden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx">Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx">Jetzt Knockout.js lernen: Observables erweitern</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formularvalidierung-mit-Undo.aspx">Jetzt Knockout.js lernen: Formularvalidierung mit Undo</a> (dieser Teil)</li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Mapping.aspx">Jetzt Knockout.js lernen: Mapping</a></li> </ul> http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formularvalidierung-mit-Undo.aspx Norbert Eder [MVP] 3556 2012-01-25T11:30:00 Now, more than ever, you need a designer <p>Before joining Microsoft, I was a consultant, primarily building desktop applications using a variety of MS technologies. During my last couple years there, Silverlight was a large part of that both for full apps and as parts of larger web sites. Prior to that, it was WPF, Windows Forms, VB, HTML and even SharePoint.</p> <blockquote> <p><strong>tl;dr:</strong> The point of this long post is not to show you examples of good design or pick apart existing designs. I'm not really qualified to do that. However, I can point out the need for design and give you some small amount of ammunition when presenting this to your own management. Visual and UX Design matters, even for internal apps, often far more than architecture or the other things we focus on. You'll be more efficient with a designer on-team, who you can iterate with. The world is evolving and we need to keep up. Go hire one now.</p> <p><strong>tl;dr the tl;dr:</strong> Hire a UX/Interaction designer.</p> </blockquote> <p><strong>The company I worked at wouldn't hire a UX/Visual designer.</strong> Why? Because they felt they could <strong>never sell the benefit of good UX and visual design to customers</strong>, and couldn't keep a designer busy 100% of the time. The problem was, without a portfolio of well-designed UI and sites, it became self-fulfilling. I had contended that if they hired one, they'd be amazed at just how busy that person would end up being. Quite frankly, they just didn't see the value.</p> <p>It got to the point that when I walked into my management's office, they'd be like "You're not going to tell us to hire a designer again, are you?"</p> <p><em>You may even be</em> <a href="http://10rem.net/blog/2010/11/19/windows-forms-to-xaml-do-i-really-need-a-designer-for-silverlight-or-wpf-applications" target="_blank"><em>thinking the same thing</em></a><em>, this post may see like Déjà vu if you've followed my blog for a few years :) I took a more conservative approach to the problem in that 2010 post. Basically saying the same types of things</em> <a href="http://www.hanselman.com/blog/SkipIntroCSS3IsTheNewFlash.aspx" target="_blank"><em>Scott Hanselman recently pointed out in his Skip Intro post</em></a><em>: don't over do it, and don't use things just because they're there. In that post, I argued that if you want to just keep making the same old battleship gray applications, you don't *need* a designer. However, I still think you can benefit from one in your projects regardless. So, I'm going to backtrack a little bit and deliver a more absolute form of the message: yes, you need a designer. Whether it is HTML/JS/CSS, or XAML/C#/VB, or something else, you should have design talent on staff.</em></p> <p>For a bit, we toyed around with the idea of partnering with an outside firm. The problem was, there was too much of a "hand off" approach to the design. Without having the design talent on-staff, you couldn't iterate during the project. The design was passed in at the beginning and then slowly destroyed by the developers (or people like me who had good UX but horrible Graphics design skills) over time.</p> <p><a href="http://bizarrocomic.blogspot.com/2010/06/tough-guy-ugly-baby-free-show.html" target="_blank"><img src="http://10rem.net/media/82999/Windows-Live-Writer_a2371b66e292_D4C8_image_22.png" width="270" height="317" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I likened it to other disciplines, like partnering with a firm just to design the database. Sure, design it all up-front and then make the project live with that throughout the rest of the lifecycle. No one found that to be a sane idea, but still couldn't see the parallel with UX and Graphics design.</p> <p>Now, in their defense, the company primarily worked on large-scale transaction processing systems. However, the business had slowly evolved to include more commercial work, and more intranet and internet sites for these same customers. Also, in their defense, our customers felt the same way. They wanted really great UX and graphics, but somehow felt those things simply fell from the sky. They didn't want to pay for designers on projects either. <strong>They were still stuck in the team makeup and ideas that worked for mainframe applications and, as in most cases, haven't moved on.</strong></p> <blockquote> <p>One thing the enterprise development community in general has been slower to adopt is the idea that you really do need visual and UX design talent on staff. <strong>Application success or failure rarely comes from architecture, or reuse, or whether or not you use stored procedures or which ORM you use.</strong> Those are things we do to meet other, more internal, goals, most of which users don't care about in the least, many of which offer more perceived benefit than actual real project benefit. As Scott mentioned in another post, <a href="http://www.hanselman.com/blog/YourUsersDontCareIfYouUseWebSockets.aspx" target="_blank">users don't care if you use Web Sockets</a>. Sure, if you really botch the architecture, the app may be unusable, but beyond that, your investment is better spent elsewhere.</p> </blockquote> <p><a href="http://www.flickr.com/photos/binaryape/458758810/" target="_blank"><img src="http://10rem.net/media/83004/Windows-Live-Writer_a2371b66e292_D4C8_image_16.png" width="650" height="139" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <h3>Knowing Your Limits</h3> <p>Yesterday, <a href="http://www.hanselman.com/blog/SkipIntroCSS3IsTheNewFlash.aspx" target="_blank">Scott Hanselman blogged about the emerging trend to throw a dog's breakfast of features into HTML5/CSS3/JS sites</a>. He and I chatted about it for a little, and felt I should write something up here as well. Go read his post as well.</p> <p><strong>It's natural for developers to push the boundaries when a new technology comes out.</strong> That's how we learn. That's fine for demo sites in developer echo chambers and <a href="http://www.youtube.com/results?search_query=c64+compo" target="_blank">compos</a>, but <strong>don't do it in front of actual users</strong>. It's too easy to have your app look and perform like a Pirate's idea of wealth: all money and no style. Scott has links to a few examples of that in his post. There are a couple other good ones in the comments.</p> <p>How did we get to this point? Why do so many still not even realize we're here?</p> <h3>Modern Development</h3> <p>First, a little history:</p> <p><img src="http://10rem.net/media/83009/Windows-Live-Writer_a2371b66e292_D4C8_image_40.png" width="213" height="155" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/><a href="http://www.flickr.com/photos/vax-o-matic/5625208727/" target="_blank"><img src="http://10rem.net/media/83014/Windows-Live-Writer_a2371b66e292_D4C8_image_35.png" width="223" height="155" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a><img src="http://10rem.net/media/83019/Windows-Live-Writer_a2371b66e292_D4C8_image_38.png" width="209" height="155" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/>&nbsp;</p> <p><img src="http://10rem.net/media/83024/Windows-Live-Writer_a2371b66e292_D4C8_image_39.png" width="192" height="245" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/><a href="http://www.amazon.com/Wordperfect-5-1-Toolbox-Keyboard-Template/dp/078211007X/ref=cm_cmu_pg__header" target="_blank"><img src="http://10rem.net/media/83029/Windows-Live-Writer_a2371b66e292_D4C8_image_43.png" width="453" height="245" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I don't wear a suit to work. In fact, I work from home, so what I wear barely counts as clothing some days. I also don't sit in front of a highly non-ergonomic greenscreen device, trying to remember arcane codes (ok, well I do when I'm messing around with microcontrollers or old computers). Why? Because the world has moved on from that. Gradually over the years, more and more UX thought has been given to applications.</p> <ul> <li>When applications were just command-line affairs, the only UX you really had to worry about was how hard the transaction codes were to remember. Most of the UX was the huge three-ring binder manual or the multiple volume boxed sets with textured cloth covers. Users often spent as much time in that as interacting with the screen, especially during their first X months. <strong>Graphics were Escher prints you hung on the wall.</strong> Computers were <strong>supposed to be hard</strong>, for some unknown reason. Engineering ruled the experience, and we didn't know any better.</li> <li>When they became full-screen character mode applications (Curses, anyone? No? How about the <a href="http://www.lanet.lv/simtel.net/msdos/turbo_c.html" target="_blank">windowing libraries for DOS TurboC</a>/C++ apps? IBM 3270? No? Pah. Youngsters.) there was more UX to worry about, but the layout and workflow choices were still so limited as to make it easier for a dev with a good eye to come up with something decent. <strong>Graphics were things scientists used for data visualization</strong>. Design was for <a href="http://www.thedieline.com/blog/2009/11/17/the-evolution-of-the-coca-cola-contour-bottle.html" target="_blank">companies making physical products customers had to hold in their hands</a>. An entire industry was built around plastic-coated cardboard keyboard templates to help you remember all the keyboard shortcuts required to navigate important applications, especially for DOS. I blame WordPerfect.</li> <li>When apps became battleship gray forms over data (white 2d forms first), <strong>UX consisted mainly of making sure you had a decent tab order and "fit as much as possible" on a single screen</strong>. Navigation often resembled those character mode applications we were upgrading/scraping/rewriting because our users didn't know any better, and we didn't know how to show them something else and prove it would be more efficient and/or easier. Things started to go south here. <strong>Graphics were for games and maybe 16x16 16 color icons</strong>. <a href="http://www.youtube.com/watch?v=lAD6Obi7Cag" target="_blank">Animation was for music videos</a>.</li> <li style="list-style: none"> <ul> <li>Early on, apps were even just black and white instead of gray. But when the gray "3d" chiseled look started coming into vogue late in Win 3.11's lifetime (there was a special common controls library just for it), developers went nuts and had apps that looked like Mayan pyramids. Borders within borders within borders. Uuuuugly. And I did it too. It was new. It was exciting, and it was abused.</li> </ul> </li> <li>When the web started making inroads into application development, UX became further complicated. First, the workflow options for early web apps really blew. <strong>Many apps had two buttons "Submit" and "Reset" and a big form above them</strong>. Validation and all other processing mostly took place server-side. Sometimes, we got clever, and sent all the data through an iframe to avoid a full page refresh. We made excuses because that's how all the other apps were working, and we didn't take much inspiration from branded experiences. However, at least they often looked nicer than those battleship gray apps. That made a real impression on people. Hmm. Graphics. Colors. Whitespace. Animation was for dancing hamsters or Pixar.</li> <li>And then the proliferation of consumer-oriented smart phones and tablets with great UX, driven in no small part by Apple, <strong>really brought UX and visual design to the front</strong>. It's not because the platform had a great API or something (I've never worked on it, but I hear the platform, from a programming and design standpoint is … interesting), but because it attracted a lot of designer types. It's no accident that companies like Apple and Microsoft chose design firms to create the first showcase prototypes of their new UI technologies. Now, even inside the enterprise, many customers expect this level of UX in their own applications. Graphics, colors, whitespace, touch, animation, easing and more all become important parts of the visual design, and it's <a href="http://kellabyte.com/2011/12/19/when-metro-design-falls-off-the-tracks/" target="_blank">very easy to get it wrong</a> unless you have a <a href="http://www.riagenic.com/archives/751" target="_blank">talent for design</a>.</li> <li style="list-style: none"> <ul> <li>Your users now and in the future are ones brought up on this crop of highly visual applications. What do you think they will have for expectations?</li> </ul> </li> </ul> <p>But now we're stuck, because we have the tools to do whatever we want, but haven't evolved the teams to effectively make use if them.</p> <p>We've had tools and platforms to build these types of apps for some time. Flash has been around forever, Silverlight since 2007, and finally, HTML5/CSS3 and its support in browsers is mostly catching up to where FutureSplash was in 1996. <strong>The capabilities are there, we just need to use them for good.</strong></p> <p>It's not just for consumer-oriented apps, either. As companies consolidate their development staff, expose more things outside the firewall, or work to support a large number of internal users, the line between consumer and enterprise development continues to thin.</p> <p>For grins, here are some apps I wrote in the 90s and early 2000-ish, where I happened to have saved some screen shots (I really wished I had saved more, especially the dBase/FoxPro apps I wrote for DOS and the VB3-6 and PowerBuilder ones for Windows 3.11). The first one was written for DOS entirely in Borland C++ (including the database) the others were, as I recall, .NET 1.0 beta versions of VB4 apps I wrote earlier. in the first one, I mainly just had to worry about tab order. In the second, the users were most comfortable with filling out an online version of the paper form (in fact, they'd have the paper form in front of them while doing this). The third was using the typical "Make it look like Outlook" model of the time.</p> <p><a href="http://10rem.net/media/83034/Windows-Live-Writer_a2371b66e292_D4C8_image_28.png" target="_blank"><img src="http://10rem.net/media/83039/Windows-Live-Writer_a2371b66e292_D4C8_image_thumb_10.png" width="400" height="188" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>That version actually looked quite a bit better on a giant 15" CRT, perched high enough up to count as neck-stretching exercise. :)</p> <p><a href="http://10rem.net/media/83044/Windows-Live-Writer_a2371b66e292_D4C8_image_24.png" target="_blank"><img src="http://10rem.net/media/83049/Windows-Live-Writer_a2371b66e292_D4C8_image_thumb_8.png" width="320" height="225" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a><a href="http://10rem.net/media/83054/Windows-Live-Writer_a2371b66e292_D4C8_image_26.png" target="_blank"><img src="http://10rem.net/media/83059/Windows-Live-Writer_a2371b66e292_D4C8_image_thumb_9.png" width="320" height="224" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Like so many other developers, I simply copied prevalent design trends of the time (how many devs have you talked to back in the day who said they were trying to make their app look like Outlook or Visual Studio/Visual Basic?). I got away with it, but you can see that it doesn't 100% work. <strong>I force fit some things due to the limited palette of visual design patterns at my disposal</strong>. Bonus points: battleship gray.</p> <h3>Is it Customer-Facing?</h3> <p>I've heard many people say that they'll only invest in UX for customer-facing stuff. But who is the customer?</p> <p>It depends on how you define your customer. For many IT shops, the customer works for the same company. For sales apps, many times the field laptop/tablet application is seen by both a sales person and the customer. Many of the same things you'd do in a consumer application apply to these same customers, but <strong>because they're forced to use your app, they don't get the same attention to UX</strong> that external true consumer apps do.</p> <p>Here's a list of some common types of applications being built today, or in the near future. Tell me where UX doesn't matter? Which is customer-facing?</p> <ul> <li>Call center apps inside an organization</li> <li>The departmental day-to-day work application at your company</li> <li>Consumer touch-oriented apps on phones and tablets</li> <li>Consumer apps on the desktop</li> <li>Enterprise shrink-wrapped products for the desktop</li> <li>Your intranet site</li> <li>That forms-over-data application everyone in the company uses</li> <li>Time and expense reporting systems</li> <li>Your one-off shell script to update a bunch of database tables</li> </ul> <p>Ok, so I through the shell script in there just to have something where you could get away without UX. Of course, there's often some design put in there too. Did you take a moment to set up different command line arguments? UX, just no pretty graphics :)</p> <blockquote> <p>How many times have you heard "Sorry, the computer is really slow today" when you talk to someone at a call center? Typically this is because it takes forever to accomplish anything on their crappy apps. Sometimes, of course, they have a 9600bps serial connection from wherever they are to wherever the data is, but most of the time it's just a crap UI that takes forever to help you, or they've forgotten how to do a certain thing in the UI because it's so non-intuitive. I've seen these apps up close. I've seen the massive amount of training that people have to go through to learn them. Some of the most horrendous UX I've ever seen was in an in-house call center application to take outage reports at a power company - one of their most critical ways to help customers. Are they not worth better UX?</p> </blockquote> <p><strong>UX is important for all those applications</strong>. <strong>The more the application gets used, the more good or bad UX comes into play.</strong> It's not just about selling the application and getting 5 stars, it's also about making others more efficient and making their lives a little less hellish. Making them feel good while using it is a nice bonus.</p> <p>Most developers, let's not kid ourselves, are simply not well-equipped to make good UX decisions. They're too embedded in the software, <strong>and too aware of the underlying code and architecture changes that a change in UX would require</strong> so they push back, or don't even consider the things that really should be done. Or they build the system back-to-front and the UI, by necessity, resembles the data structures they used throughout the app. Oh, and changing the UX of an application is one of those things that becomes becomes far more expensive as you wait. UX is the entire workflow of the application. It touches just about everything.</p> <p><a href="http://www.flickr.com/photos/drewmaughan/6425581707/in/photostream/" target="_blank"><img src="http://10rem.net/media/83064/Windows-Live-Writer_a2371b66e292_D4C8_image_5.png" width="650" height="127" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>What about graphics? In companies, "Designer" often encompasses both UX and Visual/Graphics design. Often, good UX designers also have a decent visual design background as well, just as developers often have more than one related skill.</p> <h3>Just go Find Some Free Icons</h3> <p>That's something I heard over and over, and you probably hear it too. "Go find some clipart" or "go find a free icon set". Ugh. Invariably, the icon set would only have 1/3 of the required icons, and the remaining ones would have to magically appear. I was able to do that (Axialis Icon Designer saw a lot of me back then), not anywhere near as well as the original designer, but well enough. There were many other applications I saw which had different icons from various sets, with incompatible styles, all mashed together on the same toolbar.</p> <p>The same thing applies to site themes, css, and more. "Just go find a template" is almost as bad as "go find some free icons".</p> <p>There are more choices today, but you still end up with those toolbars (or pages, or whatever) where half the icons look good, and the other half show some bad image with a three letter acronym over it.</p> <p>They all have <a href="http://www.bing.com/images/search?q=splash+screen" target="_blank">splash screens</a> though, and with clip art. Maybe even from MS Word. :)</p> <p><a href="http://office.microsoft.com/en-us/images/results.aspx?qu=screenbeans" target="_blank"><img src="http://10rem.net/media/83069/Windows-Live-Writer_a2371b66e292_D4C8_image_19.png" width="650" height="68" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>In some cases, the UI starts to resemble that PTA fund raising newsletter sent home with your kid, or that office baby shower announcement :)</p> <p>When I did work on the graphics and visual design of applications a decade or more ago, I had to do it while hiding in the corner. I had a customer PM come over and tell me "don't let anyone see you drawing pictures". Clearly the visual design and icons and other elements "just appeared" from nowhere. The effort was not respected, but the outcome was expected.</p> <p>The problem here is not that developers don't know their limits (most do), but that <strong>management doesn't see the UX/Graphics Designer role as useful. It's not seen as a science, it's art. And just like in public schools, it's art that gets cut first</strong>.</p> <blockquote> <p>An introduction to Interaction Design should be a required subject for all Computer Science and MIS majors. Not because I would expect them to learn to be great at it, but because they need good solid exposure to it so they know something's possible, even if they don't have the required skills/talent. They'll see there's more science to it than they thought. In fact, Psychology backgrounds often help in UX design. It helps that most Psychology majors are otherwise unemployed :)</p> </blockquote> <p>Like I said to Scott "The more freedom and power you have, the more you need someone to tell you what <strong><span>not</span></strong> to do." You need this person on your team.</p> <h3>Who is On Your Team?</h3> <p>If you are on a team larger than two people, I bet you have a project manager, a dedicated DBA/database designer (or someone who doubles as DBA and database designer while also coding queries and services and whatnot), a front-end programmer, a back-end programmer, and maybe even a tester. Maybe you have an analyst or two, although those seem to be getting scarce. Larger teams tend to have more finely grained roles.</p> <p><a href="http://10rem.net/media/83074/Windows-Live-Writer_a2371b66e292_D4C8_image_10.png" target="_blank"><img src="http://10rem.net/media/83079/Windows-Live-Writer_a2371b66e292_D4C8_image_thumb_3.png" width="650" height="150" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>These are all seen as specialized skills. Not everyone is great at designing databases. Not everyone is equally skilled at creating good services and APIs. Not everyone can line up fields on a form. With web apps, the division tends to be even stronger due to the different languages and platforms in use in the different tiers or sections of the application. Inside the development community we, and our managers, understand these limitations and try to work within them.</p> <p>But most importantly, not everyone can take a problem and visualize an appropriate workflow and user experience, low-cost prototype it, test it with the users, and make the changes required to make the best UX the project budget will support. It's a specialized skill like any other.</p> <p><strong>And like any other part of a software project, you don't want to just do it at the end. No lipstick on a pig. Get a designer involved form the beginning, and include them in your iteration plan. You'll be amazed at what you can accomplish.</strong></p> <p><strong>How have your battles gone on this front? Have you been able to convince management about the importance of UX/Interaction/Visual design? If so, how did you go about it? What worked? What didn't?</strong></p> <p><a href="http://feedads.g.doubleclick.net/~a/KHHu-7N4oo6qJuTl19Z110ijeVg/0/da"><img src="http://feedads.g.doubleclick.net/~a/KHHu-7N4oo6qJuTl19Z110ijeVg/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/KHHu-7N4oo6qJuTl19Z110ijeVg/1/da"><img src="http://feedads.g.doubleclick.net/~a/KHHu-7N4oo6qJuTl19Z110ijeVg/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=KjRU3dOJmeI:k0UEhhFKrnU:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=KjRU3dOJmeI:k0UEhhFKrnU:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=KjRU3dOJmeI:k0UEhhFKrnU:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=KjRU3dOJmeI:k0UEhhFKrnU:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/KjRU3dOJmeI" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/KjRU3dOJmeI/now-more-than-ever-you-need-a-designer Pete Brown 3555 2012-01-25T07:52:53 Google API jQuery UI 1.7.2 Theme Übersicht + Razor-Layout Im folgenden eine kleine Übersicht von Google API jQuery UI 1.7.2 Designs, so vorbereitet, dass diese schnell im Header eines Razor Layouts eingefügt werden können. base &#60;link href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/base/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; type=&#34;text/css&#34; /&#62; blitzer &#60;link href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/blitzer/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; type=&#34;text/css&#34; /&#62; dark-hive &#60;link href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/dark-hive/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; type=&#34;text/css&#34; /&#62; eggplant &#60;link href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/eggplant/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; type=&#34;text/css&#34; /&#62; flick &#60;link href=&#34;@Url.Content(&#34;http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/flick/jquery-ui.css&#34;)&#34; rel=&#34;Stylesheet&#34; type=&#34;text/css&#34; /&#62; [...] http://feedproxy.google.com/~r/BigglesBlog/~3/Trqwqv7QzS8/google-api-jquery-ui-1-7-2-theme-bersicht-razor-layout Mario Priebe 3554 2012-01-24T15:02:13 Jetzt Knockout.js lernen: Observables erweitern <p>Im vergangenen Teil der <a title="Jetzt Knockout.js lernen" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie zu Knockout.js</a> wurden <a title="Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx">benutzerdefinierte Bindungen</a> behandelt. Dieser Teil beschäftigt sich nun damit, wie Observables auf eigene Bedürfnisse hin erweitert werden können. <a title="Knockout.js" href="http://knockoutjs.com/" target="_blank">Knockout.js</a> unterstützt das Lesen und Schreiben von Werten, als auch Benachrichtigungen bei Wertänderungen. In zahlreichen Fällen (zum Beispiel Validierung) möchte man dieses Verhalten jedoch erweitern. Dieser Beitrag zeigt, wie dies umgesetzt werden kann.</p> <h2>Extender zur Validierung erstellen</h2> <p>Knockout.js bietet mit Extendern eine Erweiterungsmöglichkeit an, sich an Observables zu hängen. Das nachfolgende Beispiel erweitert Observables durch die Möglichkeit einer Validierung.</p> <p>Im ersten Schritt erstellen wir ein einfaches ViewModel, welches an eine View gebunden wird. Hier das ViewModel:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">function</span> BookViewModel(title, author, pages) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.title = ko.observable(title).extend({ requiredField: <span style="color: #006080">&quot;Please enter a title&quot;</span> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.author = ko.observable(author).extend({ requiredField: <span style="color: #006080">&quot;Please enter an author&quot;</span> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.pages = ko.observable(pages).extend({ requiredField: <span style="color: #006080">&quot;&quot;</span> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">}</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> viewModel = <span style="color: #0000ff">new</span> BookViewModel(<span style="color: #006080">&quot;Windows Presentation Foundation 4.5 - Einführung und Praxis&quot;</span>, <span style="color: #006080">&quot;Norbert Eder&quot;</span>, 400);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.applyBindings(viewModel);</pre> <!--CRLF--></div> </div> <p>Und passend dazu die View:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 300px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">=&quot;bookform&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Title<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: title'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Author<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: author'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Pages<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: pages'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Soweit nichts Neues. Nun möchten wir aber, dass eingegebene Werte validiert werden. In diesem Fall beschränke ich mich darauf, Felder als benötigt zu definieren. Ein Extender wird daher benötigt. Um einen Extender zu schreiben muss <font face="Courier New">ko.extenders</font> um eine Funktion erweitert werden, nennen wir sie <font face="Courier New">requiredField</font>. Diese enthält als Parameter das Ziel der Erweiterung und eine Nachricht (die für die Anzeige einer Meldung verwendet werden kann). </p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 300px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.extenders.requiredField = <span style="color: #0000ff">function</span>(target, message) { </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.hasError = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.validationMessage = ko.observable();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">function</span> validate(newValue) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.hasError(newValue ? <span style="color: #0000ff">false</span> : <span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.validationMessage(newValue ? <span style="color: #006080">&quot;&quot;</span> : message || <span style="color: #006080">&quot;* required&quot;</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> validate(target());</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> target.subscribe(validate);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">return</span> target;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Der Extender hängt dem Ziel zwei Eigenschaften <font face="Courier New">hasError</font> und <font face="Courier New">validationMessage</font> um. Die erste beschreibt, ob ein Validierungsfehler vorliegt, die zweite, welcher Fehler dies genau ist. Zusätzlich wird eine Funktion <font face="Courier New">validate</font> benötigt, welche die Validierung schlussendlich durchführt. Dies ist in diesem Beispiel recht einfach. So muss lediglich auf Vorhandensein eines Wertes geprüft werden. Ist dies nicht der Fall wird die über den Parameter <font face="Courier New">message</font> übergegebene Meldung angezeigt - wurde keine eigene Meldung übergeben, wird ein Fallback auf eine Standardmeldung verwendet.</p> <p>Via<font face="Courier New"> validate(target());</font> wird eine initiale Validierung durchgeführt. Per <font face="Courier New">target.subscribe(validate);</font> wird ein Abonnement hinsichtlich Wertänderungen hinzugefügt, damit auch in diesen Fällen die Validierung durchgeführt wird.</p> <p>Anschließend wird das ursprüngliche Observable zurück geliefert. Das war alles, was auf JavaScript-Seite zu implementieren war. Nun ist noch die View anzupassen, um etwaige Meldungen sichtbar zu machen.</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 300px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">id</span><span style="color: #0000ff">=&quot;bookform&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Title<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;css: { error: title.hasError }&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: title, valueUpdate: &quot;afterkeydown&quot;'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='visible: title.hasError, text: title.validationMessage'</span><span style="color: #0000ff">&gt;</span> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Author<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;css: { error: author.hasError }&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: author, valueUpdate: &quot;afterkeydown&quot;'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='visible: author.hasError, text: author.validationMessage'</span><span style="color: #0000ff">&gt;</span> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span>Pages<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h3</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">p</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;css: { error: pages.hasError }&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='value: pages, valueUpdate: &quot;afterkeydown&quot;'</span> <span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">='visible: pages.hasError, text: pages.validationMessage'</span><span style="color: #0000ff">&gt;</span> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">p</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Für die Erweiterung der View wurden insgesamt drei Schritte unternommen:</p> <ol> <li>Für die <font face="Courier New">input</font>-Felder wird die Aktualisierung des Wertes im ViewModel durch <font face="Courier New">valueUpdate</font> auf <font face="Courier New">afterkeydown</font> geändert, damit jede Änderung sofort im ViewModel wirksam wird und so schon zur Eingabe eine Validierung stattfinden kann. </li> <li>Es wird ein <font face="Courier New">span</font>-Element eingeführt. Dieses enthält eine <font face="Courier New">visible</font>-Bindung auf die Eigenschaft <font face="Courier New">hasError</font> - ist also nur sichtbar, wenn ein Validierungsfehler aufgetreten ist. Der anzuzeigende Wert des span-Elementes wird auf die <font face="Courier New">validationMessage</font> gebunden. </li> <li>Die umschließenden <font face="Courier New">p</font>-Tags wurden um eine <font face="Courier New">css</font>-Bindung erweitert. Dadurch wird bei Vorhandensein eines Validierungsfehlers eine CSS-Klasse <font face="Courier New">error</font> auf das Element gehängt, wodurch ein Styling einfach gemacht wird. So sieht es aus: </li> </ol> <a href="http://devtyr.norberteder.com/image.axd?picture=image_122.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="Knockout.js: Validierung per Extender" border="0" alt="Knockout.js: Validierung per Extender" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_76.png" width="604" height="152" /></a> <ol></ol> <h2>Download / Showcase</h2> <p>Das Beispiel kann unter <a href="http://jsfiddle.net/zHFEU/">http://jsfiddle.net/zHFEU/</a> getestet und bezogen werden. Untenstehend findet sich das JSFiddle in eingebundener Form, benötigt aber einen Browser ungleich IE.</p> <p><iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/nitronic/zHFEU/embedded/" frameborder="0" allowfullscreen="allowfullscreen"></iframe></p> <h2>Fazit</h2> <p>Dieses Beispiel hat gezeigt, wie einfach es möglich ist, ein Verhalten á la Validierung per Knockout.js und Extender zu realisieren. Auch hier gilt wieder, dass die Zuständigkeiten sauber getrennt sind und einfach gewartet werden. </p> <h2>Jetzt Knockout.js lernen: Die Serie</h2> <ul> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Slides-und-Beispiele.aspx">Jetzt Knockout.js lernen: Slides und Beispiele</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formulare-und-Listen-binden.aspx">Jetzt Knockout.js lernen: Formulare und Listen binden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Template-verwenden.aspx">Jetzt Knockout.js lernen: Template verwenden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx">Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx">Jetzt Knockout.js lernen: Observables erweitern</a> (dieser Teil)</li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formularvalidierung-mit-Undo.aspx">Jetzt Knockout.js lernen: Formularvalidierung mit Undo</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Mapping.aspx">Jetzt Knockout.js lernen: Mapping</a></li> </ul> http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx Norbert Eder [MVP] 3553 2012-01-24T10:00:00 Arduino The Documentary <p>For anyone interested in open source microcontroller development, or why boards are named with *duino type names, this 1/2 hour documentary is a great watch.</p> <p><a href="http://vimeo.com/18539129" target="_blank"><img src="http://10rem.net/media/82982/Windows-Live-Writer_Arduino-the-Documentary_989_image_6.png" width="662" height="382" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p><a href="http://vimeo.com/18539129" title="http://vimeo.com/18539129">http://vimeo.com/18539129</a></p> <p>Arduino.TheDocumentary.2010.English explains the origin of the Arduino in a relatively non-technical interview-based documentary style, with interviews for people involved in the creation, as well as key partners and users.</p> <p>Spanish version is also available.</p> <p><a href="http://feedads.g.doubleclick.net/~a/MZ6XpYcOxlOmDwl-zBngC-b56Wg/0/da"><img src="http://feedads.g.doubleclick.net/~a/MZ6XpYcOxlOmDwl-zBngC-b56Wg/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/MZ6XpYcOxlOmDwl-zBngC-b56Wg/1/da"><img src="http://feedads.g.doubleclick.net/~a/MZ6XpYcOxlOmDwl-zBngC-b56Wg/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cjfhIcd9A28:TL--vRci44g:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cjfhIcd9A28:TL--vRci44g:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=cjfhIcd9A28:TL--vRci44g:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=cjfhIcd9A28:TL--vRci44g:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/cjfhIcd9A28" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/cjfhIcd9A28/arduino-the-documentary Pete Brown 3552 2012-01-24T05:48:51 Yet Another Podcast #57–Kendo UI http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/_z42LvHCim0/ Jesse Liberty [MS] 3551 2012-01-23T19:49:57 Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen <p>In den vorangegangenen drei Teilen der <a title="Jetzt Knockout.js lernen" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie über Knockout.js</a> haben wir uns mit einer generellen Einführung, einer beispielhaften Bindung an Formulare und Listen als auch der Verwendung von Vorlagen beschäftigt. Dieser Teil beschäftigt sich nun mit der Möglichkeit der benutzerdefinierten Bindungen. So wird gezeigt, wann diese sinnvoll verwendet werden und wie sie denn überhaupt funktionieren.</p> <h2>Bindungen im Allgemeinen</h2> <p>Vordefinierte Bindungen stehen für die unterschiedlichsten Bereiche zur Verfügung. Abgedeckt werden Bindungen um Text und Darstellung zu kontrollieren (z.B. <font face="Courier New">visible</font>, <font face="Courier New">text</font>, <font face="Courier New">html</font>, <font face="Courier New">css</font> usw.), um mit Formular-Feldern zu arbeiten (<font face="Courier New">submit</font>, <font face="Courier New">checked</font>, <font face="Courier New">value</font>, etc.) und zur Ablaufsteuerung (<font face="Courier New">foreach</font>, <font face="Courier New">if</font>, <font face="Courier New">ifnot</font>, <font face="Courier New">with</font>). </p> <blockquote> <p>Eine vollständige Übersicht findet sich in den <a title="Jetzt Knockout.js lernen: Slides und Beispiele" href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Slides-und-Beispiele.aspx">Slides</a> des ersten Teils.</p> </blockquote> <p>Trotz der mannigfaltigen Möglichkeiten, besteht der Bedarf an eigenen Bindungen, gerade dann, wenn Werteänderungen spezielle Auswirkungen auf DOM Elemente haben sollen. Äußerst hilfreich sind benutzerdefinierte Bindungen auch dann, wenn Steuerelemente von Drittanbietern zum Einsatz kommen und so spezielle oder zusätzliche Attribute/Verhalten bedient werden sollen.</p> <h2>Grundlagen</h2> <p>Eine benutzerdefinierte Bindung wird durch folgenden Code erstellt, wobei <font face="Courier New">myBindingName</font> mit dem tatsächlichen Namen der Bindung zu ersetzen ist:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.bindingHandlers.myBindingName = {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> init: <span style="color: #0000ff">function</span>(element, valueAccessor, allBindingsAccessor, viewModel) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> },</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> update: <span style="color: #0000ff">function</span>(element, valueAccessor, allBindingsAccessor, viewModel) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Von Bedeutung sind die beiden Funktionen <font face="Courier New">init</font> und <font face="Courier New">update</font>:</p> <ul> <li><strong>init</strong>: Wird nur beim ersten Auswerten der Bindung aufgerufen. Ideal für die Initialisierung, beispielsweise dem Setzen von Eventhandlern. </li> <li><strong>update</strong>: Hier kann darauf reagiert werden, wenn sich gebundene Werte verändern. Der Aufruf erfolgt, wenn die Bindung das erste Mal angewandt wird und bei jeder Änderung des gebundenen Wertes. Sollte verwendet werden, um DOM Elemente auf Basis des geänderten Wertes zu manipulieren/aktualisieren. </li> </ul> <p>Beide Funktionen sind mit insgesamt vier Parametern ausgestattet:</p> <ul> <li><strong>element</strong>: Das DOM Element der Bindung. Dadurch ist es nicht notwendig, dem Element eine Id oder Ähnliches zuzuweisen. </li> <li><strong>valueAccessor</strong>: Dahinter verbirgt sich eine Funktion, die Zugriff zum gebundenen Wert gibt. Die Funktion liefert ein Observable zurück, nicht den tatsächlichen Wert. Wird ein Ausdruck für die Bindung verwendet, dann wird dieser zurück geliefert. </li> <li><strong>allBindingsAccessor</strong>: Eine Funktion, die alle Bindungen auf dieses DOM Element zurück liefert. </li> <li><strong>viewModel</strong>: Das ViewModel-Objekt, das via <font face="Courier New">ko.applyBindings</font> gebunden wurde. Bei einer verschachtelten Bindung wird das gebundene Datenelement zurückgeliefert. </li> </ul> <p>Doch sehen wir uns ein einfaches Beispiel an.</p> <h2>Beispiel</h2> <p>Sehen wir uns die benutzerdefinierte Bindung anhand eines kleinen Beispiels an. Es soll ein Eingabeformular geben. Hier können sowohl der Vor- als auch der Nachname erfasst werden. Die Daten werden darüber als Zusammenfassung angezeigt. Wird ein Wert in der Eingabe verändert, soll die Wertänderung in der Zusammenfassung mit Hilfe eines Fade-In-Effekts angezeigt werden.</p> <p>Dazu ist ein einfaches ViewModel notwendig, welches an das User Interface gebunden wird:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> viewModel = {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> firstName: ko.observable(<span style="color: #006080">&quot;Norbert&quot;</span>),</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> lastName: ko.observable(<span style="color: #006080">&quot;Eder&quot;</span>)</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.applyBindings(viewModel);</pre> <!--CRLF--></div> </div> <p>Die View selbst sieht dann so aus:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">class</span><span style="color: #0000ff">=&quot;overview&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>First name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: firstName&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>Last name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: lastName&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">class</span><span style="color: #0000ff">=&quot;data&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>First name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;value: firstName&quot;</span> <span style="color: #0000ff">/&gt;&lt;</span><span style="color: #800000">br</span><span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>Last name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;value: lastName&quot;</span> <span style="color: #0000ff">/&gt;&lt;</span><span style="color: #800000">br</span><span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Soweit noch nichts aufregendes. Bei einer Werteveränderung, wird dies sofort in der Zusammenfassung nachgezogen. Nun soll aber noch der Einblendeffekt hinzukommen. Dazu erstellen wir eine benutzerdefinierte Bindung, die auf eine Wertänderung reagiert (<font face="Courier New">update</font>-Funktion).</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">ko.bindingHandlers.fade= {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> update: <span style="color: #0000ff">function</span>(element, valueAccessor) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> $(element).hide().fadeIn(1000);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> } </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Da lediglich auf die Wertänderung, ohne Berücksichtigung des tatsächlichen Wertes, reagiert werden muss, ist die update-Funktion ausreichend. Auch muss auf den Wert selbst nicht zugegriffen werden. Stattdessen wird lediglich das Element versteckt und via <font face="Courier New">fadeIn</font> eingeblendet.</p> <blockquote> <p><strong>Hinweis</strong>: Müsste auf den Wert reagiert werden, kann dieser via <font face="Courier New">ko.utils.unwrapObservable(valueAccessor())</font> bezogen werden.</p> </blockquote> <p>Damit dies auch tatsächlich funktioniert, muss die benutzerdefinierte Bindung noch in der View gesetzt werden:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">class</span><span style="color: #0000ff">=&quot;overview&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>First name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: firstName, fade: firstName&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>Last name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">span</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;text: lastName, fade: lastName&quot;</span><span style="color: #0000ff">&gt;&lt;/</span><span style="color: #800000">span</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;</span><span style="color: #800000">div</span> <span style="color: #ff0000">class</span><span style="color: #0000ff">=&quot;data&quot;</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>First name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;value: firstName&quot;</span> <span style="color: #0000ff">/&gt;&lt;</span><span style="color: #800000">br</span><span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span>Last name<span style="color: #0000ff">&lt;/</span><span style="color: #800000">h2</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">&lt;</span><span style="color: #800000">input</span> <span style="color: #ff0000">data-bind</span><span style="color: #0000ff">=&quot;value: lastName&quot;</span> <span style="color: #0000ff">/&gt;&lt;</span><span style="color: #800000">br</span><span style="color: #0000ff">/&gt;</span></pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">&lt;/</span><span style="color: #800000">div</span><span style="color: #0000ff">&gt;</span></pre> <!--CRLF--></div> </div> <p>Fertig ist eine einfache Verwendung.</p> <h2>Download / Showcase</h2> <p>Das Bespiel kann unter <a title="http://jsfiddle.net/SvAHr/" href="http://jsfiddle.net/SvAHr/">http://jsfiddle.net/SvAHr/</a> getestet und bezogen werden. Oder aber auch untenstehend (IE streikt hier leider):</p> <p><iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/nitronic/SvAHr/embedded/" frameborder="0" allowfullscreen="allowfullscreen"></iframe></p> <h2>Fazit</h2> <p>Auch wenn das gezeigte Beispiel ein sehr einfaches ist, können benutzerdefinierte Bindungen sehr gut eingesetzt werden, um komplexe Verhalten zu steuern. Der Vorteil liegt darin, dass so wiederverwendbare Bindungen erstellt werden können. Im einfachsten Fall können darüber Standardverhalten/-einstellungen gesetzt werden, die jedoch nur an einer einzelnen Stelle zu implementieren/verwalten sind.</p> <h2>Jetzt Knockout.js lernen: Die Serie</h2> <ul> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Slides-und-Beispiele.aspx">Jetzt Knockout.js lernen: Slides und Beispiele</a> </li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formulare-und-Listen-binden.aspx">Jetzt Knockout.js lernen: Formulare und Listen binden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Template-verwenden.aspx">Jetzt Knockout.js lernen: Template verwenden</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx">Jetzt Knockout.js lernen: Benutzerdefinierte Bindungen</a> (dieser Teil)</li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Observables-erweitern.aspx">Jetzt Knockout.js lernen: Observables erweitern</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Formularvalidierung-mit-Undo.aspx">Jetzt Knockout.js lernen: Formularvalidierung mit Undo</a></li> <li><a href="http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Mapping.aspx">Jetzt Knockout.js lernen: Mapping</a></li> </ul> http://devtyr.norberteder.com/post/Jetzt-Knockoutjs-lernen-Benutzerdefinierte-Bindungen.aspx Norbert Eder [MVP] 3550 2012-01-23T06:50:32 My First Real PIX-6T4 Game: Sixty4Racer <p>After <a href="http://10rem.net/blog/2012/01/21/assembling-the-pix-6t4-netduino-powered-hand-held-game-system" target="_blank">assembling my Netduino-powered PIX-6T4</a>, I wanted to go and write a simple game. This post describes the construction of that game, including all source code.</p> <h3>Concept</h3> <p>When you have 64 monochrome red pixels, you need to keep the graphics simple. I decided on a game inspired by the classic <a href="http://www.bing.com/images/search?q=river+raid&amp;qs=n&amp;sk=&amp;sc=8-3&amp;form=QBIR" target="_blank">Atari River Raid game</a>. This is essentially a vertical scrolling game where you need to dodge obstacles with your boat. Variations included things like <a href="http://www.bing.com/images/search?q=commodore+spy+hunter&amp;qs=n&amp;sk=&amp;sc=8-13&amp;form=QBIR" target="_blank">Spy Hunter on the C64</a> and many many others. Most of those games also involved shooting and enemies, but that's a but more complex than you can reasonably do on this board. I won't enable moving walls like <a href="http://www.bing.com/images/search?q=laser+gates+atari&amp;qs=n&amp;sk=&amp;form=QBIR" target="_blank">Laser Gates</a>, but I'll leave things open enough not to make it impossible to do that in the future.</p> <p>The game had to be small enough that I could figure out the API, and then design, code, and blog about it in a single evening after my kids went to bed The PIX-6T4 is fun, but I have way too many projects on my backlog to be able to devote any <strong>significant</strong> time to it (<em>here's a taste: a ShapeOko CNC mill, an AVR MIDI-&gt;CV Converter, the final touches on the MIDI Thru Box, several MFOS Synth Modules, Several Gadgeteer Board Concepts, a Win8 XAML book, chapters to review in my Silverlight 5 book, and much much more</em>). In fact, that was one of the <strong>big selling points of this device: simple gameplay and quick to develop for</strong>. Combined with the great library Fabien designed, and my past experience with Netduino and, more specifically, C#, and this should be an evening project.</p> <h3>GamePlay</h3> <p>A simple dodge'em racing game. Levels get progressively longer and faster until you crash. And you will crash. And burn. And die.</p> <p>The joystick lets you control moving your single pixel vehicle either left or right. You can't move in any other direction.</p> <h4>Progression</h4> <p>Most good games increase in difficulty as you progress through the game. I decided that for this one, both the number of screens in the level and the overall speed would both increase as you progress through the game.</p> <h4>Goals</h4> <p>The goal is simple: not die. The longer you last, the more levels you'll make it through, and the more you'll be able to brag to your friends.</p> <h3>Screen Design</h3> <p>Back in the 80s, in 7th grade, I used to design single-color sprites for the commodore 64. The sprites themselves were 3 bytes wide, with each pixel represented as a single bit in the byte. I used to define them on graph paper, but alas, the notebooks I filled with sprites and BASIC listings have long since disappeared.</p> <p>I used to create my own fonts (programmable characters) as well. Those were done in a similar way, but on 8x8 graphs, eight bytes total for each character. Some pretty amazing games were created just with character graphics (the amazing <a href="http://www.mobygames.com/game/below-the-root" target="_blank">Below the Root on the C64</a>, as I recall, was one of them. You can <a href="http://www.youtube.com/watch?v=zWKpgpHa2Mo" target="_blank">see it played here</a>.). That model is what we have to work with on the PIX-6T4, but showing the equivalent of one character at a time.</p> <p>Here's an image from the Commodore 64 Programmer's Reference Manual</p> <p><a href="http://10rem.net/media/82883/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_2.png" target="_blank"><img src="http://10rem.net/media/82888/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_thumb.png" width="369" height="280" alt="image" border="0" style="background-image: none; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>You can see there how the letter A is formed by the bit patterns for the eight bytes. I'm going to use a similar approach for the level design here. I'll create the "blocks" that make up the play area and then chain them together to create a playable game field.</p> <p>Here here are the initial screens I created, using notepad. The only criteria I had was to make sure the middle two spots were open at the start and end of each screen. Zeroes are safe areas, ones are walls/shore/hard-deadly-smashy-things.</p> <p><a href="http://10rem.net/media/82893/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_4.png" target="_blank"><img src="http://10rem.net/media/82898/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_thumb_1.png" width="660" height="205" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>The more variation in screens you add, the more challenging and interesting the game can be. So, I'll be sure to leave the design open enough to allow for other screens to be easily added.</p> <p>I don't need to limit myself to 8 bytes high, but I did anyway, as that will let you do things like use <a href="http://www.min.at/prinz/software/pixelfont/" target="_blank">8x8 pixel font editors</a> to design the screens. In fact, here are the same levels (with a couple slight modifications), plus a bunch more, done using the pixel font editor. I got a little carried away :)</p> <p><a href="http://10rem.net/media/82903/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_6.png" target="_blank"><img src="http://10rem.net/media/82908/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_thumb_2.png" width="660" height="152" alt="image" border="0" style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px"/></a></p> <p>The first entry is the start screen, the second is the end screen. All the ones after that are random ones which can be shown at any point during play. There are several "break" screens in there. That is, screens that aren't particularly difficult. And then there are a number of hair-pullers, and ones with dead-ends too :) Black is wall, white is passable space.</p> <p>Thanks to Fabien for pointing out this tool in the source code. It's so much easier to visualize the design when using the font editor, plus you get to export the bytes automatically. Speaking of export, here's what the tool generated for me:</p> <pre class="brush: csharp;"> <br /> // Font: ScreensSource.pf<br /> <br /> unsigned char font[2048] =<br /> {<br /> 0xC7, 0xC3, 0x83, 0x83, 0x81, 0x00, 0x00, 0x00, // Char 000 (.)<br /> 0x00, 0x00, 0x81, 0x81, 0xC3, 0xC3, 0xE7, 0xE7, // Char 001 (.)<br /> 0xE3, 0xCF, 0x87, 0xCF, 0x81, 0xF3, 0xC3, 0x87, // Char 002 (.)<br /> 0x87, 0xC1, 0xF1, 0xF9, 0xE1, 0x87, 0xC3, 0x81, // Char 003 (.)<br /> 0x81, 0xE7, 0x81, 0x99, 0x99, 0x81, 0xE7, 0xC3, // Char 004 (.)<br /> 0xE7, 0xE7, 0xF1, 0xC1, 0xCF, 0xE3, 0xF7, 0xE7, // Char 005 (.)<br /> 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, // Char 006 (.)<br /> 0xE3, 0xF9, 0xFC, 0xFE, 0xFC, 0xF9, 0xF3, 0xE7, // Char 007 (.)<br /> 0xE7, 0xE7, 0xEF, 0xE7, 0xF7, 0xE7, 0xEF, 0xE7, // Char 008 (.)<br /> 0xC3, 0xFB, 0xE1, 0xEF, 0xC1, 0xFB, 0x83, 0xE7, // Char 009 (.)<br /> 0xE7, 0xC1, 0x81, 0xF9, 0xC0, 0x81, 0x9F, 0x87, // Char 010 (.)<br /> 0xE7, 0xC3, 0x00, 0x7E, 0x00, 0xE7, 0xE7, 0x81, // Char 011 (.)<br /> 0xE7, 0x81, 0x18, 0x7E, 0x3C, 0x99, 0xC3, 0xE7, // Char 012 (.)<br /> 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x00, // Char 013 (.)<br /> 0xC3, 0x89, 0x24, 0xB5, 0xA5, 0x2C, 0x81, 0xC3, // Char 014 (.)<br /> 0x86, 0x3C, 0x5A, 0x66, 0x3C, 0x28, 0x82, 0xC6, // Char 015 (.)<br /> 0x81, 0x00, 0x66, 0x66, 0x00, 0x99, 0x81, 0xA5, // Char 016 (.)<br /> 0xE7, 0xC3, 0xDF, 0x83, 0xF7, 0xA1, 0x8D, 0xE7, // Char 017 (.)<br /> 0xC3, 0x81, 0x00, 0x18, 0x18, 0x00, 0x81, 0xC3, // Char 018 (.)<br /> 0xE7, 0xC3, 0x99, 0x38, 0x1C, 0x99, 0xC3, 0xE7, // Char 019 (.)<br /> 0x04, 0x49, 0x02, 0x10, 0x4A, 0x00, 0x44, 0x20, // Char 020 (.)<br /> 0xA7, 0x2D, 0x6A, 0x2D, 0x26, 0x55, 0x56, 0x27, // Char 021 (.)<br /> 0xE7, 0xF7, 0xE7, 0xF7, 0xC3, 0xDB, 0xC3, 0xE7, // Char 022 (.)<br /> 0xE3, 0xCB, 0x9B, 0x3B, 0x89, 0x3D, 0x9D, 0xC5, // Char 023 (.)<br /> 0xE7, 0xC3, 0x99, 0xBD, 0x99, 0xC3, 0xE7, 0xE7, // Char 024 (.)<br /> 0xC3, 0xCF, 0xC3, 0xF3, 0xC7, 0xE3, 0xCF, 0xC7, // Char 025 (.)<br /> 0x65, 0xB6, 0x55, 0xB6, 0x65, 0xAA, 0x6D, 0xA7, // Char 026 (.)<br /> 0xE7, 0xC3, 0xE3, 0xF1, 0xF8, 0xF3, 0xC7, 0xE7, // Char 027 (.)<br /> 0xE4, 0xF4, 0xF4, 0xF4, 0xE4, 0x69, 0x0B, 0xA7, // Char 028 (.)<br /> 0x00, 0x18, 0x66, 0x5A, 0x5A, 0x66, 0x18, 0x00, // Char 029 (.)<br /> 0xA5, 0x91, 0x93, 0x97, 0x93, 0xCB, 0xE3, 0xE7, // Char 030 (.)<br /> 0x00, 0xDB, 0xC3, 0xE7, 0xE7, 0xC3, 0x99, 0x00, // Char 031 (.)<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 032 ( )<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 033 (!)<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 034 (")<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 035 (#)<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 036 ($)<br /> ...<br /> 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Char 255 (.)<br /> }; </pre> <p>I then just needed to prune it to remove the stuff I wasn't using, and make it work in my app. The font designer certainly took a lot of the work out of it, though. The end-result can be seen in the code listing for the Screens class.</p> <p>With that in place, it was time to actually start creating the game.</p> <h3>First Iteration: Creating the scrolling playfield</h3> <p>I called my project PeteBrown.Sixty4Racer. Just as in the previous post, I copied over the Program.cs file from another project and used that as the start. Please <a href="http://10rem.net/blog/2012/01/21/assembling-the-pix-6t4-netduino-powered-hand-held-game-system" target="_blank">read my previous post</a> to see what references you need and whatnot.</p> <p>The first class I created was the one that manages the creation of the screens.</p> <h4>The Screens Class</h4> <p>The Screens class is responsible for storing all the known screens, and then assembling them into a level when requested. It knows how large the screens are, and how large a single level's full set of screens is.</p> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> using netduino.helpers.Imaging;<br /> <br /> namespace PeteBrown.Sixty4Racer<br /> {<br /> class Screens<br /> {<br /> // Font: ScreensSource.pf<br /> private const int ScreenCount = 32;<br /> private const int ScreenHeight = 8;<br /> private readonly byte[] _screenBytes = new byte[ScreenCount * ScreenHeight]<br /> {<br /> 0xC7, 0xC3, 0x83, 0x83, 0x81, 0x00, 0x00, 0x00, // Start Screen<br /> 0x00, 0x00, 0x81, 0x81, 0xC3, 0xC3, 0xE7, 0xE7, // Stop Screen<br /> 0xE3, 0xCF, 0x87, 0xCF, 0x81, 0xF3, 0xC3, 0x87, // Char 002 (.)<br /> 0x87, 0xC1, 0xF1, 0xF9, 0xE1, 0x87, 0xC3, 0x81, // Char 003 (.)<br /> 0x81, 0xE7, 0x81, 0x99, 0x99, 0x81, 0xE7, 0xC3, // Char 004 (.)<br /> 0xE7, 0xE7, 0xF1, 0xC1, 0xCF, 0xE3, 0xF7, 0xE7, // Char 005 (.)<br /> 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, // Char 006 (.)<br /> 0xE3, 0xF9, 0xFC, 0xFE, 0xFC, 0xF9, 0xF3, 0xE7, // Char 007 (.)<br /> 0xE7, 0xE7, 0xEF, 0xE7, 0xF7, 0xE7, 0xEF, 0xE7, // Char 008 (.)<br /> 0xC3, 0xFB, 0xE1, 0xEF, 0xC1, 0xFB, 0x83, 0xE7, // Char 009 (.)<br /> 0xE7, 0xC1, 0x81, 0xF9, 0xC0, 0x81, 0x9F, 0x87, // Char 010 (.)<br /> 0xE7, 0xC3, 0x00, 0x7E, 0x00, 0xE7, 0xE7, 0x81, // Char 011 (.)<br /> 0xE7, 0x81, 0x18, 0x7E, 0x3C, 0x99, 0xC3, 0xE7, // Char 012 (.)<br /> 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x00, // Char 013 (.)<br /> 0xC3, 0x89, 0x24, 0xB5, 0xA5, 0x2C, 0x81, 0xC3, // Char 014 (.)<br /> 0x86, 0x3C, 0x5A, 0x66, 0x3C, 0x28, 0x82, 0xC6, // Char 015 (.)<br /> 0x81, 0x00, 0x66, 0x66, 0x00, 0x99, 0x81, 0xA5, // Char 016 (.)<br /> 0xE7, 0xC3, 0xDF, 0x83, 0xF7, 0xA1, 0x8D, 0xE7, // Char 017 (.)<br /> 0xC3, 0x81, 0x00, 0x18, 0x18, 0x00, 0x81, 0xC3, // Char 018 (.)<br /> 0xE7, 0xC3, 0x99, 0x38, 0x1C, 0x99, 0xC3, 0xE7, // Char 019 (.)<br /> 0x04, 0x49, 0x02, 0x10, 0x4A, 0x00, 0x44, 0x20, // Char 020 (.)<br /> 0xA7, 0x2D, 0x6A, 0x2D, 0x26, 0x55, 0x56, 0x27, // Char 021 (.)<br /> 0xE7, 0xF7, 0xE7, 0xF7, 0xC3, 0xDB, 0xC3, 0xE7, // Char 022 (.)<br /> 0xE3, 0xCB, 0x9B, 0x3B, 0x89, 0x3D, 0x9D, 0xC5, // Char 023 (.)<br /> 0xE7, 0xC3, 0x99, 0xBD, 0x99, 0xC3, 0xE7, 0xE7, // Char 024 (.)<br /> 0xC3, 0xCF, 0xC3, 0xF3, 0xC7, 0xE3, 0xCF, 0xC7, // Char 025 (.)<br /> 0x65, 0xB6, 0x55, 0xB6, 0x65, 0xAA, 0x6D, 0xA7, // Char 026 (.)<br /> 0xE7, 0xC3, 0xE3, 0xF1, 0xF8, 0xF3, 0xC7, 0xE7, // Char 027 (.)<br /> 0xE4, 0xF4, 0xF4, 0xF4, 0xE4, 0x69, 0x0B, 0xA7, // Char 028 (.)<br /> 0x00, 0x18, 0x66, 0x5A, 0x5A, 0x66, 0x18, 0x00, // Char 029 (.)<br /> 0xA5, 0x91, 0x93, 0x97, 0x93, 0xCB, 0xE3, 0xE7, // Char 030 (.)<br /> 0x00, 0xDB, 0xC3, 0xE7, 0xE7, 0xC3, 0x99, 0x00 // Char 031 (.)<br /> };<br /> <br /> private RacerGame _parentGame;<br /> <br /> public Screens(RacerGame parentGame)<br /> {<br /> _parentGame = parentGame;<br /> }<br /> <br /> <br /> public int GetLevelScreenCount(int level)<br /> {<br /> return level * 6 + 2;<br /> }<br /> <br /> public int GetLevelPixelHeight(int level)<br /> {<br /> return GetLevelScreenCount(level) * ScreenHeight;<br /> }<br /> <br /> public Composition GetLevelComposition(int level)<br /> {<br /> int levelScreenCount = GetLevelScreenCount(level);<br /> <br /> byte[] bytes = new byte[ScreenHeight * levelScreenCount];<br /> <br /> int b = 0;<br /> int startIndex = 0;<br /> <br /> for (int i = 0; i &lt; levelScreenCount; i++)<br /> {<br /> if (i == 0)<br /> {<br /> // finish screen. This is the second screen in the array<br /> startIndex = ScreenHeight;<br /> }<br /> else if (i == levelScreenCount - 1)<br /> {<br /> // start screen. First screen in the array<br /> startIndex = 0;<br /> }<br /> else<br /> {<br /> // regular random screen<br /> int selectedScreen = _parentGame.Random.Next(ScreenCount - 2) + 2;<br /> <br /> startIndex = ScreenHeight * selectedScreen;<br /> }<br /> <br /> for (int a = startIndex; a &lt; startIndex + ScreenHeight; a++, b++)<br /> {<br /> bytes[b] = _screenBytes[a];<br /> }<br /> }<br /> <br /> return new Composition(bytes, 8, levelScreenCount * ScreenHeight);<br /> }<br /> <br /> }<br /> } </pre> <p>The class has the primary function GetLevelComposition which builds the background bitmap used to populate all the little red LEDs. The Composition class is one of the netduino helper classes in the PIX-6T4 library.</p> <p>The next class is the main game class.</p> <h4>The RacerGame class</h4> <p>The RacerGame class is responsible for all the game-specific logic. It handles scrolling the level, displaying messages, and (eventually), moving the player around the screen and handling collision detection. In this first iteration, all it does is display the level number and then scroll the level at the appropriate speed. The first level may seem to scroll painfully slow, but trust me, throw a player pixel in there and you'll change your mind.</p> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> using netduino.helpers.Fun;<br /> using netduino.helpers.Imaging;<br /> using System.Threading;<br /> <br /> namespace PeteBrown.Sixty4Racer<br /> {<br /> class RacerGame : Game<br /> {<br /> private Screens _screens;<br /> <br /> public RacerGame(ConsoleHardwareConfig config)<br /> : base(config)<br /> {<br /> _screens = new Screens(this);<br /> <br /> DisplayDelay = 25;<br /> }<br /> <br /> protected override void OnGameEnd()<br /> {<br /> }<br /> <br /> <br /> protected override void OnGameStart()<br /> {<br /> base.OnGameStart();<br /> }<br /> <br /> <br /> private int _currentLevel = 0;<br /> private float _currentlevelSpeedIncrement = 0f;<br /> private float _exactScrollPosition = 0.0f;<br /> <br /> private int CurrentWorldLine<br /> {<br /> get { return (int)_exactScrollPosition; }<br /> }<br /> <br /> private void InitializeLevel()<br /> {<br /> World = _screens.GetLevelComposition(_currentLevel);<br /> _exactScrollPosition = _screens.GetLevelPixelHeight(_currentLevel) - 1;<br /> _currentlevelSpeedIncrement = 0.02f * (float)_currentLevel; // tweak this to change speed<br /> }<br /> <br /> private void CompleteLevel()<br /> {<br /> // TODO: Play some music<br /> <br /> Thread.Sleep(1500);<br /> ScrollMessage(" Level Up!");<br /> }<br /> <br /> private void IntroduceLevel()<br /> {<br /> // show the level number<br /> Hardware.Matrix.Display(SmallChars.ToBitmap(_currentLevel / 10, _currentLevel % 10));<br /> Thread.Sleep(1000);<br /> }<br /> <br /> private bool _firstTime = true;<br /> public override void Loop()<br /> {<br /> if (CurrentWorldLine == 0 || _firstTime)<br /> {<br /> if (!_firstTime)<br /> {<br /> CompleteLevel();<br /> }<br /> <br /> // Next Level<br /> _currentLevel++;<br /> InitializeLevel();<br /> IntroduceLevel();<br /> <br /> _firstTime = false;<br /> }<br /> else<br /> {<br /> _exactScrollPosition -= _currentlevelSpeedIncrement;<br /> }<br /> <br /> // draw the frame<br /> Hardware.Matrix.Display(World.GetFrame(0, CurrentWorldLine));<br /> }<br /> }<br /> } </pre> <p>The RacerGame class knows a level is complete when the top row of pixels is the first row of pixels in the level. Remember, since we're scrolling the screen down, we're moving from the bottom to the top of the rows of pixels. Get to the top, and the level is done.</p> <blockquote> <p><strong>Minor But Fatal Bitmap Class Bug</strong></p> <p>The GetFrame method in the version of the source code I used has an incorrect check. Where it says if (x &gt;= 0 &amp;&amp; x + FrameSize &lt; Width) it should say if (x &gt;= 0 &amp;&amp; x + FrameSize &lt;= Width). Notice the &lt;= instead of &lt;. The incorrect check means that any bitmaps that are exactly 8 pixels wide, like the one here, simply won't display. This bug may be fixed in the source you get, but you'll want to double-check. I'm just glad I had the source to refer to (and fix!). Isn't OSS great? :)</p> </blockquote> <p>When the level is complete, you get the scrolling "Level Up!" message. Click the joystick button and you're good to try the next level.</p> <p>Now is a good time to run the game and see what it looks like. You should see a vertically scrolling playfield, but no player just yet.</p> <h3>Second Iteration: Adding in the player</h3> <p>The PIX-6T4 libraries have built-in the concept of a PlayerMissile. This is a single pixel on the playfield. It may move, so it has X and Y speed. You can show or hide it, so it has Visibility. And most importantly, it has collision detection with other PlayerMissile instances. For our game, we're not going to use that, since we're looking for collision detection with the background. So, a little manual detection is in order.</p> <blockquote> <p><strong>Bitmap Class Bugs</strong></p> <p>While coding this game, I found a few more bugs in the Bitmap class. I've alerted Fabien, so you should see an updated set of source code soon. The first bug is that the GetPixel and SetPixel methods do some incorrect bounds checking up front. Y is checked against width rather than height, and the opposite happens with X. We're going to use GetPixel in a moment, so fixing this is important.</p> </blockquote> <p>Here's the updated RacerGame class</p> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> using netduino.helpers.Fun;<br /> using netduino.helpers.Imaging;<br /> using System.Threading;<br /> <br /> namespace PeteBrown.Sixty4Racer<br /> {<br /> class RacerGame : Game<br /> {<br /> private Screens _screens;<br /> private PlayerMissile _ship;<br /> <br /> public RacerGame(ConsoleHardwareConfig config)<br /> : base(config)<br /> {<br /> _screens = new Screens(this);<br /> <br /> DisplayDelay = 25;<br /> }<br /> <br /> protected override void OnGameEnd()<br /> {<br /> ScrollMessage(" Game Over");<br /> }<br /> <br /> <br /> protected override void OnGameStart()<br /> {<br /> base.OnGameStart();<br /> <br /> _ship = new PlayerMissile()<br /> {<br /> Name = "ship",<br /> IsEnemy = false,<br /> X = 3,<br /> IsVisible = true,<br /> VerticalSpeed = 0,<br /> };<br /> <br /> ScrollMessage(" Sixty4Racer!");<br /> }<br /> <br /> <br /> private int _currentLevel = 0;<br /> private float _currentlevelSpeedIncrement = 0f;<br /> private const float BaseShipSpeed = 0.25f; // speed ship moves across the screen<br /> private float _exactScrollPosition = 0.0f;<br /> <br /> private int CurrentWorldLine<br /> {<br /> get { return (int)_exactScrollPosition; }<br /> }<br /> <br /> private void InitializeLevel()<br /> {<br /> World = _screens.GetLevelComposition(_currentLevel);<br /> World.AddMissile(_ship);<br /> <br /> _exactScrollPosition = _screens.GetLevelPixelHeight(_currentLevel) - 1;<br /> _currentlevelSpeedIncrement = 0.02f * (float)_currentLevel; // tweak this to change speed<br /> <br /> _ship.Y = CurrentWorldLine + 7; // always be on bottom line<br /> _ship.X = 3;<br /> }<br /> <br /> private void CompleteLevel()<br /> {<br /> // TODO: Play some music<br /> <br /> Thread.Sleep(1500);<br /> ScrollMessage(" Level Up!");<br /> }<br /> <br /> private void IntroduceLevel()<br /> {<br /> // show the level number<br /> Hardware.Matrix.Display(SmallChars.ToBitmap(_currentLevel / 10, _currentLevel % 10));<br /> Thread.Sleep(1000);<br /> }<br /> <br /> private bool CheckForCollision()<br /> {<br /> return World.Background.GetPixel(_ship.X, _ship.Y);<br /> }<br /> <br /> private bool _firstTime = true;<br /> public override void Loop()<br /> {<br /> _ship.HorizontalSpeed = (float)Hardware.JoystickLeft.XDirection * BaseShipSpeed;<br /> _ship.Move();<br /> <br /> if (_ship.X &lt; 0) _ship.X = 0;<br /> if (_ship.X &gt; 7) _ship.X = 7;<br /> <br /> if (CurrentWorldLine == 0 || _firstTime)<br /> {<br /> if (!_firstTime)<br /> {<br /> CompleteLevel();<br /> }<br /> <br /> // Next Level<br /> _currentLevel++;<br /> InitializeLevel();<br /> IntroduceLevel();<br /> <br /> _firstTime = false;<br /> }<br /> else<br /> {<br /> _exactScrollPosition -= _currentlevelSpeedIncrement;<br /> <br /> _ship.Y = CurrentWorldLine + 7; // always be on bottom line<br /> }<br /> <br /> // draw the frame<br /> Hardware.Matrix.Display(World.GetFrame(0, CurrentWorldLine));<br /> <br /> if (CheckForCollision())<br /> {<br /> // game over<br /> Stop();<br /> }<br /> }<br /> }<br /> } </pre> <p>This version of the source adds in the player pixel</p> <blockquote> <p><strong>I represent the "This Game is Too Damn Hard" Party</strong></p> <p>At this point, after adding the player and collision detection, I realized just how hard this game is! If you want to make it easier, I suggest editing the levels to make them a little more open, like a minimum of two pixels wide for any path, and no sudden moves left or right. You may even want to segment the level array into easy/medium/hard, and then change the mix of screens from level to level.</p> </blockquote> <p>With all that in place, now is another great time to try out the game. It has all the main functionality at this point; anything else is just polish.</p> <h3>Third Iteration: Polishing</h3> <p>The first thing I realized was that it was really hard to make out the player pixel in the sea of red. That's to be expected on a monochrome display at 8x8 resolution. The approach I came up with to make it a bit easier is to simply flicker the player pixel. Each time the game loop executes, I toggle the visibility of the ship PlayerMissile to give it a nice seizure-inducing flicker.</p> <pre class="brush: csharp; highlight: [7];"> public override void Loop()<br /> {<br /> _ship.HorizontalSpeed = (float)Hardware.JoystickLeft.XDirection * BaseShipSpeed;<br /> _ship.Move();<br /> <br /> // make the ship blink so we can see it<br /> _ship.IsVisible = !_ship.IsVisible;<br /> <br /> if (_ship.X &lt; 0) _ship.X = 0;<br /> if (_ship.X &gt; 7) _ship.X = 7;<br /> <br /> if (CurrentWorldLine == 0 || _firstTime)<br /> {<br /> if (!_firstTime)<br /> {<br /> CompleteLevel();<br /> }<br /> <br /> // Next Level<br /> _currentLevel++;<br /> InitializeLevel();<br /> IntroduceLevel();<br /> <br /> _firstTime = false;<br /> }<br /> else<br /> {<br /> _exactScrollPosition -= _currentlevelSpeedIncrement;<br /> <br /> _ship.Y = CurrentWorldLine + 7; // always be on bottom line<br /> }<br /> <br /> // draw the frame<br /> Hardware.Matrix.Display(World.GetFrame(0, CurrentWorldLine));<br /> <br /> if (CheckForCollision())<br /> {<br /> // game over<br /> Stop();<br /> }<br /> } </pre> <h4>Sound Effects</h4> <p>The next thing this needed was some sound effects. When you move left or right a full pixel, it would be helpful to play a little blip. Maybe that's annoying? Nah! Let's do it.</p> <p>This is really easy to do. I simply keep track of the last position the ship moved to. If the new position is different from the old one, I call the Beep function to make a noise. The actual implementation here results in an extra blip on startup, but I'm cool with that :)</p> <pre class="brush: csharp;"> private void Blip()<br /> {<br /> Beep(200, 30);<br /> }<br /> <br /> private bool _firstTime = true;<br /> private int _oldShipX = 0;<br /> public override void Loop()<br /> {<br /> _ship.HorizontalSpeed = (float)Hardware.JoystickLeft.XDirection * BaseShipSpeed;<br /> _ship.Move();<br /> <br /> if (_ship.X != _oldShipX)<br /> {<br /> Blip();<br /> _oldShipX = _ship.X;<br /> }<br /> <br /> <br /> // make the ship blink so we can see it<br /> _ship.IsVisible = !_ship.IsVisible;<br /> <br /> ...<br /> <br /> }<br /> </pre> <p>Next, we need to do a little bit of exploding when you hit the wall. And yes, you <strong>will</strong> hit the wall.</p> <h4>The Explosion Sprite</h4> <p>The final thing was to add a little bit of an explosion when the player hits the wall. I designed the sprite data using the same PixelFont editor.</p> <p><a href="http://10rem.net/media/82913/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_8.png" target="_blank"><img src="http://10rem.net/media/82918/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_thumb_3.png" width="660" height="90" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I then copied just those 8 x 8 bytes of data into the initializer for the sprite class. Here's the Sprite class itself (without the initializer code).</p> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> <br /> namespace PeteBrown.Sixty4Racer<br /> {<br /> class Sprite<br /> {<br /> private readonly byte[] _frames;<br /> private const int Width = 8;<br /> private const int Height = 8;<br /> private int _frameCount;<br /> <br /> public Sprite (byte[] frames)<br /> {<br /> _frames = frames;<br /> <br /> _frameCount = _frames.Length / Height;<br /> }<br /> <br /> public int FrameCount<br /> {<br /> get { return _frameCount; }<br /> }<br /> <br /> public int CurrentFrame<br /> {<br /> get { return _currentFrame; }<br /> }<br /> <br /> public void Reset()<br /> {<br /> _currentFrame = 0;<br /> }<br /> <br /> private int _currentFrame = 0;<br /> public byte[] GetNextFrame()<br /> {<br /> return GetFrame(_currentFrame++);<br /> }<br /> <br /> public byte[] GetFrame(int index)<br /> {<br /> if (index &gt;= FrameCount)<br /> throw new IndexOutOfRangeException("Index must be less than " + FrameCount);<br /> <br /> byte[] b = new byte[Height];<br /> <br /> for (int i = 0; i &lt; Height; i++)<br /> {<br /> b[i] = _frames[index * Height + i];<br /> }<br /> <br /> return b;<br /> }<br /> }<br /> } </pre> <p>(The initialization code will be in the next listing.)</p> <p>Next, I added in a little music. How about some Sad Trombone when you die? Sounds good to me. By looking at the Pac Man music example, and the source code for the RttlSong class, I was able to figure out how to build a string of notes to play the sad trombone sound asynchronously while the explosion happens. Rub it in!</p> <pre class="brush: csharp;"> Sprite _explosion = new Sprite(new byte[]<br /> {<br /> 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,<br /> 0x00, 0x00, 0x18, 0x24, 0x24, 0x18, 0x00, 0x00,<br /> 0x08, 0x3C, 0x72, 0x5B, 0xDE, 0x62, 0x3C, 0x08,<br /> 0x3C, 0xD2, 0xAD, 0xFE, 0x5F, 0xF5, 0x56, 0x28,<br /> 0x00, 0x3C, 0x56, 0x7A, 0x6E, 0x72, 0x3C, 0x00,<br /> 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00,<br /> 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,<br /> 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00<br /> });<br /> <br /> protected override void OnGameEnd()<br /> {<br /> var song = new RttlSong("SadTrombone:d=4,o=4,b=40:32d4,32c#4,32c4,4b3");<br /> var thread = song.Play(Hardware.Speaker, true);<br /> <br /> for (int i = 0; i &lt; _explosion.FrameCount; i++)<br /> {<br /> Hardware.Matrix.Display(_explosion.GetNextFrame());<br /> Thread.Sleep(200);<br /> }<br /> <br /> ScrollMessage(" Game Over");<br /> } </pre> <p>You can go further, of course, but I'm going to wrap it up at that.</p> <h3>Final Steps</h3> <p>The final things to do are to create the manifest file and bitmap which will be used on the SD card. I'll need to check with Fabien to see what the exact format of the .bin file is, but I suspect it's just the 8 bytes of data formatted like all the other bitmap data in this application. I'm also not sure if he has a nice little app to write that data out, or convert from a bitmap, or something else. I ended up just using a hex editor to recreate the pattern from one of the images I created in the font editor.</p> <p><a href="http://10rem.net/media/82923/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_10.png" target="_blank"><img src="http://10rem.net/media/82928/Windows-Live-Writer_My-First-Real-PIX-6T4-Game_1194A_image_thumb_4.png" width="257" height="198" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>The assembly itself needs to be loaded from the SD card as a .pe file, as Fabien explains in <a href="http://fabienroyer.wordpress.com/2010/12/29/loading-assemblies-dynamically-from-an-sd-card-with-a-netduino/" target="_blank">his blog post on dynamically loading assemblies from an SD card</a>. Be sure to comment out the #define dev before your final compile. Make sure you take the .pet file from the LE (little endian) folder, not the BE (big endian) folder. The files look identical other than byte order, but the BE version will not work.</p> <p>I then created a cartridge.txt manifest file for my game. The contents of that consist of a single line:</p> <pre class="brush: csharp;"> <br /> assembly:file=PeteBrown.Sixty4Racer.pe;name=PeteBrown.Sixty4Racer;version=1.0.0.0;class=PeteBrown.Sixty4Racer.Program;method=Run<br /> <br /> </pre> <p>They must be in a folder with the same name as the root file name for the image. So, these go in a PeteBrown.Sixty4Racer folder.</p> <p>Finally, don't forget to reflash the PIX-6T4 with the main ConsoleBootLoader application from your solution.</p> <p>After that, pop the SD card into the PIX-6T4 and have a blast!</p> <h3>What You Can Do</h3> <p>This came is completely free and open source. While I'd love credit for the initial work, it's not a requirement. Go ahead and do whatever you'd like with the source and have a blast :)</p> <p>Here's a video of the game in action.</p> <div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:69c56f9e-f14a-4e1e-9f9f-43d6647494c3" style="margin: 0px; display: inline; float: none; padding: 0px;"> <div id="6da37833-fb66-423f-bda2-cefc06cf5131" style="margin: 0px; display: inline; padding: 0px;"><object width="611" height="343" data="http://www.youtube.com/v/10FzxWQTzzM?hl=en&amp;hd=1" type="application/x-shockwave-flash"><param name="src" value="http://www.youtube.com/v/10FzxWQTzzM?hl=en&amp;hd=1" /> </object></div> <div style="width: 611px; clear: both; font-size: 0.8em;">PIX-6T4 Netduino Mini Game</div> </div> <p><a href="http://feedads.g.doubleclick.net/~a/yocakmzHMcesqfhZTMHyJkfAQLk/0/da"><img src="http://feedads.g.doubleclick.net/~a/yocakmzHMcesqfhZTMHyJkfAQLk/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/yocakmzHMcesqfhZTMHyJkfAQLk/1/da"><img src="http://feedads.g.doubleclick.net/~a/yocakmzHMcesqfhZTMHyJkfAQLk/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ZnhAnsv1yk8:vlM6_TNId14:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ZnhAnsv1yk8:vlM6_TNId14:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=ZnhAnsv1yk8:vlM6_TNId14:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=ZnhAnsv1yk8:vlM6_TNId14:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/ZnhAnsv1yk8" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/ZnhAnsv1yk8/my-first-real-pix-6t4-game-sixty4racer Pete Brown 3549 2012-01-22T14:34:30 Assembling the PIX-6T4 Netduino-powered Hand-Held Game System <p>I recently picked up a PIX-6T4 build by Fabien Royer (with games by Fabien Royer and Bertrand Le Roy). This is a 64 pixel, two joystick/button, monophonic sound hand-held game device based around the Netduino Mini from Secret Labs. You create games in C# using Visual studio.</p> <blockquote> <p>Disclaimer: I work for Microsoft and I enjoy working in the .NET Micro Framework as well as C++ on other microcontrollers. I purchased this product on my own, at full price; this is not a sample or review unit. Presumably, I got the same package of goodies everyone else gets.</p> </blockquote> <h3>Unboxing</h3> <p>The kit came in a regular USPS shipping box inside which were four bags. Two bags had components, and two had joysticks.</p> <p><a href="http://10rem.net/media/82716/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_9.png" target="_blank"><img src="http://10rem.net/media/82721/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_3.png" width="660" height="316" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Everything was well-packed in ESD safe bags. The two joysticks were in their original as-delivered bags.</p> <p>What's inside?</p> <h4>What the Kit Includes</h4> <p>The kit includes just about everything you need to assemble a working PIX-6T4 handheld game console. The PCB, of course, as well as a nice laser-cut acrylic bottom.</p> <p><a href="http://10rem.net/media/82726/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_11.png" target="_blank"><img src="http://10rem.net/media/82731/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_4.png" width="660" height="355" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p><a href="http://10rem.net/media/82736/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_13.png" target="_blank"><img src="http://10rem.net/media/82741/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_5.png" width="660" height="423" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>It can be powered by a 9v battery or a regular 9v power supply. When using a battery, you solder a barrel jack on the battery lead to make it easy to switch between the two.</p> <p>The entire thing is open source hardware and software from the Netduino all the way up to the full kit and the software to run it.</p> <h4>What it doesn't Include</h4> <p><strong>A USB to Serial converter</strong>. Many people with Netduino minis already have one of these cables. You can get them cheap online, or (worst case) get one at Best Buy or Fry's. I happened to have one just because my C64-TPC (a connection between my Commodore and my PC) needed one. Putting on-board USB would have been nice, but that typically requires a surface-mount FTDI chip which is very difficult for DIY soldering (or a breakout board from a place like SparkFun which adds bulk, cost, and complexity). The <a href="http://www.netduino.com/netduinomini/specs.htm" target="_blank">Netduino mini</a>, unlike the Netduino, doesn't offer an external USB interface. So, while I'm not a fan of the serial approach, I do think it was the best choice here.</p> <p><a href="http://www.netduino.com/netduinomini/specs.htm" target="_blank"><img src="http://10rem.net/media/82746/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_3.png" width="253" height="178" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>You can <a href="http://www.pix6t4.com/source" target="_blank">play the stock games by just loading them on the SD card</a>. However, if you want to do any programming or debugging of your own, you'll really need the USB converter, as debugging is impossible without it.</p> <p>Speaking of SD card, that's the other things that you'll need: <strong>A FAT32-formatted SD card</strong>. You'll need one of these to store the games you write and also to load the stock games.</p> <h3>Assembly</h3> <p>I love the blue board. I've always liked blue, black and red PCBs. The typical green solder mask just looks too much like some crap 486 motherboard. I'm glad they went with something attractive here.</p> <p>First step is the most unusual bit of soldering. There's one surface mount component, but it's stunningly easy to solder. That's the SD card slot. Make sure you following the video and do not solder the two pins on the right (although if they're not connected to anything, as long as you don't short them, I would think they'd be fine)</p> <p><a href="http://10rem.net/media/82751/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_15.png" target="_blank"><img src="http://10rem.net/media/82756/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_6.png" width="660" height="337" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Next up, flip the board over and install the IC socket for the LED controller, and then a resistor.</p> <p><a href="http://10rem.net/media/82761/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_17.png" target="_blank"><img src="http://10rem.net/media/82766/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_7.png" width="660" height="327" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>In this particular case, it seems the stencil for the resistor was sized for a 1/8" watt resistor. The space is also cramped, but this resistor sticking up like that just breaks my heart. At least, it's on the bottom side of the board so we can all hide our shame :)</p> <p>Next, up, the LED array and then the Netduino mini socket.</p> <p><a href="http://10rem.net/media/82771/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_19.png" target="_blank"><img src="http://10rem.net/media/82776/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_8.png" width="660" height="400" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>A power switch and another resistor standing at attention</p> <p><a href="http://10rem.net/media/82781/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_21.png" target="_blank"><img src="http://10rem.net/media/82786/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_9.png" width="660" height="313" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>From there, it's the audio trim pot and the power LED. At this point, I have to mention that the power LED is REALLY bright and in your face when you're trying to play. I covered mine with a little bit of black paint, but even that isn't quite enough. I actually think it would be better to solder that LED upside down, so it is on the underside of the board. That might even be a cool effect with the acrylic down there. Fabien mentioned he might bump up the resistor value, but the more I think about putting it upside down, the more I like it. I might even desolder mine and put a new LED underneath :)</p> <p><a href="http://10rem.net/media/82791/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_23.png" target="_blank"><img src="http://10rem.net/media/82796/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_10.png" width="660" height="239" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I did a few more steps before taking another photo. Resistors, the power connector (underside), and a transistor are all in place.</p> <p><a href="http://10rem.net/media/82801/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_25.png" target="_blank"><img src="http://10rem.net/media/82806/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_11.png" width="660" height="501" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>In case you haven't gathered, I'm not a big fan of having resistors stand up in general. In fact, I hate them in all but the most cramped design with huge multi-watt resistors. They're easy to knock over, bend, etc, and they're more difficult to solder in that configuration. There's tons of spare room on this board, so I'm not entirely sure why Fabien and folks did that. Just be careful when soldering the other components so you don't break these off.</p> <p>Ceramic capacitor, electrolytic capacitor, and the 3.3v power regulator in place. If I were designing this board, I think I would have found a way to position the regulator so it lay flat on the board with its leads bent at 90 degree angles. That's a pretty common approach in other builds, like the <a href="http://10rem.net/blog/2011/10/22/just-completed-my-sammich-sid-build" target="_blank">Sammich SID I built</a>.</p> <p><a href="http://10rem.net/media/82811/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_27.png" target="_blank"><img src="http://10rem.net/media/82816/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_12.png" width="660" height="305" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Finally, the joysticks, pin headers, and the ICs themselves. Before I installed the ICs, I went and cleaned the bottom of the board with flux remover spray and a cleaning brush. It helps clean things up quite a bit. You'll see later, it also helped me see a bad solder joint. Organic Rosin flux isn't super harmful to leave on, but it's ugly to see on the board.</p> <p><a href="http://10rem.net/media/82821/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_29.png" target="_blank"><img src="http://10rem.net/media/82826/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_13.png" width="660" height="373" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Don't forget the one IC that goes on the bottom.</p> <p><a href="http://10rem.net/media/82831/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_31.png" target="_blank"><img src="http://10rem.net/media/82836/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_14.png" width="660" height="225" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <h3>Smoke Test</h3> <p>Time for a power-on test! After I cleaned it up, dried if off, and installed the ICs, I powered it up without an SD card. This is what I saw:</p> <p><a href="http://10rem.net/media/82841/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_33.png" target="_blank"><img src="http://10rem.net/media/82846/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_15.png" width="660" height="275" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Sweet! I got the skull and crossbones, which means it's working. As an aside, you can also see what I was talking about with the super bright LED. It easily overpowers the playing field. Looks cool in a photo, though, a bit like a Star Trek II photon torpedo.</p> <p>Next, I copied the three game folders (from the zip file referenced on the PIX-6T4 site) on to a 512MB SD card and started it up. I was greeted by the icons of the games I could choose. I picked a game and then…</p> <h3>And…I got nothing.</h3> <p>At first I thought it was this bad solder joint (which makes no sense, but hey, I figured the code was perfect!). It's really hard to see in this picture (and in fact, it looks ok in this picture), but when viewed through a lens you can see that I never heated the pin: the solder goes around the pin but never really joins with it.</p> <p><a href="http://10rem.net/media/82851/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_5.png" target="_blank"><img src="http://10rem.net/media/82856/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_1.png" width="520" height="330" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>I only found this after cleaning the board with flux remover. Flux can fill in holes like that and make it so you can't see them without doing a continuity test. I resoldered that pin (and the one next to it which didn't look much better), and then did a continuity test between the mini and each of the breakout pins, just to make sure there were no others.</p> <p>Still no go.</p> <p>After trying out a few SD cards and getting nothing, and following some other suggestions from Fabien, I still had nothing. Finally, Fabien recommended I dig out that usb-&gt;serial cable (I wasn't able to find it at first) and <a href="http://www.pix6t4.com/deploy-and-debug" target="_blank">compile and re-deploy</a> the current source from Codeplex. Guess what? It worked :)</p> <p>It turns out I had one of the Netduino Minis that had the old firmware on it, and the games were built with new firmware. The deploy got them synchronized.</p> <p>Excellent. I have a working PIX-6T4 :)</p> <h3>The Test Games</h3> <p>I tried the games that come with it. One plays the Pac Man song, one is a game of pong, and the other is an asteroid avoidance game. You have only 64 pixels to work with, and they're all the same color, so like the early days of video games, you need to use your imagination.</p> <p>The stock games are well done. However, since I had the debugger/programmer connected, I wanted to go and build something myself.</p> <h3>My First Game</h3> <p>My first game is going to be a test game. I added a new Netduino application project named "PeteTestGame". I did this in the same solution that contains the PIX6T4 source code and the other three stock games. Next, I added a reference to pix6t4.netduino.helpers project.</p> <p>In the project, I also removed the reference to SecretLabs.NETMF.Hardware.Netduino and add reference to SecretLabs.NETMF.Hardware.NetduinoMini</p> <p>Next, add a GameOfTest class. I named it this following the naming conventions Fabien and Bertrand set up, but you can call it anything. GameOfThrones is an obvious choice ;)</p> <h4>The GameOfTest class</h4> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> using netduino.helpers.Fun;<br /> <br /> namespace PeteTestGame<br /> {<br /> class GameOfTest : Game<br /> {<br /> public GameOfTest(ConsoleHardwareConfig config)<br /> : base(config)<br /> {<br /> <br /> }<br /> <br /> protected override void OnGameEnd()<br /> {<br /> base.OnGameEnd();<br /> }<br /> <br /> protected override void OnGameStart()<br /> {<br /> base.OnGameStart();<br /> <br /> ScrollMessage(" Hello World!");<br /> }<br /> <br /> protected override void OnLeftButtonClick(uint port, uint state, DateTime time)<br /> {<br /> base.OnLeftButtonClick(port, state, time);<br /> }<br /> <br /> protected override void OnRightButtonClick(uint port, uint state, DateTime time)<br /> {<br /> base.OnRightButtonClick(port, state, time);<br /> }<br /> <br /> public override void Loop()<br /> {<br /> <br /> }<br /> }<br /> <br /> } <br /> </pre> <h4>Program</h4> <p>Next, I copied the Program.cs from one of the other projects. I made three changes to the source:</p> <ul> <li>Uncomment the #define dev at the top</li> <li>Changed the namespace to match the rest of the project</li> <li>Changed the class in the Run method to be GameOfTest instead of the one copied from the other project</li> </ul> <p>The program.cs enables you to deploy your test game as the only game on the device, without worrying about building up a manifest and icon and dealing with the SD card and whatnot. Obviously, this also makes debugging possible.</p> <p>Set the game project as startup. Next, right-click and select project properties and navigate to the .NET Micro Framework tab. Make sure the deployment is set to Serial and you have the COM port that your Netduino mini is attached to.</p> <p>Once done, you can run the project. In my case, I immediately saw the scrolling (and repeating) Hello World text. The ScrollMessage function is doing all the heavy lifting. It's great that Fabien put in the nifty retro font in there as well.</p> <p><a href="http://10rem.net/media/82861/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_7.png" target="_blank"><img src="http://10rem.net/media/82866/Windows-Live-Writer_Assembling-the-PIX-6T4-Netduino-powered-_102FF_image_thumb_2.png" width="520" height="408" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>Next step was to add some beeping. Because, well, it's not a game without beeping.</p> <pre class="brush: csharp;"> using System;<br /> using Microsoft.SPOT;<br /> using netduino.helpers.Fun;<br /> <br /> namespace PeteTestGame<br /> {<br /> class GameOfTest : Game<br /> {<br /> public GameOfTest(ConsoleHardwareConfig config)<br /> : base(config)<br /> {<br /> <br /> }<br /> <br /> protected override void OnGameEnd()<br /> {<br /> base.Beep(440, 500);<br /> base.Beep(220, 500);<br /> base.Beep(440, 500);<br /> }<br /> <br /> <br /> protected override void OnGameStart()<br /> {<br /> base.OnGameStart();<br /> <br /> base.Beep(440, 500);<br /> base.Beep(220, 500);<br /> base.Beep(440, 500);<br /> <br /> ScrollMessage(" Hello World!");<br /> }<br /> <br /> protected override void OnLeftButtonClick(uint port, uint state, DateTime time)<br /> {<br /> base.OnLeftButtonClick(port, state, time);<br /> <br /> base.Beep(440, 500);<br /> <br /> }<br /> <br /> protected override void OnRightButtonClick(uint port, uint state, DateTime time)<br /> {<br /> base.OnRightButtonClick(port, state, time);<br /> <br /> base.Beep(220, 500);<br /> <br /> }<br /> <br /> public override void Loop()<br /> {<br /> <br /> }<br /> }<br /> <br /> }<br /> </pre> <p>Now it plays a few beeps on startup and then beeps when you press down on either joystick. Note that that does stop the scrolling text, however.</p> <p>I'll leave the more complex games to a later date :)</p> <blockquote>Update 1/22: To see my first real full game, check out my blog <a href="http://10rem.net/blog/2012/01/22/my-first-real-pix-6t4-game-sixty4racer">post on building Sixty4Racer</a>.</blockquote> <h3>Conclusion</h3> <p>I'm impressed with what Fabien has come up with here, and the stock games he and Bertrand have done are just perfect. I think the board and enclosure could use a little more design to make it more compact and also more hand-friendly, but overall, I think this is an excellent way to get into Netduino programming using something fun and exciting. Also, because of the display technology, you are constrained to creating games with very simple graphics (just LEDs) so, by necessity, you avoid that common barrier to entry. Sometimes constraint is a good thing.</p> <p>Congratulations Fabien and Bertrand!</p> <p><a href="http://feedads.g.doubleclick.net/~a/whKP6-ORjtSSDYR-6izmoWJELE0/0/da"><img src="http://feedads.g.doubleclick.net/~a/whKP6-ORjtSSDYR-6izmoWJELE0/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/whKP6-ORjtSSDYR-6izmoWJELE0/1/da"><img src="http://feedads.g.doubleclick.net/~a/whKP6-ORjtSSDYR-6izmoWJELE0/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=8a8bxEqkR_U:03Ulo_ojlQ8:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=8a8bxEqkR_U:03Ulo_ojlQ8:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=8a8bxEqkR_U:03Ulo_ojlQ8:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=8a8bxEqkR_U:03Ulo_ojlQ8:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/8a8bxEqkR_U" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/8a8bxEqkR_U/assembling-the-pix-6t4-netduino-powered-hand-held-game-system Pete Brown 3548 2012-01-21T08:12:51 WPF 4.5 Cross-Thread Collection Synchronization Redux <p>In my <a href="http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification" target="_blank">post about WPF 4.5 Observable Collection Cross-thread Change Notification</a>, I showed the basics of how to synchronize collection updates in WPF, and how to avoid having to manually dispatch calls to the UI thread. In the comments, Jonathan Allen brought up some very good points that I simply didn't know the answers to (and a lock I was missing in the example). Thanks to Jonathan for keeping me honest :)</p> <blockquote> <p>For background, go back and <a href="http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification" target="_blank">read that post</a>, but then come here for some of the updates.</p> </blockquote> <p>So, rather than guess at the answers, I went right to the guy responsible for most (all?) of the design of the binding structure in WPF: Sam Bent. I also went back to the spec document, and also dove deeper into what's actually happening.</p> <h3>Locking</h3> <p>Q: If I'm using collection synchronization, do I need to lock my own access to the collection?</p> <p>A: Yes, you do. The collection won't do any locking by itself (I had thought that ObservableCollection was doing some, but both Jonathan and Sam corrected me here. Sam also pointed out this works with just about any collection).&nbsp; Having the collection handle any locking internally is "full of pitfalls" (Sam's words, which I agree with having seen the examples) and was abandoned early in .NET's development cycle.</p> <p>What this does mean, is that my example from the previous post really needed to lock the collection add call. Here's the updated viewmodel source.</p> <pre class="brush: csharp;"> class MainViewModel<br /> {<br /> public ObservableCollection&lt;Stock&gt; Stocks { get; private set; }<br /> <br /> private object _stocksLock = new object();<br /> <br /> public MainViewModel()<br /> {<br /> Stocks = new ObservableCollection&lt;Stock&gt;();<br /> <br /> BindingOperations.EnableCollectionSynchronization(Stocks, _stocksLock);<br /> }<br /> <br /> <br /> private Random _random = new Random();<br /> public void AddNewItems()<br /> {<br /> lock (_stocksLock)<br /> {<br /> for (int i = 0; i &lt; 100; i++)<br /> {<br /> var item = new Stock();<br /> for (int j = 0; j &lt; _random.Next(2, 4); j++)<br /> {<br /> item.Symbol += char.ConvertFromUtf32(_random.Next(<br /> char.ConvertToUtf32("A", 0),<br /> char.ConvertToUtf32("Z", 0)));<br /> }<br /> <br /> item.Value = (decimal)(_random.Next(100, 6000) / 100.0);<br /> Stocks.Add(item);<br /> Debug.WriteLine(item.Symbol);<br /> }<br /> }<br /> }<br /> <br /> <br /> public void StartAddingItems()<br /> {<br /> Task.Factory.StartNew(() =&gt;<br /> {<br /> while (true)<br /> {<br /> AddNewItems();<br /> Thread.Sleep(500);<br /> }<br /> });<br /> <br /> }<br /> } </pre> <p>Notice how I lock the entire loop in AddNewItems. Depending on what's going on inside that loop, or how many iterations, that may simply be too large/long a lock. If you need a smaller/shorter lock, it would be safe to wrap the Add call instead, like this:</p> <pre class="brush: csharp;"> private Random _random = new Random();<br /> public void AddNewItems()<br /> {<br /> for (int i = 0; i &lt; 100; i++)<br /> {<br /> var item = new Stock();<br /> for (int j = 0; j &lt; _random.Next(2, 4); j++)<br /> {<br /> item.Symbol += char.ConvertFromUtf32(_random.Next(<br /> char.ConvertToUtf32("A", 0),<br /> char.ConvertToUtf32("Z", 0)));<br /> }<br /> <br /> item.Value = (decimal)(_random.Next(100, 6000) / 100.0);<br /> lock (_stocksLock)<br /> {<br /> Stocks.Add(item);<br /> }<br /> Debug.WriteLine(item.Symbol);<br /> }<br /> } </pre> <p><strong>I'll leave the choice of which one is appropriate up to the folks designing individual applications</strong>. It sounds like a punt, but it really is up to you guys. Each has merits (fewer lock acquisitions in first one, more atomic actions and less thread blocking in second one), but really are very application dependent. In my particular demo example here, with 100 iterations, I'm fine with handling the lock outside the loop.</p> <h3>About the EnableCollectionSynchronization Overload</h3> <p>Here's one question that Sam answered. I'll quote him directly.</p> <p>Q: Why are there two overloads to EnableCollectionSynchronization?</p> <p>A: <em>You pick which overload of EnableCollectionSynchronization to use based on how your app synchronizes access to its own collection. If you're using fancy synchronization primitives - semaphores, ReadWriteLock, ManualResetEvent, etc. - you'd use the overload with a callback argument. Whenever WPF needs to touch the collection, it calls the callback, which uses the fancy primitive to gain the right permissions. On the other hand, if you're just using&nbsp; "lock(x) { … }", you'd use the overload with just a lock object, passing in x. Whenever WPF needs to touch the collection, it also does "lock(x) { … }". This saves one level of callback in the common case.</em></p> <p>Also note that you can use <strong>BindingOperations.DisableCollectionSynchronization(collection)</strong> when you want to stop using synchronization.</p> <h3>Getting in Before the CollectionView is Created</h3> <p>The BindingOperations class provides an event CollectionRegistering, which lets you register the collection for cross-thread access before any CollectionView instances are generated for it. Inside this event handler, you can do the actual registering of the collection. Here's an example:</p> <pre class="brush: csharp;"> public ObservableCollection&lt;Stock&gt; Stocks { get; private set; }<br /> <br /> private object _stocksLock = new object();<br /> <br /> public MainViewModel()<br /> {<br /> Stocks = new ObservableCollection&lt;Stock&gt;();<br /> BindingOperations.CollectionRegistering += BindingOperations_CollectionRegistering;<br /> <br /> //BindingOperations.EnableCollectionSynchronization(Stocks, _stocksLock);<br /> }<br /> <br /> void BindingOperations_CollectionRegistering(object sender, CollectionRegisteringEventArgs e)<br /> {<br /> Debug.WriteLine("CollectionRegistering Event");<br /> if (e.Collection == Stocks)<br /> {<br /> BindingOperations.EnableCollectionSynchronization(Stocks, _stocksLock);<br /> }<br /> } </pre> <p>This is actually a good place to handle the registration, as you know you'll get to register for cross-thread access at the right time, before any dependent objects are created. In fact, I'd say this is the <strong>best place to do the EnableCollectionSynchronization call</strong>. You can do it in the constructor for simple apps, but once you start adding views (and sorting, and datagrids and the like), you'll want to do it here.</p> <h3>What's Happening with Change Notifications</h3> <p>Inside the framework, pending change notifications from the background thread are queued up and then acted on by the UI thread when it has time. This helps increase responsiveness for higher-priority events like input (mouse, keyboard, touch).</p> <p>This is new for .NET 4.5 as part of this cross-thread collection work.</p> <h3>Fin</h3> <p>I hope you've found that this post helps clarify some of the open questions from the previous post. The work the WPF team has done to enable cross-thread collections is a fair bit of work, even though it surfaces through a pretty small API. This is also one of those features which will simply make your own application code nicer, with less plumbing gunk in it.</p> <p>Updated example code attached.</p> <p><a href="http://feedads.g.doubleclick.net/~a/xpK0io5Brd9EB7uUB9hgei-WUrc/0/da"><img src="http://feedads.g.doubleclick.net/~a/xpK0io5Brd9EB7uUB9hgei-WUrc/0/di" border="0" ismap="true"></img></a><br/> <a href="http://feedads.g.doubleclick.net/~a/xpK0io5Brd9EB7uUB9hgei-WUrc/1/da"><img src="http://feedads.g.doubleclick.net/~a/xpK0io5Brd9EB7uUB9hgei-WUrc/1/di" border="0" ismap="true"></img></a></p><div class="feedflare"> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:I9og5sOYxJI"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=I9og5sOYxJI" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/PeteBrown?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=c2zpPWogWkQ:xz9n4qu29zE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=c2zpPWogWkQ:xz9n4qu29zE:F7zBnMyn0Lo" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/PeteBrown?a=c2zpPWogWkQ:xz9n4qu29zE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/PeteBrown?i=c2zpPWogWkQ:xz9n4qu29zE:gIN9vFwOqvQ" border="0"></img></a> </div><img src="http://feeds.feedburner.com/~r/PeteBrown/~4/c2zpPWogWkQ" height="1" width="1"/> http://feedproxy.google.com/~r/PeteBrown/~3/c2zpPWogWkQ/wpf-45-cross-thread-collection-synchronization-redux Pete Brown 3547 2012-01-20T22:59:04 Suche der BlogEngine.NET mittels Knockout.js verbessern - ein Praxisbeispiel <p>In meiner <a title="Serie zu Knockout.js" href="http://devtyr.norberteder.com/?tag=/Knockout.js">Serie zu Knockout.js</a> habe ich bereits gezeigt, was diese Bibliothek leisten kann. Nichts liegt also näher, als das eigene Blog ein wenig auf Vordermann zu bekommen. Stein des Anstoßes ist die Suche, die bei <a title="BlogEngine .NET" href="http://www.dotnetblogengine.net/" target="_blank">BlogEngine.NET</a> sehr altertümlich wirkt und in meinem neuen Design daher bis dato nicht berücksichtigt wurde. Mit Knockout.js sollte eine verbesserte Variante schnell implementiert sein. Da die Suchergebnisse durch diverse Bots auch nicht indiziert werden müssen, kann Knockout.js bedenkenlos eingesetzt werden. Hier mein Erfahrungsbericht.</p> <h1>Kontext</h1> <p>Um den Kontext für diejenigen zu setzen, die sich weder mit der BlogEngine.NET beschäftigt haben, noch dies wollen, ein kurze Einführung wie die Zusammensetzung hinsichtlich der Suche ist und was alles benötigt wird. Wenn Erfahrungen mit eben diesem System bestehen, kann dieser Abschnitt übersprungen werden.</p> <p>Die Suche der BlogEngine.NET besteht aus folgenden Teilen:</p> <ul> <li>Klasse <font face="Courier New">Search</font>, welche die Suche übernimmt und Ergebnisse zurück liefert. </li> <li>Ein Steuerelement <font face="Courier New">SearchBox</font>, welches ein Input-Feld, eine Schaltfläche und noch ein paar andere Dinge rendert. Das Input-Feld selbst reagiert auf ein Enter und leitet eigentlich nur auf die Suchseite weiter. </li> <li>Ein Widget, welches das Steuerelement verwendet </li> <li>Eine Suchseite (<font face="Courier New">search.aspx</font>), welche ebenfalls das angesprochene Steuerelement verwendet. </li> </ul> <p>Das ist grob alles. Um das gesamte User Interface der Suche ersetzen zu können, ist es also notwendig, ein einzelnes Steuerelement (zum Einbinden im Header etc.), ein Widget und eine dedizierte Suchseite anzubieten.</p> <h1>Suche via AJAX ermöglichen</h1> <p>Die Suchfunktionalität besteht bereits (durch die Klasse <font face="Courier New">Search</font>). Diese sollte soweit bestehen bleiben, um auch mit zukünftigen Versionen kompatibel zu sein. </p> <p>Was allerdings fehlt, ist ein Service, um dies nutzen zu können. Damit das Ergebnis am Client gut verarbeitet werden kann, liefert dieses JSON zurück. Zusätzlich werden auf Einstellungen des Blogsystems zurück gegriffen, um die Beschreibungen der Posts für die Ergebnisliste entsprechend zu kürzen. Der Code hierfür kann man sich dann im Download-Package ansehen.</p> <h1>Knockout.js ViewModel</h1> <p>Damit in der UI die notwendigen Bindungen gesetzt werden können, ist ein entsprechendes ViewModel notwendig, nennen wir es <font face="Courier New">FastSearchViewModel</font>. Welche Möglichkeiten soll es bieten?</p> <ol> <li>Es muss der Suchbegriff abgefasst werden. </li> <li>Der Benutzer kann wählen, ob Kommentare durchsucht werden sollen. Diese Information ist ebenfalls durch das ViewModel zu halten. </li> <li>Die AJAX-Suche muss unter Angabe des Suchbegriffs durchgeführt werden. </li> <li>Eine Liste der Ergebnisse ist zur Verfügung zu stellen. </li> <li>Der Benutzer möchte Rückmeldungen á la “Keine Suchergebnisse” erhalten. Diese müssen angeboten werden. </li> <li>Die Suche kann theoretisch dreimal auf einer Seite vorkommen (Suchfeld, Widget und Suchseite). Dieser Umstand ist zu berücksichtigen (wichtig für die Datenbindung auf einen bestimmten Bereich, da sonst alle Suchmöglichkeiten mit derselben Bindung ausgestattet werden). </li> <li>Schlussendlich bedarf es noch Kleinigkeiten, wie das Zurücksetzen des Eingabefeldes bei einem Klick bzw. dem Auslösen der Suche bei Betätigen der Enter-Taste. </li> </ol> <p>All diese Dinge wurden im nachfolgenden ViewModel berücksichtigt.</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 600px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> FastSearchViewModel = <span style="color: #0000ff">function</span> (containerElement) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">var</span> self = <span style="color: #0000ff">this</span>;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.container = containerElement;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.searchValue = ko.observable(<span style="color: #006080">''</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.includeComments = ko.observable(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.foundItems = ko.observableArray();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.showNoItemsFound = ko.observable(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasSearchValue = ko.computed(<span style="color: #0000ff">function</span> () { <span style="color: #0000ff">return</span> self.searchValue().length &gt; 0; }, self);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasResult = ko.observable(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.notExecuted = ko.observable(<span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.search = <span style="color: #0000ff">function</span> () {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">var</span> dto = { <span style="color: #006080">&quot;searchTerm&quot;</span>: self.searchValue(), <span style="color: #006080">&quot;includeComments&quot;</span>: self.includeComments() };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> $.ajax({</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> url: <span style="color: #006080">&quot;uri/to/searchmethod&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> data: JSON.stringify(dto),</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> type: <span style="color: #006080">&quot;POST&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> contentType: <span style="color: #006080">&quot;application/json; charset=utf-8&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> dataType: <span style="color: #006080">&quot;json&quot;</span>,</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> success: <span style="color: #0000ff">function</span> (result) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.foundItems = ko.observableArray();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">if</span> (result.d.Posts &amp;&amp; result.d.Posts.length &gt; 0) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">for</span> (<span style="color: #0000ff">var</span> i = 0; i &lt; result.d.Posts.length; i++) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.foundItems().push(<span style="color: #0000ff">new</span> ResultViewModel(result.d.Posts[i].Title, result.d.Posts[i].Content, result.d.Posts[i].Link));</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.showNoItemsFound(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasResult(<span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> } <span style="color: #0000ff">else</span> {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.showNoItemsFound(<span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasResult(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.notExecuted(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> ko.applyBindings(self, self.container);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> });</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.inputChanged = <span style="color: #0000ff">function</span> (sender, <span style="color: #0000ff">event</span>) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">if</span> (<span style="color: #0000ff">event</span>.keyCode === 13 &amp;&amp; self.hasSearchValue()) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.search();</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.clear = <span style="color: #0000ff">function</span> (sender, <span style="color: #0000ff">event</span>) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.searchValue(<span style="color: #006080">''</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasResult(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.showNoItemsFound(<span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.notExecuted(<span style="color: #0000ff">true</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.closePopup = <span style="color: #0000ff">function</span> () {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> self.hasResult(<span style="color: #0000ff">false</span>);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> };</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <p>Zu beachten ist, dass die Ergebnisse aus dem AJAX-Aufruf in ein weiteres ViewModel geschrieben werden (<font face="Courier New">ResultViewModel</font>). Dies ist notwendig, damit die Bindungen greifen, die in einem Template für die einzelnen Einträge festgelegt sind. Hier der entsprechende Code:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"><span style="color: #0000ff">var</span> ResultViewModel = <span style="color: #0000ff">function</span> (postTitle, postContent, postLink) {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.title = ko.observable(postTitle);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.content = ko.observable(postContent);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">this</span>.link = ko.observable(postLink);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">};</pre> <!--CRLF--></div> </div> <h1>User-Interface</h1> <p>Das User-Interface muss nun die für die Suche notwendigen Elemente zur Verfügung stellen und die Bindungen festlegen. Hier das auf die Suchseite vereinfachte Markup:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 600px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;div id=<span style="color: #006080">&quot;fastsearchpage&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;input id=<span style="color: #006080">&quot;fastsearchbox&quot;</span> data-bind=<span style="color: #006080">&quot;value: searchValue, valueUpdate: 'afterkeydown', event: { keyup: inputChanged, click: clear }&quot;</span> placeholder=<span style="color: #006080">&quot;Suche&quot;</span> onkeypress=<span style="color: #006080">&quot;return noenter()&quot;</span>&gt;&lt;/input&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;input id=<span style="color: #006080">&quot;fastsearchcomment&quot;</span> type=<span style="color: #006080">&quot;checkbox&quot;</span> data-bind=<span style="color: #006080">&quot;checked: includeComments&quot;</span>&gt;Kommentare inkludieren&lt;/input&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;input type=<span style="color: #006080">&quot;button&quot;</span> id=<span style="color: #006080">&quot;fastsearchbutton&quot;</span> data-bind=<span style="color: #006080">&quot;click: search, enable: hasSearchValue&quot;</span> value=<span style="color: #006080">&quot;Durchsuchen&quot;</span>&gt;&lt;/input&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div id=<span style="color: #006080">&quot;fastsearchresult&quot;</span> data-bind=<span style="color: #006080">&quot;template: { name: 'searchresult-template', foreach: foundItems }&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div id=<span style="color: #006080">&quot;nofastsearchresult&quot;</span> data-bind=<span style="color: #006080">&quot;visible: showNoItemsFound&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> Keine Suchergebnisse.</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div id=<span style="color: #006080">&quot;nofastsearchexecuted&quot;</span> data-bind=<span style="color: #006080">&quot;visible: notExecuted&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> Geben Sie einen Suchbegriff ein und drücken Sie Enter oder die Suchen-Schaltfläche.</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/p&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;script type=<span style="color: #006080">&quot;text/html&quot;</span> id=<span style="color: #006080">&quot;searchresult-template&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div <span style="color: #0000ff">class</span>=<span style="color: #006080">&quot;searchitem&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;a data-bind=<span style="color: #006080">&quot;attr: { href: link, title: title }&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;span&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;strong data-bind=<span style="color: #006080">&quot;text: title&quot;</span>&gt;&lt;/strong&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/span&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/a&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;div data-bind=<span style="color: #006080">&quot;text: content&quot;</span>/&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/div&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/script&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&#160;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;script type=<span style="color: #006080">&quot;text/javascript&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">function</span> noenter() {</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> <span style="color: #0000ff">return</span> !(window.<span style="color: #0000ff">event</span> &amp;&amp; window.<span style="color: #0000ff">event</span>.keyCode == 13);</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> }</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> &lt;/script&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;/div&gt;</pre> <!--CRLF--></div> </div> <p>Der Großteil der darin verwendeten Bindungen bringen zur <a title="Serie zu Knockout.js" href="http://devtyr.norberteder.com/?tag=/Knockout.js" target="_blank">Knockout.js-Serie</a> nichts Neues. Interessant ist das Eingabefeld selbst:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;input id=<span style="color: #006080">&quot;fastsearchbox&quot;</span> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> data-bind=<span style="color: #006080">&quot;value: searchValue, valueUpdate: 'afterkeydown', event: { keyup: inputChanged, click: clear }&quot;</span> </pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: white; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px"> placeholder=<span style="color: #006080">&quot;Suche&quot;</span> onkeypress=<span style="color: #006080">&quot;return noenter()&quot;</span>&gt;</pre> <!--CRLF--> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; background-color: #f4f4f4; margin: 0em; border-left-style: none; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &#39;Courier New&#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; border-right-style: none; font-size: 8pt; overflow: visible; padding-top: 0px">&lt;/input&gt;</pre> <!--CRLF--></div> </div> <p>Dieses selbst hat eine Bindung auf <font face="Courier New">searchValue</font> um den eingegebenen Suchbegriff in das ViewModel durchzuschreiben. Ebenfalls wird das Ereignis <font face="Courier New">OnKeyUp</font> abonniert um auf das Betätigen der Enter-Taste hin zu prüfen, da in diesem Fall die Suche ausgelöst werden sollte. Beim Standardverhalten der <font face="Courier New">value</font>-Bindung ist es nun aber so, dass zum Zeitpunkt des Auslösens des Ereignisses (und des Verarbeitens in der Funktion des ViewModels) der eingegebene Wert noch nicht bekannt ist. Das liegt daran, dass die <font face="Courier New">value</font>-Bindung erst beim Verlassen des Fokus die Bindung selbst aktualisiert.</p> <p>Um aber derartige Anforderungen abzudecken, steht der zusätzliche Parameter <font face="Courier New">valueUpdate</font> bereit. Darüber lässt sich konfigurieren, zu welchem Zeitpunkt die gebundene Eigenschaft des ViewModels aktualisiert werden soll. Folgende Möglichkeiten stehen zur Verfügung:</p> <ul> <li><strong>change</strong>: Das ist die Standardeinstellung und aktualisiert das ViewModel wenn der Fokus zu einem anderen Element geht. </li> <li><strong>keyup</strong>: Die Aktualisierung findet beim Loslassen des Tastendrucks statt. </li> <li><strong>keypress</strong>: Das ViewModel wird aktualisiert, wenn eine Taste gedrückt wurde. Hier ist anzumerken, dass dieses Ereignis mehrfach ausgelöst wird, wenn der Benutzer auf der Taste bleibt. </li> <li><strong>afterkeydown</strong>: Hier wird das ViewModel aktualisiert, sobald der Benutzer beginnt, ein Zeichen einzugeben. Diese Auswahl eignet sich bestens dazu, wenn das ViewModel in Echtzeit aktualisiert werden soll. </li> </ul> <p>Stellen wir also <font face="Courier New">valueUpdate</font> auf <font face="Courier New">afterkeydown</font>, steht beim Auslösen der Suche der durch den Benutzer eingegebene Suchbegriff bereit.</p> <blockquote> <p><strong>Hinweis</strong>: Zusätzlich wird beim <font face="Courier New">OnKeyPress</font> noch die Funktion <font face="Courier New">noenter()</font> aufgerufen. Diese dient lediglich dazu, ein Submit eines eventuell vorhandenen Formulars zu verhindern.</p> </blockquote> <h1>Binden des ViewModels</h1> <p>Damit die Bindungen angezogen werden, muss <font face="Courier New">ko.applyBindings</font> aufgerufen werden. Wie bereits oben angesprochen, kann die Suche selbst mehrfach auf der Seite vorkommen. Damit nicht alle an dasselbe ViewModel gebunden werden (und dadurch anspringen, sobald es in einem der Felder eine Aktion gibt), sollte überprüft werden, wie viele Vorkommen vorhanden sind und an jedes eine eigene Instanz des ViewModels gebunden werden.</p> <h1>Das Ergebnis</h1> <p>Für die Verwendung unter BlogEngine.NET ist nun nicht mehr viel zu tun und weniger relevant, da lediglich Widget-Control (als Host) implementiert werden muss etc. Dies kann sich der interessierte Leser im Download genauer ansehen.</p> <p>Das Resultat kann sich jeder hier im Blog ansehen, da alle Suchmöglichkeiten auf dieser Implementierung beruhen.</p> <p>So sieht die Einbindung des Suchfeldes aus:</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_120.png"><img style="background-image: none; border-right-width: 0px; margin: 0px 10px 10px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="FastSearch für BlogEngine.NET Searchbox" border="0" alt="FastSearch für BlogEngine.NET Searchbox" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_74.png" width="604" height="329" /></a></p> <p>Und hier noch die Suchseite:</p> <p><a href="http://devtyr.norberteder.com/image.axd?picture=image_121.png"><img style="background-image: none; border-right-width: 0px; margin: 0px 10px 10px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="FastSearch für BlogEngine.NET Search Page" border="0" alt="FastSearch für BlogEngine.NET Search Page" src="http://devtyr.norberteder.com/image.axd?picture=image_thumb_75.png" width="604" height="389" /></a></p> <h1>Download / BlogEngine.NET Package</h1> <p>Der Download ist über die <a title="Download FastSearch BlogEngine.NET package" href="http://dnbegallery.org/cms/List/Extensions/FastSearch" target="_blank">BlogEngine.NET Gallery</a> verfügbar. Darin befindet sich die Erweiterung zur BlogEngine.NET selbst, als auch die gesamte Implementierung. Wer diese Erweiterung für sein Blogsystem basierend auf BlogEngine.NET einsetzen möchte, findet weitere Informationen unter <a title="FastSearch for BlogEngine.NET" href="http://devtyr.com/fastsearch.html" target="_blank">http://devtyr.com/fastsearch.html</a>.</p> <h1>Feedback</h1> <p>Wie immer freue ich mich über jegliches Feedback. Sowohl zum Code selbst, als auch grundsätzlich zur Funktionalität der BlogEngine.NET-Erweiterung.</p> http://devtyr.norberteder.com/post/Suche-der-BlogEngineNET-mittels-Knockoutjs-verbessern-ein-Praxisbeispiel.aspx Norbert Eder [MVP] 3546 2012-01-19T15:25:52 Full Stack Resumes–MVVM Design http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/l-ZWlwjR-tw/ Jesse Liberty [MS] 3545 2012-01-19T15:15:29 New Videos Uploaded http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/Sm2SJooUVFw/ Jesse Liberty [MS] 3544 2012-01-18T18:50:10 Supersampling Nueva entrega de ja serie sobre técnicas de antialiasing,  esta vez en DesarrolloWeb.com. Espero que os sirva http://speakingin.net/2012/01/17/supersampling/ Juan María Laó Ramos 3543 2012-01-17T22:30:03 Desplegando un ChildWindow en objetos Window en Silverlight 5 Silverlight 5 cuenta con muchas características enfocadas principalmente a la construcción de aplicaciones de negocio, una de ellas es la creación de Ventanas de Sistema. Sin embargo, si necesitas mostrar un ChildWindow dentro de una ventana creada dinámicamente, el comportamiento que obtendrás tal vez no es el esperado, y es que el ChildWindow se mostrará en la raíz de la aplicación (comunmente MainPage) y no en la ventana nueva en donde estás ejecutando el código. Por ejemplo, en la siguiente figura la aplicación crea la ventana de la izquierda… …pero al crear el objeto ChildWindow dentro de la ventana en donde esperas que se despliegue: Este comportamiento no es el esperado ¿cierto? En este artículo explicaré cómo puedes obligar que el ChildWindow se dibuje adecuadamente en la ventana que le corresponda. Puedes leer el artículo completo aquí http://blogs.ligasilverlight.com/2012/01/desplegando-un-childwindow-en-objetos-window-en-silverlight-5/ La Liga Silverlight 3542 2012-01-17T17:33:41 Calling Navigate From The View Model http://feedproxy.google.com/~r/JesseLiberty-SilverlightGeek/~3/8EY0wAQIEDY/ Jesse Liberty [MS] 3541 2012-01-17T17:04:01 WPF 4.5: Observable Collection Cross-Thread Change Notification <p>WPF 4.5 is available as part of the <a href="http://msdn.microsoft.com/en-us/vstudio/hh127353" target="_blank">Visual Studio 11 Developer Preview</a> released at the Build 2011 conference, and is part of the .NET Framework version 4.5. WPF 4.5 addresses several important customer requests including the ability to have cross-thread change notification for collections - the topic of this post.</p> <blockquote><strong>Update 1/20/2012:</strong> I have <a href="http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux"> posted a set of updates</a> to this post with additional information about the details, as well as a few code changes. Be sure to read that post as well as this one.</blockquote> <h3>The Problem</h3> <p>As I explained in my post on <a href="http://10rem.net/blog/2012/01/10/threading-considerations-for-binding-and-change-notification-in-silverlight-5" target="_blank">cross-thread change notification</a>, Silverlight and WPF have the concept of the UI thread and background (or secondary) threads. Code on a background thread is not allowed to interact with UI elements (controls, etc.) on the foreground thread. You can find more information about the general problem in that post.</p> <p>What happens when you need to add and remove items from a collection from that background thread? That's pretty common in applications which must keep a list updated (stock trading is an obvious example). When the collection raises the change notification events, the binding system and listeners, on the UI thread, have code executed. Remember, an event is just a delegate, or really, just a function pointer. So, essentially, the collection is calling a function on the UI thread. That's not allowed, and it throws an exception.</p> <p><img src="http://10rem.net/media/82267/Windows-Live-Writer_Threading-Considerations-for-Binding-in-_EF97_image_2.png"/></p> <p>The approach pre-4.5 was to dispatch all calls to add items to the collection. The diagram from the Silverlight post applies here.</p> <p><a href="http://10rem.net/media/82660/Windows-Live-Writer_WPF.5-Observable-Collection-Cross-Thread_CD9A_image_2.png" target="_blank"><img src="http://10rem.net/media/82665/Windows-Live-Writer_WPF.5-Observable-Collection-Cross-Thread_CD9A_image_thumb.png" width="650" height="265" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>If code on the background thread needs to access elements on the UI thread, set a property for example, it needs to dispatch the call to the UI thread using the dispatcher or synchronization context. Get enough of this happening, such as when constantly updating a collection, and you can run into some real performance problems.</p> <h3>The 4.5 Solution</h3> <p>We actually have a good solution for this in WPF 4.5. The BindingOperations class includes the EnableCollectionSynchronization function. This function helps the binding system and the collection intelligently handle change notification.</p> <p>Let's look at an example. The application, when run, will look like this:</p> <p><a href="http://10rem.net/media/82670/Windows-Live-Writer_WPF.5-Observable-Collection-Cross-Thread_CD9A_image_4.png" target="_blank"><img src="http://10rem.net/media/82675/Windows-Live-Writer_WPF.5-Observable-Collection-Cross-Thread_CD9A_image_thumb_1.png" width="500" height="318" alt="image" border="0" style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px"/></a></p> <p>The stocks in the ListBox are loaded by clicking the "Load" button. To test the change notification, click the Keep Loading button. For this example, the Keep Updating button doesn't do anything (more on that when I cover in-place sorting and updating in another post)</p> <h4>XAML</h4> <p>Just for grins, and to show its inclusion in 4.5, I've put the functionality into another new addition to WPF proper: the Ribbon. I didn't use commands although they are supported.</p> <pre class="brush: xml;"> &lt;RibbonWindow x:Class="WpfApplication2.MainWindow"<br /> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br /> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br /> xmlns:local="clr-namespace:WpfApplication2"<br /> Title="MainWindow" Height="350" Width="525"&gt;<br /> &lt;Grid&gt;<br /> &lt;Ribbon&gt;<br /> &lt;RibbonTab Header="First Tab"&gt;<br /> &lt;RibbonGroup Header="Group 1"&gt;<br /> &lt;RibbonButton x:Name="Foo" Label="Foo!" /&gt;<br /> &lt;RibbonButton x:Name="Bar"<br /> Label="Bar!" /&gt;<br /> &lt;/RibbonGroup&gt;<br /> &lt;RibbonGroup Header="Group 2"&gt;<br /> &lt;RibbonButton x:Name="Load"<br /> Click="Load_Click"<br /> Label="Load" /&gt;<br /> &lt;RibbonButton x:Name="KeepLoading"<br /> Click="KeepLoading_Click"<br /> Label="Keep loading" /&gt;<br /> &lt;RibbonButton x:Name="KeepUpdating"<br /> Label="Keep Updating" /&gt;<br /> <br /> &lt;/RibbonGroup&gt;<br /> &lt;RibbonGroup Header="Count"&gt;<br /> &lt;TextBlock Text="{Binding Stocks.Count}" /&gt;<br /> &lt;/RibbonGroup&gt;<br /> &lt;/RibbonTab&gt;<br /> &lt;RibbonTab Header="Second Tab"&gt;<br /> <br /> &lt;/RibbonTab&gt;<br /> &lt;RibbonTab Header="Stuff that doesn't go anywhere else"&gt;<br /> <br /> &lt;/RibbonTab&gt;<br /> &lt;/Ribbon&gt;<br