<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Dev Notes</title>
	<atom:link href="https://devspoint.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://devspoint.wordpress.com</link>
	<description>Notes on Development with Microsoft Technologies</description>
	<lastBuildDate>Thu, 08 Jul 2021 14:46:58 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<site xmlns="com-wordpress:feed-additions:1">16939628</site><cloud domain='devspoint.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>https://s0.wp.com/i/buttonw-com.png</url>
		<title>Dev Notes</title>
		<link>https://devspoint.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="https://devspoint.wordpress.com/osd.xml" title="Dev Notes" />
	<atom:link rel='hub' href='https://devspoint.wordpress.com/?pushpress=hub'/>
	<item>
		<title>Exploring Tailwind CSS, Vue, and Vuex</title>
		<link>https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/</link>
					<comments>https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Thu, 26 Mar 2020 13:45:00 +0000</pubDate>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Tailwind CSS]]></category>
		<category><![CDATA[VueJS]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=1297</guid>

					<description><![CDATA[Today, I&#8217;m taking a look at Tailwind CSS. This is a utility-first CSS framework. On the landing page of the site it clearly states: Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to &#8230; <a href="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/" class="more-link">Continue reading <span class="screen-reader-text">Exploring Tailwind CSS, Vue, and&#160;Vuex</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Today, I&#8217;m taking a look at <a href="https://tailwindcss.com" target="_blank" rel="noopener">Tailwind CSS</a>. This is a utility-first CSS framework. On the landing page of the site it clearly states:</p>
<blockquote>
<p>Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.</p>
</blockquote>
<p>The goal of the framework is to provide a set of utility classes that help you define the structure and layout of your web application. It is unlike Bootstrap and Foundation because the entire focus of those frameworks is to provide a basic set of components to define a unified look of your website. So my goal is to set out and see if these frameworks work together well to build a simple web application.</p>
<p><span id="more-1297"></span></p>
<p>In order to learn this framework, I will also be utilizing <a href="https://vuejs.org/" target="_blank" rel="noopener">VueJS</a> and <a href="https://vuex.vuejs.org/" target="_blank" rel="noopener">Vuex</a> to create a simple Todo application, which is not the focus of this post. I will take time to highlight some of the code used in building the application, but my main goal is to build an application that I can utilize <em>Tailwind CSS</em> to style the application. This means there are a few prerequisites you will need if you plan to follow along.</p>
<ul>
<li>Node JS will need to already be installed on your computer. You can visit <a href="https://nodejs.org/" target="_blanke">https://nodejs.org/</a> to download the latest LTS version (at the time of this writing, 12.16.1)</li>
<li><a href="https://code.visualstudio.com/" target="_blank" rel="noopener">Visual Studio Code</a> will be used as the primary editor. You can use any other editor you wish, but I find that Visual Studio code offers a lot of features that help with this type of development. I will include a list of extensions I&#8217;m using at the end of this article as well.</li>
<li><a href="https://cli.vuejs.org/" target="_blank" rel="noopener">Vue CLI</a> will be used to scaffold the project. This can be installed with node package manager (npm) by executing the following command at a command prompt: <strong><em>npm install -g @vue/cli</em></strong></li>
</ul>
<h1>Starting a Vue Project with Vue CLI</h1>
<p>Begin by opening up a command line on your computer. Navigate to the path where you will create the project and add a new folder. Navigate to the new folder and type the following command: <strong>vue create tailwind-todo</strong>. This will invoke Vue CLI which will walk you through setting up the new project. Follow the screenshots below to set the options for the project:</p>
<p>Begin by manually selecting the options.</p>
<p><img data-attachment-id="1304" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-1/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png" data-orig-size="373,110" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-1" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png?w=373" class="alignnone size-full wp-image-1304" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png" alt="CLI-1" width="373" height="110" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png 373w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png?w=150&amp;h=44 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png?w=300&amp;h=88 300w" sizes="(max-width: 373px) 100vw, 373px" /></p>
<p>Next, make sure Babel, Vuex, CSS Pre-processors, and Linter / Formatter are selected as pictured.</p>
<p><img data-attachment-id="1305" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-2/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png" data-orig-size="704,305" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-2" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png?w=700" class="alignnone size-full wp-image-1305" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png" alt="CLI-2" width="704" height="305" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png 704w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png?w=150&amp;h=65 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png?w=300&amp;h=130 300w" sizes="(max-width: 704px) 100vw, 704px" /></p>
<p>Babel is a transpiler that converts ES2016+ JavaScript into JavaScript that is executed by the browser. Vuex is a state management library that holds the executing state of the application. CSS Pre-processors allow you to utilize SASS/SCSS or other CSS stylesheet pre-processor frameworks. Linter / Formatter helps enforce coding standards and best practices.</p>
<p>Select a Sass/SCSS processor. I prefer node-sass, but you can use any other if you want. Tailwind CSS uses PostCSS, which is why a CSS Pre-Processor is selected. We will use a Vue CLI plugin to enable the use of PostCSS.</p>
<p><img data-attachment-id="1306" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-3/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png" data-orig-size="1309,198" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-3" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=700" class="alignnone size-full wp-image-1306" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png" alt="CLI-3" width="1309" height="198" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png 1309w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=150&amp;h=23 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=300&amp;h=45 300w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=768&amp;h=116 768w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png?w=1024&amp;h=155 1024w" sizes="(max-width: 1309px) 100vw, 1309px" /></p>
<p>I prefer to have linting turned on and use error prevention only. This will let me know if there are code issues as files are saved, primarily during hot module replacement.</p>
<p><img loading="lazy" data-attachment-id="1307" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-4/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png" data-orig-size="749,223" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-4" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png?w=700" class="alignnone size-full wp-image-1307" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png" alt="CLI-4" width="749" height="223" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png 749w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png?w=150&amp;h=45 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png?w=300&amp;h=89 300w" sizes="(max-width: 749px) 100vw, 749px" /></p>
<p>Lint on save is the default option, but feel free to change based on your needs.</p>
<p><img loading="lazy" data-attachment-id="1308" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-5/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png" data-orig-size="542,211" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-5" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png?w=542" class="alignnone size-full wp-image-1308" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png" alt="CLI-5" width="542" height="211" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png 542w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png?w=150&amp;h=58 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png?w=300&amp;h=117 300w" sizes="(max-width: 542px) 100vw, 542px" /></p>
<p>Use dedicated config files for various utility settings. I just find this easier to review and change when needed. The defaults in these files should work with this application.</p>
<p><img loading="lazy" data-attachment-id="1309" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-6/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png" data-orig-size="1108,225" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-6" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=700" class="alignnone size-full wp-image-1309" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png" alt="CLI-6" width="1108" height="225" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png 1108w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=150&amp;h=30 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=300&amp;h=61 300w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=768&amp;h=156 768w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png?w=1024&amp;h=208 1024w" sizes="(max-width: 1108px) 100vw, 1108px" /></p>
<p>If you want, feel free to make this a preset, but I&#8217;m not going to step through how that is done.</p>
<p><img loading="lazy" data-attachment-id="1310" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-7/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png" data-orig-size="816,213" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-7" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png?w=700" class="alignnone size-full wp-image-1310" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png" alt="CLI-7" width="816" height="213" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png 816w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png?w=150&amp;h=39 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png?w=300&amp;h=78 300w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png?w=768&amp;h=200 768w" sizes="(max-width: 816px) 100vw, 816px" /></p>
<p>Vue CLI will now start to scaffold the project and install all dependencies needed. Once the project is ready you will see a message with information on how to start up the site.</p>
<p><img loading="lazy" data-attachment-id="1311" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-8/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png" data-orig-size="728,140" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-8" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png?w=700" class="alignnone size-full wp-image-1311" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png" alt="CLI-8" width="728" height="140" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png 728w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png?w=150&amp;h=29 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png?w=300&amp;h=58 300w" sizes="(max-width: 728px) 100vw, 728px" /></p>
<p>At this point, if you chose to run the project you should be able to open a browser and see a basic Vue application running in a browser.</p>
<h1>Including NPM Packages and Vue CLI Plugins</h1>
<p>Now that we have a project scaffolded, <strong>Tailwind CSS</strong> needs to be installed. There is a Vue CLI plugin that enables Tailwind in projects which makes this much easier to do. Execute the following command: <strong>vue add vue-cli-plugin-tailwind</strong> and wait for the installation to prompt you concerning a configuration file. Select the minimal configuration.</p>
<p><img loading="lazy" data-attachment-id="1312" data-permalink="https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/cli-9/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png" data-orig-size="671,116" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CLI-9" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png?w=671" class="alignnone size-full wp-image-1312" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png" alt="CLI-9" width="671" height="116" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png 671w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png?w=150&amp;h=26 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png?w=300&amp;h=52 300w" sizes="(max-width: 671px) 100vw, 671px" /></p>
<p>You can learn more about configuring Tailwind CSS here: <a href="https://tailwindcss.com/docs/configuration" target="_blank" rel="noopener">https://tailwindcss.com/docs/configuration</a></p>
<p>The project is almost set and ready to go, but a few more packages will be used. Execute the following command to use npm to install the final dependencies: <strong><em>npm install &#8211;save @mdi/font shortid</em></strong></p>
<p>This installs a stylesheet with some icons and a utility to generate short unique id&#8217;s.</p>
<h1>Cleaning the Slate</h1>
<p>With the project scaffolded and all dependencies installed, run Visual Studio Code by typing <strong><em>code .</em></strong> at the command line. If Visual Studio Code is installed on your computer, it should start and already be pointed at the newly scaffolded project.</p>
<p>First, open the <strong>src</strong> folder and then open <strong>App.vue</strong>. This is the starting point for the application&#8217;s UI/UX, or the root component of the application.</p>
<p>Replace the content with the following:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;template&gt;
  &lt;div id=&quot;app&quot;&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  name: &quot;App&quot;,
  components: {
  },
  data: function() {
    return {
    };
  },
  computed: {
  },
  mounted() {
  },
  methods: {
  }
};
&lt;/script&gt;
&lt;style lang=&quot;postcss&quot;&gt;
&lt;/style&gt;
</pre></div>

<p>If the site is previewed at this point, it will be a completely empty site.</p>
<p>Next, in the <span style="font-weight:bold;">src/components</span> folder go ahead and remove <span style="font-weight:bold;">HelloWorld.vue</span>. This is a sample component that we won&#8217;t be using on our application.</p>
<p>Finally, in the <span style="font-weight:bold;">src</span> folder, open main.js and set the content to the following:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
import Vue from &#039;vue&#039;
import App from &#039;./App.vue&#039;
import store from &#039;./store&#039;
import &#039;./assets/tailwind.css&#039;
import &#039;@mdi/font/css/materialdesignicons.css&#039;;

Vue.config.productionTip = false
new Vue({
  store,
  render: h =&gt; h(App)
}).$mount(&#039;#app&#039;)
</pre></div>

<p>This file bootstraps the application and gets everything started. Notice that the CSS file for tailwind is included. This was done by the Vue CLI Tailwind plugin. Next, the icon CSS file is imported which allows use of the icons in the application.</p>
<p>The application is ready to be developed. The process will be to first create the application and verify that everything works. Don&#8217;t worry, it will be ugly for a little while. Once the application is completed and Tailwind is used to style the application, it should start to look a lot better.</p>
<h1>Vuex</h1>
<p>This project will make use of Vuex to handle state management. Vuex is based on the principles of the <a href="https://facebook.github.io/flux/" target="_blank" rel="noopener">Flux</a> state management pattern. The idea is to control the way state is changed in the application through a predictable means.</p>
<p>At a high level, a Vuex store will include the following properties:</p>
<ul>
<li>State -The current state of the application at any given point. In most implementations of the flux pattern, state should never be modified directly.</li>
<li>Mutations &#8211; Definitions of how state changes throughout the application life. Mutations are the mechanism by which state is changed in the application.</li>
<li>Actions &#8211; Asynchronous methods that commit mutations within the application. The primary difference is that a mutation can never by asynchronous, while actions can be either synchronous or asynchronous.&nbsp;</li>
<li>Getters &#8211; Computed properties based on the current state of the application. Vuex allows for both property based and method based computed properties.&nbsp;</li>
</ul>
<p>Based on the description of a mutation, you might be tempted to execute them directly. There is nothing that would prevent you from doing so &#8212; but I would recommend using actions so that error handling can be done in a predictable fashion with JavaScript promises. You will see this pattern used in the application, even though we are not using an external data store (and we aren&#8217;t explicitly handling errors).</p>
<p>The first thing that needs to be done when setting up a Vuex store is to set what the state will look like. This is also where default values must be set. In this application, an array of tasks will be our only state property. To do this, open <strong>index.js</strong> in <strong>src/store</strong>. You will see a very basic Vuex store already defined with the properties listed above (state, mutations, actions). Getters will not be present. They will need to be added to the store, which will be done later.</p>
<h2>State</h2>
<p>Navigate to the state property and add a property named tasks with a default blank array. It is important in Vuex to set the default value of the store to the value that is expected at runtime. This will become the initial value of the state object at runtime.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
state: {
  tasks: &#91;]
},
</pre></div>

<h2>Mutations</h2>
<p>Mutations are simply functions declared within the store. However, when actions commit mutations, a string is passed so the store knows which mutation to commit. In order to simplify this, many developers will often define mutations as constants in a separate file and then utilize those constants to define the mutations for the application.</p>
<p>To create constants for the mutations needed for the application, add a file to the store folder named mutationTypes.js. Open the new file and add the following code:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
export const ADD_TASK = 'ADD_TASK';
export const COMPLETE_TASK = 'COMPLETE_TASK';
export const CLEAR_TASK = 'CLEAR_TASK';
export const REMOVE_COMPLETED_TASKS = 'REMOVE_COMPLETED_TASKS';
export const UPDATE_TASK = 'UPDATE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
</pre></div>

<p>This file defines multiple constants that will be utilized to define the mutations in the application. The application will be able to add a task (ADD_TASK),, complete a task (COMPLETE_TASK), clear the completed status of a task (CLEAR_TASK), remove all completed tasks (REMOVE_COMPLETED_TASKS), update a task (UPDATE_TASK), and remove a single task (REMOVE_TASK). For the purposes of this article, all of the mutations related to removing tasks will be skipped. Once the pattern is understood, feel free to see if you are able to implement them yourself within the application.</p>
<p>Back in the<span style="font-weight:bold;"> src/store/index.js</span> file, the <span style="font-weight:bold;">mutationTypes.js</span> file needs to be imported. Include the following line at the top of the file with the other import statements. Also, go ahead and import <span style="font-weight:bold;">shortid</span> since it will be used to create id&#8217;s for tasks as they are added by actions.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
import shortid from 'shortid';
import * as Mutation from './mutationTypes';
</pre></div>

<p>Next, we will use the capabilities of ES2016+ to name the mutations as functions within the application. Here&#8217;s the code:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
mutations: {
    &#91;Mutation.ADD_TASK](state, payload) {
      // payload should be the full task, including an id
      state.tasks.push(payload);
    },
    &#91;Mutation.UPDATE_TASK](state, payload) {
      // payload should include the index and the updated task
      state.tasks.splice(payload.index, 1, payload.updatedTask);
    },
    &#91;Mutation.REMOVE_TASK](state, payload) {
      // payload should include the index of the task to remove
      state.tasks.splice(payload.index, 1);
    },
    &#91;Mutation.COMPLETE_TASK](state, payload) {
      let task = state.tasks&#91;payload.index];
      task.complete = true;
    },
    &#91;Mutation.CLEAR_TASK](state, payload) {
      let task = state.tasks&#91;payload.index];
      task.complete = false;
    },
    &#91;Mutation.REMOVE_COMPLETED_TASKS](state) {
      let filteredTasks = state.tasks.filter(t =&gt; !t.complete);
      Vue.set(state, &#039;tasks&#039;, filteredTasks);
    }
  },
</pre></div>

<p>Each mutation makes use of state by passing the state argument. Additionally, for mutations that must make modifications to data a payload is passed to the mutation as well. Inside Chrome with the <a href="https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg" target="_blank" rel="noopener">Vue Developer Tools</a> installed, anytime mutations occur they can be visualized. Additionally, the Vue developer tools allow you to rewind state, replay mutations, and preview the current state.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="737" height="937" data-attachment-id="1325" data-permalink="https://devspoint.wordpress.com/vue-dev-mutations/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png" data-orig-size="737,937" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="vue-dev-mutations" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=236" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=700" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=737" alt="" class="wp-image-1325" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png 737w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=118 118w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=236 236w" sizes="(max-width: 737px) 100vw, 737px" /></figure>


<p>Looking at the code, the add task mutation should include a payload made up of a task object. The task object is simply pushed into the existing array. This is why it is a good idea to initialize the state of the application by setting default values for state.</p>
<p>In the update, remove, complete, and clear mutations a similar pattern is exposed. The payload includes an index which is the index of the task. This is used to splice the new data into the tasks array, or splice date out of the tasks array.</p>
<p>Finally, the remove completed tasks mutation makes use of the <strong>Vue.set()</strong> method to set the state of the tasks to only incomplete tasks. This is done because a limitation to the current reactivity detection of Vue objects and arrays. Using Vue.set() insures all reactivity hooks are executed upon the change of a complex object or array. (see: <a href="https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules" rel="nofollow">https://vuex.vuejs.org/guide/mutations.html#mutations-follow-vue-s-reactivity-rules</a>)</p>
<h2>Getters</h2>
<p>Getters are computed properties based on the current state of the store. This app will include two, a filtered list of completed tasks and a filtered list of active tasks. Here&#8217;s the code:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
getters: {
    completedTasks: (state) =&gt; {
      return state.tasks.filter(t =&gt; t.complete);
    },
    activeTasks: (state) =&gt; {
      return state.tasks.filter(t =&gt; !t.complete);
    }
  },
</pre></div>

<p>A new property is added to the root store named <strong>getters</strong>. Each getter is a function that utilizes state as the first property. In both of the getters for the task store, the <span style="font-weight:bold;">array.filter</span>() function is used to return a new array of tasks that match the supplied criteria (in this case, if complete is set to true or false respectively).</p>
<h2>Actions</h2>
<p>The most complex section of the store is the actions section. Remember earlier that I recommend that all actions be returned as promises so that error handling can be accomplished in a more unified way. Each action will commit a mutation. Once the mutation is committed successfully, the promise will be resolved. If there are any errors during execution &#8212; the promise will be rejected along with an error object.</p>
<p>Here is the code:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
actions: {
    addTask({ commit }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var newTask = { ...payload, id: shortid() };
          commit(Mutation.ADD_TASK, newTask);
          resolve();
        } catch (e) {
          reject(e);
        }
      })
    },
    updateTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload.id);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload.id} was not found`));
          } else {
            commit(Mutation.UPDATE_TASK, { index: taskIndex, updatedTask: payload });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    removeTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.REMOVE_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    completeTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.COMPLETE_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    clearTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.CLEAR_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    removeCompletedTasks({ commit }) {
      return new Promise((resolve, reject) =&gt; {
        try {
          commit(Mutation.REMOVE_COMPLETED_TASKS);
          resolve();
        } catch (e) {
          reject(e);
        }
      });
    }
  },

</pre></div>


<p class="wp-block-paragraph">The addTask action is a function that combines the payload, which should be a complete task object, with a randomly generated id. The new task is used as the payload to commit the ADD_TASK mutation. Since mutations are handled synchronously, the promise is resolved. When resolving the promise, it would also be possible to return the task. However, since the state is automatically maintained by the Vuex store, the UI will automatically be updated whenever a task is added. </p>



<p class="wp-block-paragraph">If there are any errors adding the task, then the promise is rejected. In this case, an error object is always supplied which can be utilized by any calling component to be displayed in the application.</p>


<p>This pattern provides a controlled way to make sure that each mutation should be successful. This application can use the promise.then().catch() pattern to deal with successful and unsuccessful requests to execute actions. This becomes even more important when working with external data or API&#8217;s within the application.&nbsp;</p>
<h2>Final Vuex Store</h2>
<p>Here&#8217;s the full code for the store:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
import Vue from &#039;vue&#039;
import Vuex from &#039;vuex&#039;
import shortid from &#039;shortid&#039;;
import * as Mutation from &#039;./mutationTypes&#039;;
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    tasks: &#91;]
  },
  mutations: {
    &#91;Mutation.ADD_TASK](state, payload) {
      // payload should be the full task, including an id
      state.tasks.push(payload);
    },
    &#91;Mutation.UPDATE_TASK](state, payload) {
      // payload should include the index and the updated task
      state.tasks.splice(payload.index, 1, payload.updatedTask);
    },
    &#91;Mutation.REMOVE_TASK](state, payload) {
      // payload should include the index of the task to remove
      state.tasks.splice(payload.index, 1);
    },
    &#91;Mutation.COMPLETE_TASK](state, payload) {
      let task = state.tasks&#91;payload.index];
      task.complete = true;
    },
    &#91;Mutation.CLEAR_TASK](state, payload) {
      let task = state.tasks&#91;payload.index];
      task.complete = false;
    },
    &#91;Mutation.REMOVE_COMPLETED_TASKS](state) {
      let filteredTasks = state.tasks.filter(t =&gt; !t.complete);
      Vue.set(state, &#039;tasks&#039;, filteredTasks);
    }
  },
  getters: {
    completedTasks: (state) =&gt; {
      return state.tasks.filter(t =&gt; t.complete);
    },
    activeTasks: (state) =&gt; {
      return state.tasks.filter(t =&gt; !t.complete);
    }
  },
  actions: {
    addTask({ commit }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var newTask = { ...payload, id: shortid() };
          commit(Mutation.ADD_TASK, newTask);
          resolve();
        } catch (e) {
          reject(e);
        }
      })
    },
    updateTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload.id);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload.id} was not found`));
          } else {
            commit(Mutation.UPDATE_TASK, { index: taskIndex, updatedTask: payload });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    removeTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.REMOVE_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    completeTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.COMPLETE_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    clearTask({ commit, state }, payload) {
      return new Promise((resolve, reject) =&gt; {
        try {
          var taskIndex = state.tasks.findIndex(t =&gt; t.id === payload);
          if (taskIndex === -1) {
            reject(new Error(`Task id ${payload} was not found`));
          } else {
            commit(Mutation.CLEAR_TASK, { index: taskIndex });
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    },
    removeCompletedTasks({ commit }) {
      return new Promise((resolve, reject) =&gt; {
        try {
          commit(Mutation.REMOVE_COMPLETED_TASKS);
          resolve();
        } catch (e) {
          reject(e);
        }
      });
    }
  },
  modules: {
  }
})

</pre></div>

<h1>Components</h1>
<p>Vue applications consist of one or more components. Each component has the ability to utilize the Vuex store. In this application, there will be a single component that interacts directly with the store. All other components will utilize other mechanisms in view to state management and user interactions. The remaining components for this application include a form to create tasks, a unified way to display tasks, and a way to display a list of tasks.</p>
<h2>TaskForm.vue</h2>
<p>The task form will be used to create and edit tasks. The requirements of the form is to allow the user to enter the name of a task and press enter to confirm it. Optionally, the user may press escape to cancel any pending changes. For mobile users, a button will be used to allow cancelling changes.</p>
<p>To begin, create a new file named <span style="font-weight:bold;">TaskForm.vue</span> in the <span style="font-weight:bold;">src/components</span> folder. This file is a <a href="https://vuejs.org/v2/guide/single-file-components.html" target="_blank" rel="noopener">single page Vue component</a> which will include the template, code, and styling for the component.</p>
<p>Start by putting the following code into the file:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;template&gt;
  &lt;div class=&quot;taskForm&quot;&gt;
    &lt;input
      type=&quot;text&quot;
      v-model=&quot;name&quot;
      placeholder=&quot;Task Name (i.e. Wash Dishes)&quot;
      @input=&quot;$emit(&#039;update:taskName&#039;,$event.target.value)&quot;
      @keyup.enter=&quot;$emit(&#039;save:changes&#039;,$event.target.value)&quot;
      @keyup.esc=&quot;$emit(&#039;cancel:changes&#039;)&quot;
    /&gt;
    &lt;button type=&quot;button&quot; @click.stop=&quot;$emit(&#039;cancel:changes&#039;)&quot;&gt;
      &lt;span class=&quot;mdi mdi-close-circle-outline&quot;&gt;&lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/template&gt;
</pre></div>

<p>This creates a new DIV with the class <span style="font-weight:bold;">taskForm</span>. Inside the DIV is an INPUT and a BUTTON. If you are new to view, you may see some syntax that you may not understand. <span style="font-weight:bold;">v-model</span> binds a property to an element. This is a two-way binding and will change as the value of the input is updated. Additionally, there are multiple attributes with an @ at the start of it. These are event-handlers. All of these event handlers raise custom events using Vue&#8217;s built in <strong>$emit</strong> function. These custom events can be handled by other components.</p>
<p>The<span style="font-weight:bold;"> keyup</span> events in the form make use of <a href="https://vuejs.org/v2/guide/events.html#Event-Modifiers" target="_blank" rel="noopener">event modifiers</a>. This simplifies the code required by the developer. In this component, the enter key and the escape keys are handled independently and raise custom events that can be handled by parent components via the<span style="font-weight:bold;"> $emit()</span> function in Vue.</p>
<p>Now, add the following to the file after the template section. This is the script for the component and is used to handle behavior and state.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;script&gt;
export default {
  props: {
    taskName: {
      type: String,
      default: &quot;&quot;
    }
  },
  data: function() {
    return {
      name: this.taskName
    };
  },
  watch: {
    taskName(newName) {
      this.name = newName;
    }
  }
};
&lt;/script&gt;
</pre></div>

<p>There is single property that is passed into the component, named <span style="font-weight:bold;">taskName</span>. The property is also used to initialize the internal state of the component returned by the data function.</p>
<p>The internal state of the component is tracked independently of the property. However, the component should respond to any external changes that occur due to property changes. This is accomplished by the <strong>watch</strong> function. This function simply watches <strong>taskName</strong> and applies detected changes to the internal state.</p>
<p>The property <strong>taskName </strong>is a string value that is passed to the component upon initialization. Additionally, since this component can be used to edit an existing task, that change should be explicitly tracked by an intentional desire of the user. To accomplish this, the data function returns the state of the current component. In this case, a name property is the only state value in the component. This becomes important later on when the component is used to cancel changes.</p>
<h2>TaskItem.vue</h2>
<p>Create another new file named <span style="font-weight:bold;">TaskItem.vue</span> in the <span style="font-weight:bold;">src/components</span> folder. Add the following template to the file:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;template&gt;
  &lt;div class=&quot;taskItem&quot; :class=&quot;{&#039;done&#039;:complete}&quot;&gt;
    &lt;button type=&quot;button&quot; @click.stop=&quot;onCompleteTask&quot; v-if=&quot;!complete&quot; :disabled=&quot;editMode&quot;&gt;&lt;/button&gt;
    &lt;button type=&quot;button&quot; @click.stop=&quot;onClearTask&quot; v-else :disabled=&quot;editMode&quot;&gt;
      &lt;span class=&quot;mdi mdi-check&quot;&gt;&lt;/span&gt;
    &lt;/button&gt;
    &lt;div v-if=&quot;editMode&quot; class=&quot;taskItemForm&quot;&gt;
      &lt;TaskForm
        :taskName=&quot;taskName&quot;
        @cancel:changes=&quot;onCancelChanges&quot;
        @save:changes=&quot;onSaveChanges&quot;
        ref=&quot;taskForm&quot;
      &gt;&lt;/TaskForm&gt;
    &lt;/div&gt;
    &lt;div v-else @click.stop=&quot;editMode=true&quot; class=&quot;taskName&quot;&gt;{{taskName}}&lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;

</pre></div>

<p>The template is pretty simple. The idea is to allow a user to click on the name of a task to be able to edit the name. The user may also click on the button to toggle the task&#8217;s complete state. Notice this time, many of the custom events are handled directly by methods on this component.</p>
<p>Also, notice the use of the <b>TaskForm</b> component in the template. The form will be used any time a user wants to make changes to the task. The task form uses a colon (:) prior to an attribute. This is a shorthand for a <b>v-bind</b> Vue directive which dynamically sets the value passed to the associated property on the <b>TaskForm</b> component.&nbsp;</p>
<p>The code required for the <b>TaskItem</b> is a little more involved. Place the code into the script section:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;script&gt;
import TaskForm from &quot;./TaskForm&quot;;
export default {
  components: {
    TaskForm
  },
  props: {
    taskId: {
      type: String,
      required: true
    },
    taskName: {
      type: String,
      required: true
    },
    complete: {
      type: Boolean,
      required: true
    }
  },
  data: function() {
    return {
      editMode: false
    };
  },
  watch: {
    editMode(newValue) {
      if (newValue) {
        this.$nextTick(function() {
          document.querySelector(&quot;input&quot;, this.$refs.taskForm.$el).select();
        });
      }
    }
  },
  methods: {
    onSaveChanges(newName) {
      this.editMode = false;
      this.$emit(&quot;update:task&quot;, {
        id: this.taskId,
        name: newName
      });
    },
    onCancelChanges() {
      this.editMode = false;
    },
    onCompleteTask() {
      this.$emit(&quot;complete:task&quot;, this.taskId);
    },
    onClearTask() {
      this.$emit(&quot;clear:task&quot;, this.taskId);
    }
  }
};
&lt;/script&gt;
</pre></div>

<p>The <span style="font-weight:bold;">TaskItem</span> component has properties for the <span style="font-weight:bold;">taskId</span>, <span style="font-weight:bold;">taskName</span>, and <span style="font-weight:bold;">complete</span>. These properties are all marked as required as well, meaning there will be an error displayed if they are not provided to the component at runtime.</p>
<p>This component has a single state object named <span style="font-weight:bold;">editMode</span>. The <span style="font-weight:bold;">editMode</span> state controls whether the component is in an editable state or a non-editable state. Something worth calling out in this component is the watcher. The component watches the <span style="font-weight:bold;">editMode</span> state. If the component enters edit mode, this function fires to make sure the input on the <span style="font-weight:bold;">taskForm</span> is selected. This is done by waiting on Vue to update the DOM and then calling the select function on the input using the <span style="font-weight:bold;">$nextTick</span> Vue function.</p>
<p>Finally, there are a few methods that are called by events raised in the application. The reason many of these methods are in place are to augment the event arguments and change internal state of the component.</p>
<h2>TaskList.vue</h2>
<p>The final component used in the application is the <span style="font-weight:bold;">TaskList</span>. Create a new file in <span style="font-weight:bold;">src/components</span> named <span style="font-weight:bold;">TaskList.vue</span>. The template should be:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;template&gt;
  &lt;div&gt;
    &lt;div v-for=&quot;task in tasks&quot; :key=&quot;task.id&quot;&gt;
      &lt;TaskItem
        :taskId=&quot;task.id&quot;
        :taskName=&quot;task.name&quot;
        :complete=&quot;task.complete&quot;
        @complete:task=&quot;$emit(&#039;complete:task&#039;,$event)&quot;
        @clear:task=&quot;$emit(&#039;clear:task&#039;,$event)&quot;
        @update:task=&quot;$emit(&#039;update:task&#039;,$event)&quot;
      &gt;&lt;/TaskItem&gt;
    &lt;/div&gt;
    &lt;div v-if=&quot;!hasTasks&quot;&gt;
      &lt;p&gt;{{message}}&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
</pre></div>

<p>This template uses the <span style="font-weight:bold;">v-for</span> binding in Vue to iterate over each task to create a new task item.</p>
<p>Additionally, the code for this is fairly simple as well:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;script&gt;
import TaskItem from &quot;./TaskItem&quot;;
export default {
  components: {
    TaskItem
  },
  props: {
    tasks: {
      type: Array,
      default: function() {
        return &#91;];
      }
    },
    message: {
      type: String,
      default: &quot;No tasks&quot;
    }
  },
  computed: {
    hasTasks() {
      return this.tasks.length &gt; 0;
    }
  }
};
&lt;/script&gt;
</pre></div>

<p>The script starts be importing the TaskItem and registering it for use within the component. The properties of this component include a message and tasks. Finally, there is a computed property so the component can display a message in the event there are no tasks.</p>
<h1>Putting everything together, getting the app running.</h1>
<p>At this point, all files can be closed. However, make sure <span style="font-weight:bold;">App.vue</span> in the <span style="font-weight:bold;">src</span> folder is open. This is the core of the application and will manage everything in the Todo application.</p>
<p>First, set the template:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;template&gt;
  &lt;div id=&quot;app&quot;&gt;
    &lt;h1&gt;Tailwind Todo&lt;/h1&gt;
    &lt;div id=&quot;tabs&quot;&gt;
      &lt;div class=&quot;tab&quot; :class=&quot;{&#039;active&#039;:activeTab===&#039;all&#039;}&quot; @click.stop=&quot;activeTab=&#039;all&#039;&quot;&gt;All Tasks&lt;/div&gt;
      &lt;div
        class=&quot;tab&quot;
        :class=&quot;{&#039;active&#039;:activeTab===&#039;active&#039;}&quot;
        @click.stop=&quot;activeTab=&#039;active&#039;&quot;
      &gt;Active Tasks&lt;/div&gt;
      &lt;div
        class=&quot;tab&quot;
        :class=&quot;{&#039;active&#039;:activeTab===&#039;closed&#039;}&quot;
        @click.stop=&quot;activeTab=&#039;closed&#039;&quot;
      &gt;Completed Tasks&lt;/div&gt;
    &lt;/div&gt;
    &lt;div id=&quot;list&quot;&gt;
      &lt;h2 v-if=&quot;activeTab===&#039;active&#039;||activeTab===&#039;all&#039;&quot;&gt;Tasks&lt;/h2&gt;
      &lt;h2 v-else&gt;Completed Tasks&lt;/h2&gt;
      &lt;TaskList
        :tasks=&quot;visibleTasks&quot;
        @complete:task=&quot;onCompleteTask&quot;
        @clear:task=&quot;onClearTask&quot;
        @update:task=&quot;onSaveChanges&quot;
      &gt;&lt;/TaskList&gt;
    &lt;/div&gt;
    &lt;div id=&quot;newForm&quot;&gt;
      &lt;h2&gt;New Task&lt;/h2&gt;
      &lt;TaskForm
        @save:changes=&quot;onAddTask&quot;
        @cancel:changes=&quot;onCancelAddTask&quot;
        :taskName.sync=&quot;taskName&quot;
        :addMode=&quot;true&quot;
      &gt;&lt;/TaskForm&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
</pre></div>

<p>This is a fairly simple template, but let me call out a few things. First, there div with the id tabs is set to contain three additional tabs. These are all individual tabs used in the application to change which tasks are visible. The .active class is applied dynamically as each tab is activated. Clicking on the tab will dynamically set the activeTab to the associated value (i.e. clicking Active sets activeTab to active).</p>
<p>For the app, the code is a bit more complex so each section will be added and explained bit by bit.</p>
<p>First, add the following imports to a script section:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
import { mapState, mapGetters, mapActions } from "vuex";
import TaskList from "./components/TaskList";
import TaskForm from "./components/TaskForm";
</pre></div>

<p>Each of the items that are imported, <strong>mapState</strong>, <strong>mapGetters</strong>, and <strong>mapActions,</strong> are helper functions within Vuex. These take care of the details of mapping the state within the Vuex store to the app.&nbsp;<strong>mapState and mapGetters</strong> will map any state or getters as a computed values in the component. This facilitates communication between the component (<strong>App.vue</strong>) and the Vuex store.</p>
<p>Next, the components used within the application are imported. Those need to be registered in a components property:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
export default {
  name: "App",
  components: {
    TaskList,
    TaskForm
  },
}
</pre></div>

<p>The application template has a couple of state objects that need to be made available in the app state. The first is the active tab, and the second is the name of a task to be added to the application.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
data: function() {
    return {
      activeTab: "all",
      taskName: ""
    };
  },
</pre></div>

<p>Vuex provides the remaining state of the application. All of the vuex state comes into the application as computed properties. Remember, the <strong>mapState</strong> and <strong>mapGetter</strong> functions are used to get state and computed values from the store into the component:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
computed: {
    ...mapState(&#91;"tasks"]),
    ...mapGetters(&#91;"activeTasks", "completedTasks"]),
    visibleTasks() {
      if (this.activeTab === "active") return this.activeTasks;
      if (this.activeTab === "closed") return this.completedTasks;
      return this.tasks;
    }
  },
</pre></div>

<p>Using the spread operator (…), mapState brings in all of the tasks. mapGetters brings in the two getters defined in the Vuex store. These will be mapped to properties with the same name inside the app. Next, an application specific computed property is defined named visibleTasks. All this does is determine which property to return to be displayed in the TaskList component.</p>
<p>We bring in actions in the same way, but they live within the methods property of the application:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
methods: {
    ...mapActions(&#91;
      "addTask",
      "updateTask",
      "removeTask",
      "completeTask",
      "clearTask",
      "removeCompletedTasks"
    ])
  }
</pre></div>

<p>We can utilize these actions within out own code in the application. Here are the remaining methods:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
    onCompleteTask(taskId) {
      this.completeTask(taskId);
    },
    onClearTask(taskId) {
      this.clearTask(taskId);
    },
    onSaveChanges(e) {
      let task = this.tasks.find(t =&gt; t.id === e.id);
      this.updateTask({ ...task, name: e.name });
    },
    onAddTask(newName) {
      this.addTask({ name: newName, complete: false }).then(() =&gt; {
        this.taskName = &quot;&quot;;
      });
    },
    onCancelAddTask() {
      this.taskName = &quot;&quot;;
    },
</pre></div>

<p>Notice in the method <strong>onAddTask</strong>, the promise resolution clears the current task name. The idea is that once the task has been added, the application needs to be ready to accept a new task.</p>
<p>Finally, providing a few sample tasks to review in the app can be done when the app is mounted. This is a method called during the Vue lifecycle:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
mounted() {
    this.addTask({ name: "Sample Task 1", complete: false });
    this.addTask({ name: "Sample Task 2", complete: false });
    this.addTask({ name: "Sample Task 3", complete: true });
  },
</pre></div>

<p>Here&#8217;s the full script for the app:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;script&gt;
import { mapState, mapGetters, mapActions } from &quot;vuex&quot;;
import TaskList from &quot;./components/TaskList&quot;;
import TaskForm from &quot;./components/TaskForm&quot;;
export default {
  name: &quot;App&quot;,
  components: {
    TaskList,
    TaskForm
  },
  data: function() {
    return {
      activeTab: &quot;all&quot;,
      taskName: &quot;&quot;
    };
  },
  computed: {
    ...mapState(&#91;&quot;tasks&quot;]),
    ...mapGetters(&#91;&quot;activeTasks&quot;, &quot;completedTasks&quot;]),
    visibleTasks() {
      if (this.activeTab === &quot;active&quot;) return this.activeTasks;
      if (this.activeTab === &quot;closed&quot;) return this.completedTasks;
      return this.tasks;
    }
  },
  mounted() {
    this.addTask({ name: &quot;Sample Task 1&quot;, complete: false });
    this.addTask({ name: &quot;Sample Task 2&quot;, complete: false });
    this.addTask({ name: &quot;Sample Task 3&quot;, complete: true });
  },
  methods: {
    onCompleteTask(taskId) {
      this.completeTask(taskId);
    },
    onClearTask(taskId) {
      this.clearTask(taskId);
    },
    onSaveChanges(e) {
      let task = this.tasks.find(t =&gt; t.id === e.id);
      this.updateTask({ ...task, name: e.name });
    },
    onAddTask(newName) {
      this.addTask({ name: newName, complete: false }).then(() =&gt; {
        this.taskName = &quot;&quot;;
      });
    },
    onCancelAddTask() {
      this.taskName = &quot;&quot;;
    },
    ...mapActions(&#91;
      &quot;addTask&quot;,
      &quot;updateTask&quot;,
      &quot;removeTask&quot;,
      &quot;completeTask&quot;,
      &quot;clearTask&quot;,
      &quot;removeCompletedTasks&quot;
    ])
  }
};
&lt;/script&gt;
</pre></div>

<h1>Tailwind Styling</h1>
<p>At this point, open a command prompt and navigate to the folder where the project is stored. If you are using Visual Studio Code, you can press &lt;Ctrl&gt;-&lt;Shift&gt;-&lt;~&gt; to open a terminal already test to the current folder. Type <strong>npm run serve</strong> and then open a browser to the url indicated. You should find the application works, but you will discover that it isn&#8217;t very usable. That&#8217;s where Tailwind CSS should help.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="374" height="365" data-attachment-id="1327" data-permalink="https://devspoint.wordpress.com/pre-tailwind-app/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png" data-orig-size="374,365" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="pre-tailwind-app" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=374" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=374" alt="" class="wp-image-1327" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png 374w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=300 300w" sizes="(max-width: 374px) 100vw, 374px" /></figure>


<h2>App.vue</h2>
<p>Again, start with <strong>App.vue</strong>. Add a style section at the bottom of the file for styling. Make sure that the type is PostCSS:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;style lang=&quot;postcss&quot;&gt;
&lt;/style&gt;
</pre></div>

<p>This will allow styles to be created for the application. First, create a style for the body tag of the application:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
body {
    @apply bg-blue-800 flex justify-center content-center h-screen items-center;
}
</pre></div>

<p>The way Tailwind CSS works is through the use of the <strong>@apply</strong>. The details are better explained on the Tailwind CSS site, but in the above rule, a blue background is applied and the body is set to use a CSS flexbox. All of the content is centered and the body is set to the full height of the current screen.</p>
<p>This does little more than center the application on a blue background.</p>
<p>There are several div&#8217;s in the template of <strong>App.vue</strong> that can be leveraged. First, create a rule for the root tag of App.vue:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
#app {
  @apply rounded bg-gray-200 p-8 border border-blue-900 shadow-md flex flex-col flex-grow;
  min-width: 320px;
  max-width: 800px;
}
</pre></div>

<p>This sets the background to a gray color with some padding. It also includes a border that is rounded with a box-shadow.&nbsp; Additionally, the CSS flexbox is used and set to columns instead of rows. The app is starting to look better:</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="865" height="439" data-attachment-id="1328" data-permalink="https://devspoint.wordpress.com/app-1/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png" data-orig-size="865,439" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-1" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=700" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=865" alt="" class="wp-image-1328" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png 865w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=300 300w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=768 768w" sizes="(max-width: 865px) 100vw, 865px" /></figure>


<p>Next, the tabs need to be addressed. If you look back at the template, we have a container div with the id #tabs and children divs with the class .tab. Using these, we can build several styles to control how the tabs look:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
#tabs {
  @apply flex justify-between mb-4;
}
#tabs .tab {
  @apply bg-gray-400 p-2 rounded flex-grow mr-10 text-center;
}
</pre></div>

<p>This will apply some basic styles to the tabs. First, the outer container is set to flex with space between each child. There is a bottom margin applied to provide some additional whitespace at the bottom of the container.</p>
<p>Each tab is set to grey with a rounded background. The text is centered in each tab and a margin is applied to the right of each tab.</p>
<p>However, no margin should be applied to the last tab in the collection. This can easily be done with CSS rules:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
#tabs .tab:last-child {
  @apply mr-0;
}
</pre></div>

<p>The active tab should be indicated using a different color. Again, this is accomplished through standard CSS rules:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
#tabs .tab.active {
  @apply bg-blue-700 text-gray-300;
}
</pre></div>

<p>The application now has a much better look, but there is still a lot of improvement needed.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="818" height="376" data-attachment-id="1329" data-permalink="https://devspoint.wordpress.com/app-2/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png" data-orig-size="818,376" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-2" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=700" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=818" alt="" class="wp-image-1329" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png 818w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=300 300w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=768 768w" sizes="(max-width: 818px) 100vw, 818px" /></figure>


<p>In the template, we have a couple of different headers used. Finish out styling by creating styles for those as well.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
#app h1 {
  @apply text-3xl text-blue-700 font-bold mb-3 self-center;
  text-shadow: 3px 3px 7px rgba(0, 0, 0, 0.3);
}
#app h2 {
  @apply text-xl text-gray-600 font-medium mb-3;
}
</pre></div>

<p>Notice that you can easily mix standard css properties with the PostCSS apply method as well. The application is definitely improved, but the application isn&#8217;t as usable as it needs to be. It might be tempting to continue to apply styles in this area, but in larger applications it is often better to use another feature of <strong>Vue</strong> &#8212; <a href="https://vue-loader.vuejs.org/guide/scoped-css.html" target="_blank" rel="noopener">scoped styles</a>.</p>
<h2>TaskForm.vue</h2>
<p>The first component to tackle is the form. The input needs some additional padding and the cancel button needs to be called out. However, we may not want all inputs in our application to use this new style. Let&#8217;s scope them to this component only by adding a style section that is scoped:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;style lang=&quot;postcss&quot; scoped&gt;
&lt;/style&gt;
</pre></div>

<p>Notice the <strong>scoped</strong> attribute in the style tag. This instructs Vue CLI to target any styles contained within this component to only get applied to the component. This insures when styles are applied, they will be scoped to all instances of this component but will not override any other styles in the application.</p>
<p>The container for the form component has the class .taskForm. Using this, we can begin to style the form:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
.taskForm {
  @apply flex justify-center items-center;
}
input {
  @apply flex-grow p-1 rounded-md;
}
</pre></div>

<p>First, the form will be set to use the CSS flexbox. The input will be allowed to grow. It also has some padding and a rounded border.</p>
<p>Next, the button needs some attention. Since the styles are scoped, just simple create a rule for the button:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
button {
  @apply p-1 rounded-md bg-red-800 ml-2;
  width: 32px;
  height: 32px;
}
button:active, button:focus {
  @apply outline-none;
}
button span {
  @apply text-red-200;
}
</pre></div>

<p>Again, using CSS pseudo selectors, the default outline around an active control is removed. Since there is an icon inside the button, it needs to be included. Notice that the style definition remains fairly simple &#8212; but if you look at the rules that are generated using Chrome Developer Tools, you will see an identifier used to target just those items in the application.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="307" height="119" data-attachment-id="1330" data-permalink="https://devspoint.wordpress.com/data-attribute-css/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png" data-orig-size="307,119" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="data-attribute-css" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=307" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=307" alt="" class="wp-image-1330" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png 307w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=300 300w" sizes="(max-width: 307px) 100vw, 307px" /></figure>


<p>This is how all buttons within this component are styled and do not cause conflicts with other buttons in the application.</p>
<p>Finally, adding a little interactivity to the input might be good as well. When the input is focused, make the background a blue color with a heavier outline. Here&#8217;s the rule:</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
input {
  @apply transition-all duration-150;
}
 input:focus,  input:active {
  @apply bg-blue-200 outline-none shadow-outline;
}
</pre></div>

<p>Now the input will have a short animated transition with a blue background and a heavier outline:</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="762" height="84" data-attachment-id="1331" data-permalink="https://devspoint.wordpress.com/app-3/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png" data-orig-size="762,84" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-3" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=700" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=762" alt="" class="wp-image-1331" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png 762w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=300 300w" sizes="(max-width: 762px) 100vw, 762px" /></figure>


<p>Additionally, if you click on any existing tasks they will show a similar styling. This was done within a single component. As the component is reused throughout the application, all instanced will have the same styles applied.</p>
<h2>TaskItem.vue</h2>
<p>Now that the form looks better, the form items need to be adjusted. Open TaskItem.vue and add a style element to the end of the file. Again, make sure this is a scoped style tag.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
&lt;style lang=&quot;postcss&quot; scoped&gt;
&lt;/style&gt;
</pre></div>

<p>First, apply a few styles from Tailwind to make sure the items are displayed similarly as the form.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
.taskItem {
  @apply flex items-center my-4;
}
</pre></div>

<p>If you look at the app at this point, you&#8217;ll notice everything is starting to line up, but there is no way to complete a task because the button has no width applied.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="383" height="232" data-attachment-id="1332" data-permalink="https://devspoint.wordpress.com/app-4/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png" data-orig-size="383,232" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-4" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=383" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=383" alt="" class="wp-image-1332" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png 383w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=300 300w" sizes="(max-width: 383px) 100vw, 383px" /></figure>


<p>To address this, apply styles to the button element. Remember, this is a scoped item so the styles will not affect the form button in any way.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
button {
  @apply p-1 rounded-full bg-white border border-gray-400 mr-2;
  width: 32px;
  height: 32px;
}
button span {
  @apply text-green-500;
}
</pre></div>

<p>The button now has a defined width and height which allows the user to click to either mark the task completed or incomplete.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="455" height="253" data-attachment-id="1333" data-permalink="https://devspoint.wordpress.com/app-5/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png" data-orig-size="455,253" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-5" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=455" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=455" alt="" class="wp-image-1333" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png 455w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=300 300w" sizes="(max-width: 455px) 100vw, 455px" /></figure>


<p>Notice that the original form button remains the same:</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="108" height="60" data-attachment-id="1334" data-permalink="https://devspoint.wordpress.com/app-6/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-6.png" data-orig-size="108,60" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-6" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-6.png?w=108" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-6.png?w=108" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-6.png?w=108" alt="" class="wp-image-1334" /></figure>


<p>There is one more problem to correct. If you click on the name of a task, the form is now collapsed and doesn&#8217;t fill the entire area.</p>


<figure class="wp-block-image size-large"><img loading="lazy" width="313" height="103" data-attachment-id="1335" data-permalink="https://devspoint.wordpress.com/app-7/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png" data-orig-size="313,103" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="app-7" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=313" src="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=313" alt="" class="wp-image-1335" srcset="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png 313w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=150 150w, https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=300 300w" sizes="(max-width: 313px) 100vw, 313px" /></figure>


<p>The fix is simple, apply a flex-grow to the form. At the same time, go ahead and add a style that changes the display of completed tasks.</p>

<div class="wp-block-syntaxhighlighter-code "><pre class="brush: css; title: ; notranslate">
.taskItem.done .taskName {
  @apply text-gray-600;
  text-decoration: line-through;
}
.taskItem .taskName,
.taskItem .taskItemForm {
  @apply flex-grow;
}
</pre></div>

<p>At this point, the application should be running successfully. Users should be able to add tasks, update tasks, mark tasks as complete or incomplete, and filter tasks. Feel free to explore how to add the final actions to the application. This includes the ability to remove a single task and the ability to remove all completed tasks.</p>
<p>The full source code for this example can be found on GitHub: <a href="https://github.com/cquick/tailwind-vuex-vue-todo-sample">https://github.com/cquick/tailwind-vuex-vue-todo-sample</a></p>
<h1>Final Thoughts</h1>
<p>One of the main reasons I investigated using Tailwind CSS is because so many of the web applications that I have worked on in the past make use of frameworks like Bootstrap, Foundation, or other similar frameworks. The biggest challenge faced in a lot of these frameworks is the strong opinion on the way things must look. While these frameworks allow for rapid prototyping of applications and getting something up and running fairly quickly, there comes a point in the lifecycle of some sites, especially if they are public facing, when they really need to stand apart from their competitors or more closely match design standards of the organization. It&#8217;s at this point when customization of the site starts to get hard.</p>
<p>I have often found myself and my team writing multiple style rules to override the way the base framework looks. This can be error prone and quite difficult for maintenance as it can lead to a lot of extra style rules that may not be needed. This is why I found Tailwind CSS a compelling prospect. It declares itself as a low-level CSS framework without strong opinions.&nbsp;</p>
<p>I did find working with Tailwind introduced new challenges that I didn&#8217;t expect. First, it was really difficult to force myself to use their predefined styles. Second, I didn&#8217;t completely agree with the styles that were available. For example, padding and margins were either less than I desired or far more than I wanted. I also found widths to be a bit frustrating at times as well, which is why there are explicit widths set on the icon buttons in the application. However, one of the real advantages I found using Tailwind is not having to remember how much rounding I was using in different places and the application of colors. In the end, it provided a consistent website as I didn&#8217;t have to remember that I needed 0.25rem of padding &#8212; I just applied p-1 and got it across all components in the application.</p>
<p>Would I use Tailwind CSS again? Probably not. It might be useful for someone who isn&#8217;t as familiar with CSS properties, but for me I still find it much easier to use raw CSS or SCSS stylesheets. I found myself wrestling with styles much more than I would have liked that I knew I could declare quickly and just move forward, as I spent a lot of time digging through the documentation to find things like applying the CSS flexbox to a div. Let&#8217;s face it, it&#8217;s really not much more difficult to write a CSS rule with <b>display: flex</b> vs <b>@apply flex</b>.</p>
<p>However, one thing that I really appreciate about Vue are the scoped style rules. This brings the style for the component and the design of the component into one place, with the assurance that styles from other places won&#8217;t override the way the component should appear. You can also use SCSS mixins, functions, and variables within the style tag of components, so unifying things like colors, padding, shadows, fonts, and font weights can all be declared as assets to be utilized by the various components of the application.</p>
<p>Visual Studio Code Extensions:</p>
<ul>
<li>Tailwind CSS (<a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss">https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss</a>)</li>
<li>Vetur (<a href="https://marketplace.visualstudio.com/items?itemName=octref.vetur">https://marketplace.visualstudio.com/items?itemName=octref.vetur</a>)</li>
</ul>]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2020/03/26/exploring-tailwind-css-vue-and-vuex/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1297</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-1.png" medium="image">
			<media:title type="html">CLI-1</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-2.png" medium="image">
			<media:title type="html">CLI-2</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-3.png" medium="image">
			<media:title type="html">CLI-3</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-4.png" medium="image">
			<media:title type="html">CLI-4</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-5.png" medium="image">
			<media:title type="html">CLI-5</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-6.png" medium="image">
			<media:title type="html">CLI-6</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-7.png" medium="image">
			<media:title type="html">CLI-7</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-8.png" medium="image">
			<media:title type="html">CLI-8</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/cli-9.png" medium="image">
			<media:title type="html">CLI-9</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/vue-dev-mutations.png?w=737" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/pre-tailwind-app.png?w=374" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-1.png?w=865" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-2.png?w=818" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/data-attribute-css.png?w=307" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-3.png?w=762" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-4.png?w=383" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-5.png?w=455" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-6.png?w=108" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2020/03/app-7.png?w=313" medium="image" />
	</item>
		<item>
		<title>An Opinionated Approach to KnockoutJS Components</title>
		<link>https://devspoint.wordpress.com/2019/11/01/an-opinionated-approach-to-knockoutjs-components/</link>
					<comments>https://devspoint.wordpress.com/2019/11/01/an-opinionated-approach-to-knockoutjs-components/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Fri, 01 Nov 2019 19:20:50 +0000</pubDate>
				<category><![CDATA[KnockoutJS]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=1187</guid>

					<description><![CDATA[In the prior post, I mentioned my organization began to use KnockoutJS within an application we have developed for one of our clients. You can get a little more background on the project by reading my prior post. When I first started creating components with KnockoutJS, there wasn&#8217;t any specific pattern established for handling state, &#8230; <a href="https://devspoint.wordpress.com/2019/11/01/an-opinionated-approach-to-knockoutjs-components/" class="more-link">Continue reading <span class="screen-reader-text">An Opinionated Approach to KnockoutJS&#160;Components</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>In the prior post, I mentioned my organization began to use KnockoutJS within an application we have developed for one of our clients. You can get a little more background on the project by reading my prior post. </p>
<p>When I first started creating components with KnockoutJS, there wasn&#8217;t any specific pattern established for handling state, properties, computed values, subscribed listeners, and event bindings. In hind-site, it began creating challenges when maintaining code as each developer had a different way to organize their code.</p>
<p>I took some time about 18 months ago and began to define a pattern that could be used when developing components and view models within the application. The hope was to improve the quality of the code produced and to help when maintaining the code in the future. Having unified patterns can help teams in several ways, from reviewing requirements to final code reviews. It can produce a common vocabulary and starting point for adding or updating the functionality of the application, and can help on-board new developers to the project.<br />
<span id="more-1187"></span></p>
<h1>Code Pattern Evolution</h1>
<p>Other projects that I worked on over the past several years have involved ReactJS and VueJS. I spent some significant time exploring and using both frameworks to produce applications for clients. I have learned a lot of valuable insight into the patterns that both of these libraries utilize.</p>
<p>One thing that I initially struggled to understand in ReactJS is the unidirectional flow of state. The idea that state has to be updated in response to an event or activity was a continual battle through the early stages of learning the framework. However, the ability to trace how state has changed in a controlled fashion gives React a very predictable method of altering state. This makes debugging and testing much easier as you can quickly see how state has changed for any given React component. It becomes even more powerful when you combine it with a state management library like Redux.</p>
<p>I have been watching VueJS over the past several years as well. Earlier this year, I used VueJS to replace an aging time reporting application that my company uses. One of the things that I grew to love about VueJS is the structured approach to building components. Coming from building components with KnockoutJS, this really helped me form an opinion on the way it should be done.  </p>
<p>I took ideas from both ReactJS and VueJS that ended up creating a structured approach for the application in two major areas:</p>
<ol>
<li>Develop a standardized way to build components and view models</li>
<li>Insure all requests for external data is handled outside of components and view models</li>
</ol>
<h2>Standardized Structure</h2>
<p>Both VueJS and React have a specific way in which to declare components or view models. Using these frameworks as a model, I started by standardizing the internal structure of view models and components. Each view model now begins with a structure that looks like the following:</p>
<pre class="brush: jscript; title: ; notranslate">
define('SomeViewModel', ['knockout'], function (ko){
	function SomeViewModel(options) {
		 var self = this;

		 self.state = {
			 buttonClickCount: ko.observable(0)
		 };
		 self.calc = {
			 hasBeenClicked: ko.pureComputed(function(){
				 var count = ko.unwrap(this.state.buttonClickCount);
				 return count &gt; 0;
			 }, self)
		 };
		 self.listeners = {};

         self.onButtonClick = self.onButtonClick.bind(self);
	}

	SomeViewModel.prototype.dispose = function (){
		// example to iterate over any assigned listeners
		// and then dispose of them -- code has not been tested
		Object.keys(this.listeners).forEach(function (key) {
			this.listeners[key].dispose();
		});
	};

    SomeViewModel.prototype.onButtonClick = function (data, e) {
		// handle state change here
		var currentCount = ko.unwrap(this.state.buttonClickCount);
		currentCount += 1;
		this.state.buttonClickCount(currentCount);
	};

	return SomeViewModel;
});
</pre>
<p>View models now have <strong>state</strong>, <strong>calc</strong>, and <strong>listeners</strong> properties. <strong>state</strong> holds the internal state of the view model/component. <strong>calc</strong> is used to store any calculated values based on the internal state of the view model. <strong>listeners</strong> is a place to easily register additional subscriptions for Knockout observables which can then be later cleaned up by the <strong>dispose</strong> function.</p>
<p>Components have a very similar approach, but there is now a <strong>props</strong> property that holds incoming values for the component. The incoming props are often observables from the view model. In this scope, <strong>state</strong> is intended to contain values that are only applicable to the current component. </p>
<p>As an example, this component has a property on the <strong>state</strong> object called collapsed that can be used to indicate the collapsible state of the component. Notice that <strong>props</strong> has the incoming parameters for the control&#8217;s label and the initial value. Since KnockoutJS utilizes two-way binding, the component can simply bind to the property. Additionally, all bindings that are handled by events are bound to the component view model so the reference to <em>this</em> is always the component&#8217;s view model. </p>
<pre class="brush: jscript; title: ; notranslate">
define('MyAwesomeComponent',
	['knockout', 'text!./myawesomecomponent.template.html'],
	function (ko, template) {

	function MyAwesomeComponent(params, componentInfo) {
		var self = this;

		self.$root = componentInfo.element;

		self.props = {
			label: params.Label,
			value: params.Value
		}

		self.state = {
			collapsed: ko.observable(false)
		}

        self.onToggle = self.onToggle.bind(self);
	}

	MyAwesomeComponent.prototype.onToggle = function (data, event) {
		var collapsed = ko.unwrap(data.state.collapsed);
		data.state.collapsed(!collapsed);
	};

	function MyAwesomeComponentFactory(params, componentInfo) {
		var component = new MyAwesomeComponent(params, componentInfo);
		return component;
	}

	return {
		viewModel: {
			createViewModel: MyAwesomeComponentFactory
		},
		template: template
	}
});
</pre>
<p>Additionally, all components now make use of the createViewModel factory method so it becomes possible to pass in the context of the component. This allows easy discovery of the bound HTML element if needed. </p>
<p>By going to this strategy, other developers who have joined the team are quickly able to come to an understanding of the intent of the code. Developers know when they see <b>state</b>, the intent is to store state of the component or view model. When they see <b>calc</b>, they know these are all computed values. In components, when they see <b>props</b> it is inferred this is where I will find all of my passed in parameters for the component. <b>listeners</b> contains any subscribed listeners.</p>
<h2>Handling External Data</h2>
<p>In an attempt to make the code more succinct and easier to read, I made a concerted effort to remove XHR request code from inside of view models so that the view model is much more a representation of state. This means that modules were introduced to handle loading external data into the model.</p>
<p>Based on ideas of one-way data flow from ReactJS, I employed a one way data flow for the retrieval of data from the API. The idea is the view model will make a request for data, a data manager class will execute a request to get the data from an API and resolve a promise that executes a callback function when the data is retrieved. The callback on the view model updates the observable contained in the state. This gets rid of many nested anonymous functions that plagued the code previously.</p>
<p>My data manager is only concerned with creating and resolving XHR requests. The project is using the Swagger Client JavaScript library which reads the Swagger specification and generates strongly typed method names. An intermediary module is used to load and manage the swagger client. The simplified code is here, but this module is used by all of the page data manager modules.</p>
<pre class="brush: jscript; title: Generic Swagger Loader; notranslate">
define('SwaggerClientManager', ['swagger'], function (swagger) {
	function SwaggerClientManager(clientConfigUrl, readyCallback) {
		var self = this;

		self.clientConfigUrl = clientConfigUrl;
		self.readyCallback = readyCallback;

		self.Client = null;

		self.__init();
	}

	SwaggerClientManager.prototype.__init = function() {
		var self = this;

		new swagger({url: self.clientConfigUrl, usePromise: true})
			.then(function (client) {
				self.Client = client;
				self.readyCallback();
			}, function (reason) {
				throw new Error('Unable to load client specification');
			});
	};

	return SwaggerClientManager;
});
</pre>
<p>Swagger-client.js creates a JavaScript object that exposes all of the methods of the client as strongly typed properties. This makes building the page data manager class much easier. The data manager just needs to have some callback functions passed in as arguments to the constructor so they can be called as API requests are completed. Then the data manager prepares and executes the request to the swagger-client.js module.</p>
<pre class="brush: jscript; title: Example Data Manager; notranslate">
define('DataManager', ['SwaggerClientManager'], function ($httpClient) {
	function GenericDataManager(clientConfigUrl, readyCallback, dataLoadedCallback, dataLoadErrorCallback) {
		var self = this;

		self.isReady = false;

		self.onReadyCallback = readyCallback;
		self.dataLoadedCallback = dataLoadedCallback;
		self.dataLoadErrorCallback = dataLoadErrorCallback;

		self.onReady = self.onReady.bind(self);

		self.$http = new $httpClient(clientConfigUrl, self.onReady);
	}

	GenericDataManager.prototype.onReady = function() {
		this.isReady = true;
		this.onReadyCallback();
	};

	GenericDataManager.prototype.LoadData = function () {
		var self = this;
		self.$http.Client.apis.Widgets.CustomerWidgetsByCustomerIdGet({customerId: 'ABC'})
			.then(function (response) {
				self.dataLoadedCallback(response);
			})
			.catch(function (err) {
				self.dataLoadErrorCallback(err);
			});
	};

	return GenericDataManager;
});
</pre>
<p>Now everything can be put together. The highlighted code shows how the data manager is now incorporated into the view model. </p>
<pre class="brush: jscript; highlight: [24,25,26,28,29,30,31,32,35,36,37,38,40,41,42,44,45,46]; title: ; notranslate">
define('DataDrivenViewModel', ['knockout', 'DataManager'], function (ko, $dataManager){
	function DataDrivenViewModel(options) {
		 var self = this;
		 
		 self.state = {
			 externalData: ko.observableArray([]),
			 errors: ko.observableArray([])
		 };
		 
		 self.calc = {
			 hasData: ko.pureComputed(function(){
				 var count = ko.unwrap(this.state.externalData).length;
				 return count &gt; 0;
			 }, self),
			 hasError: ko.pureComputed(function() {
				 var errors = ko.unwrap(this.state.errors);
				 return errors.length &gt; 0;
			 }, self)
		 };
		 
		 self.listeners = {};
		 
		 // ensure context of this is the instance of DataDrivenViewModel
		 self.onClientReady = self.onClientReady.bind(self);
		 self.onDataLoaded = self.onDataLoaded.bind(self);
		 self.onDataLoadError = self.onDataLoadError.bind(self);
		 
		 self.dataManager = new $dataManager(
			options.clientConfigUrl, 
			self.onClientReady,
			self.onDataLoaded,
			self.onDataLoadError);
	}
	
	DataDrivenViewModel.prototype.onClientReady = function() {
		// the context of this will be the instance of DataDrivenViewModel
		this.dataManager.LoadData();
	};
	
	DataDrivenViewModel.prototype.onDataLoaded = function(data) {
		this.state.externalData(data);
	};
	
	DataDrivenViewModel.prototype.onDataLoadError = function(error) {
		this.state.errors.push(error);
	};
	
	DataDrivenViewModel.prototype.dispose = function (){
		// example to iterate over any assigned listeners
		// and then dispose of them -- code has not been tested
		Object.keys(this.listeners).forEach(function (key) {
			this.listeners[key].dispose();
		});
	};
	
	return DataDrivenViewModel;
});
</pre>
<p>The view model <strong>state</strong> has two properties, <em>externalData</em> and <em>errors</em>. Both state properties are Knockout observables, so Knockout will maintain the state of the UI/UX when bound to the HTML on the page. </p>
<p>Using the prototype interface, functions are registered to handle the necessary callbacks for the data operations. <strong>onClientReady</strong> is called when the data manager successfully initializes the swagger client instance. When the method is called, the view model then makes a request to the data manager to load the necessary data. Once the data manager has loaded the data successfully, the <strong>onDataLoaded</strong> function is called and passes an argument containing the data retrieved. This argument is used to override the data currently in the externalData property. If there are any errors retrieving data, the <strong>onDataLoadError</strong> function is called and a new error is added to the errors observable. </p>
<p>If the data needed to be saved, the first task is to create a method on the GenericDataManager to handle calling the API to store the state. At a minimum, a completed callback should be added and then the DataDriveViewModel would need to have a method to handle the callback. Finally, the view model will need a method to invoke saving the data.</p>
<h1>2020 Roadmap</h1>
<p>For this project, I have already described how it was moved from RequireJS to Webpack. This has allowed the team to begin getting familiar with how webpack works and how to use it to bundle the final client-side scripts. However, it would be ideal if we could also migrate the script more into ES6 and totally remove all dependencies on RequireJS. So this year, I hope to start transitioning the code toward this direction.</p>
<p>One thing that has been sorely lacking in the project over the past several years is a unit testing the various modules and components in the project. While the team has been fairly good at catching errors before they reach production, inevitably some bugs have been missed. By incorporating unit testing, the goal will be to improve overall code quality and make sure that newly introduced components and modules do not break existing functionality. So, this year I hope to be able to begin testing portions of the client script, and start incorporating those tests within the build pipeline as well. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2019/11/01/an-opinionated-approach-to-knockoutjs-components/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1187</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>
	</item>
		<item>
		<title>RequireJS to WebPack</title>
		<link>https://devspoint.wordpress.com/2019/10/25/requirejs-to-webpack/</link>
					<comments>https://devspoint.wordpress.com/2019/10/25/requirejs-to-webpack/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Fri, 25 Oct 2019 12:46:41 +0000</pubDate>
				<category><![CDATA[JavaScript Clients]]></category>
		<category><![CDATA[KnockoutJS]]></category>
		<category><![CDATA[Webpack]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[RequireJS]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=1225</guid>

					<description><![CDATA[There are several articles on the web that discuss converting a site developed using RequireJS to WebPack. However, in several of these articles, there are assumptions in place that didn&#8217;t fit my scenario and had no instructions on how to handle a few of the modules in use within a project that my company needed &#8230; <a href="https://devspoint.wordpress.com/2019/10/25/requirejs-to-webpack/" class="more-link">Continue reading <span class="screen-reader-text">RequireJS to WebPack</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>There are several articles on the web that discuss converting a site developed using RequireJS to WebPack. However, in several of these articles, there are assumptions in place that didn&#8217;t fit my scenario and had no instructions on how to handle a few of the modules in use within a project that my company needed to convert. These articles were a great place to start, but then found myself really getting to see some of the power available to you when using WebPack with an ASP.Net 4.6.2 MVC application that utilizes ASP.Net core 2.2 as well.</p>
<p><span id="more-1225"></span></p>
<h2>Project Background</h2>
<p>The project started around 3 years ago with the technology stack that was available at the time, which included:</p>
<ul>
<li>ASP.Net MVC with ASP.Net Core</li>
<li><a href="https://knockoutjs.com/" target="_blank" rel="noopener">KnockoutJS</a></li>
<li><a href="https://requirejs.org/" target="_blank" rel="noopener">RequireJS</a></li>
</ul>
<p>The web site hosts multiple single page applications built using Knockout as the client framework and RequireJS for dependency injection. Early on, this framework worked well and made it relatively easy for developers to know where to go to update the client files and how those files were loaded into each page of the application.</p>
<p>However, as the application continued to grow, RequireJS began to make more XHR requests for dependencies needed by the application. This began to impact the overall performance of the application and page load times started to steadily increase. As an example, one page in the application could take up to a minute to fully load all dependencies and data. However, most of the load time for the page was simply getting all of the required dependencies.</p>
<p>Another of the challenges faced with this framework has always been client side builds. Initially, <a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier" target="_blank" rel="noopener">Visual Studio&#8217;s Bundler &amp; Minifier</a> extension was used to bundle all JavaScript and CSS files. However, maintaining the bundleconfig.json file became a difficult task and dreaded by all developers who worked on the project. This doesn&#8217;t even include the extra load on Visual Studio to parse and build those files.</p>
<p>Both the performance of the site, and the difficulty of building the client made it clear a new solution was needed. Additionally, users of the application began reporting performance issues at a much more regular cadence. Added to this challenge was changes to Bower, which was used early on for 3rd party dependencies. The requirements of the new build system were simple:</p>
<ol>
<li>Reduce the number of XHR calls for scripts and dependencies for the client pages to improve performance of the application.</li>
<li>Remove the dependence on bower in favor of a more widely used package management tool.</li>
<li>Improve the developer experience and reduce the required maintenance of build scripts.</li>
</ol>
<h2>Reducing XHR Calls</h2>
<p>One of the largest problems we had with this project is the number of calls required for a single page to be presented. The project used KnockoutJS components which require a backing view-model and a template. Each component defined in the solution had a JavaScript file and an HTML snippet file in the same folder. This was done to help with maintenance of the individual components, giving developers a way to target changes to specific components functionality, or presentation. During the build done by Azure DevOps, the HTML files were simply copied into the scripts folder of the application and retrieved by RequireJS by relative paths at runtime.</p>
<p>Initially, each page only loaded a few HTML templates, so the impact of these loading times was fairly low. However, as the application continued to grow, some pages required up to twenty different HTML snippets. These snipped were necessary just to render HTML on the page as part of the Knockout component. This didn&#8217;t include other dependencies like third party scripts or true requests for data. Loading the HTML snippets was causing blocking behavior in the browser, which significantly delayed the loading of application data.</p>
<p>As a first initiative, the goal was to create one bundle per application page which should include all of the dependencies for the page and all of the HTML snippets as modules that returned string values. I began by using <a href="https://nodejs.org/" target="_blank" rel="noopener">Node.js</a> and used the <a href="https://requirejs.org/docs/optimization.html" target="_blank" rel="noopener">r.js</a> script delivered as part of the require.js optimizer options to create the required bundles. This worked great, and there was an immediate improvement in performance. The page that took upwards of a full minute to load was now returning in less than 5 seconds, including the calls necessary to retrieve data for the page.</p>
<p>However, a new problem quickly emerged. The scripts required to create the client script bundles took several minutes to run. This made it difficult for developers to make a single change to a file. Each change required the developer to wait for all of the client files to be built before they could resume any debugging tasks. This slowed down the progress of development significantly. But, the performance gains were a step in the right direction. The client was literally &#8220;wowed&#8221; by the performance of these pages, even on slower networks.</p>
<h2>Bower to NPM</h2>
<p>At the time the project was started, <a href="https://bower.io/" target="_blank" rel="noopener">Bower</a> was a fairly widely used package management platform. It is still maintained, but other package management software has taken precedence and are now much more widely supported than Bower. Bower was replaced with NPM and all third party libraries were able to be located and associated with the project.</p>
<p>Our organization has started using <a href="https://webpack.js.org" target="_blank" rel="noopener">webpack</a> to handle the bundling of client side frameworks in other projects, so my knowledge of webpack has improved significantly over the last two years. This made webpack an ideal candidate.</p>
<p>Additionally, webpack is able to parse and understand the define and require statements used by require.js throughout the project. So very little had to be touched to get things running.</p>
<p>However, after creating a quick webpack.config.js file and running it against the project it was evident additional work would be required as the initial run of webpack resulted in multiple missing modules. This is because all of the dependencies in the project are referenced based on the module name, not the module path.</p>
<p>This ended up being a fairly simply fix, as <a href="https://webpack.js.org/configuration/" target="_blank">webpack&#8217;s config</a> allows the specification of <a href="https://webpack.js.org/configuration/resolve/#resolvealias" target="_blank">aliases</a> for the resolution of modules. One of the first steps taken was to define a modules file that developers could utilize to declare components, modules, and view-models for the application so they could be references by the module name. <img loading="lazy" data-attachment-id="1237" data-permalink="https://devspoint.wordpress.com/2019/10/25/requirejs-to-webpack/client_folder_structure/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png" data-orig-size="236,108" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="client_folder_structure" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png?w=236" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png?w=236" class=" size-full wp-image-1237 alignright" src="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png" alt="client_folder_structure" width="236" height="108" srcset="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png 236w, https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png?w=150&amp;h=69 150w" sizes="(max-width: 236px) 100vw, 236px" />The folder structure included separate files to define various modules which are then combined by an index.js file using ES2015 JavaScript syntax.</p>
<pre class="brush: jscript; title: Module Definitions; notranslate">
const path = require('path');

const workingDirectory = path.resolve(__dirname, '../../app_scripts/modules');

/* This is a list of all modules used to handle and manage data */
const dataModules = {
    "CoreDataManagerModule": path.resolve(workingDirectory, "data/coredatamanager.module.js"),
    "CustomersDataManager": path.resolve(workingDirectory, "data/customersdatamanager.module.js"),
};

const jsModules = {
    "ConsoleLogger": path.resolve(workingDirectory, "consolelogger.js"),
    "DateFormatConstants": path.resolve(workingDirectory, "dateformatconstants.js"),
    "FileUploadHandlerModule": path.resolve(workingDirectory, "fileuploadhandler.module.js"),
    "GlobalComponentRegistration": path.resolve(workingDirectory, "GlobalComponentRegistration.js"),
    "LocalStorageManager": path.resolve(workingDirectory, "localstoragemanager.js"),
    "RandomStringModule": path.resolve(workingDirectory, "randomstringgeneratormodule.module.js"),
    "SwaggerClientManagerModule": path.resolve(workingDirectory, "swaggerclientmanager.module.js"),
    "SwaggerSpecLookup": path.resolve(workingDirectory, "swaggerspeclookup.module.js"),
    "visualizationColors": path.resolve(workingDirectory, "visualizationcolorsmodule.js")
};

/* This combines all of the modules into a single export */
module.exports = {
    ...dataModules,
    ...jsModules
};
</pre>
<p>Once all of the modules were defined in this way, they could be used within the webpack.config.js file.</p>
<pre class="brush: jscript; highlight: [6,23]; title: Webpack Configuration; notranslate">
const path = require("path");
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// pull definitions of all modules from definition folder
const assets = require("./build/client_asset_definitions");

// ... other code ... //
module.exports = env =&gt; {
    // ... other code ... //

    return {
        // ... other code ... //
        resolve: {
            alias: {
                'domReady': 'requirejs-domready',
                'text': 'requirejs-text',
                'config': path.resolve(workingDirectory, 'config.js'),
                'charts': 'chart.js',
                'swagger': 'swagger-client',
                'customBindings': path.resolve(workingDirectory, 'customBindings.js'),
                'slick': 'slick-carousel',
                ...assets
            }
        },
    // ... other code ... //
    };
};
</pre>
<p>This corrected the mapping of many of the modules within the application. The final challenge in getting the configuration to work was getting all of the HTML template files incorporated into the build. The default <em>text!</em> dependency would not work correctly without making use of a loader. The loader selected was the <a href="https://webpack.js.org/loaders/html-loader/" target="_blank">html-loader</a>. An alias was created so that the <em>html-loader</em> would be used to emit the correct template for consumption by webpack.</p>
<pre class="brush: jscript; highlight: [14,24]; title: Webpack Configuration; notranslate">
const path = require("path");
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// ... omitted code ... //
// Export the configuration
module.exports = env =&gt; {
    // ... omitted code ... //

    return {
        resolve: {
            alias: {
                'domReady': 'requirejs-domready',
                'text': 'requirejs-text',
                'config': path.resolve(workingDirectory, 'config.js'),
                'charts': 'chart.js',
                'swagger': 'swagger-client',
                'customBindings': path.resolve(workingDirectory, 'customBindings.js'),
                'slick': 'slick-carousel',
                ...assets
            }
        },
        resolveLoader: {
            alias: { 'text': 'html-loader' }
        },
    // ... omitted code ... //
    };
};
</pre>
<p>Line 14 declares that the text dependency should use the node module <em>requirejs-text</em> and the loader to utilize is the <em>html-loader</em>, as seen on line 24. Now the HTML templates are included within the webpack bundles and continue to reduce the number of required XHR requests.</p>
<p>Finally, the application has multiple entry points that need to be defined. In an effort to reduce the overhead of maintaining multiple files, scripting was utilized to search for the files and dynamically create the entry points for the application.</p>
<p>In the application, all of the entry points have the extension <em>.bootstrapper.js</em> and are stored in one of two folders in the client source scripts. The script simply needed to crawl the two folders and find all of the associated scripts to create the app entry points. Since they are contained within two folders, this was very easy to do just using NodeJS. Additionally, developers can easily add a new bootstrapper script into these folders and know they will be picked up the next time a webpack build is executed.</p>
<pre class="brush: jscript; title: Entry Points; notranslate">
const fs = require('fs');
const path = require('path');

const userBootstrappersDir = path.resolve(__dirname, '../../app_scripts/bootstrappers');
const adminBootstrappersDir = path.resolve(__dirname, '../../app_scripts/bootstrappers/admin');

// Read the list of files from the directoris
let userFiles = fs.readdirSync(userBootstrappersDir, { withFileTypes: true });
let adminFiles = fs.readdirSync(adminBootstrappersDir, { withFileTypes: true });

// Declare an object to hold all entry points
var entryPoints = {};

userFiles.forEach((f) =&gt; {
    if (f.isFile()) {
        var nameReplace = f.name.replace(".bootstrapper.js", ".page");
        entryPoints[nameReplace] = path.resolve(userBootstrappersDir, f.name);
    }
});

adminFiles.forEach((f) =&gt; {
    if (f.isFile()) {
        var nameReplace = f.name.replace(".bootstrapper.js", ".page").replace('admin_', 'admin.');
        entryPoints[nameReplace] = path.resolve(adminBootstrappersDir, f.name);
    }
});

// Export all entry points defined
module.exports = entryPoints;
</pre>
<p>With all of the entry points defined, it was simply a matter of referencing them in the webpack.config.js file. Additionally, we have one <a href="https://webpack.js.org/configuration/externals/" target="_blank">external reference</a> that is not able to be retrieved using npm (the project presents maps with a third party mapping tool that does not deliver an NPM package). It is declared as an external dependency so that webpack doesn&#8217;t attempt to pull it into the bundles. The dependency is instead declared with a traditional script tag on the pages that need it. </p>
<pre class="brush: jscript; highlight: [9,22,45]; title: Webpack Configuration; notranslate">
const path = require("path");
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// pull definitions of all modules from definition folder
const assets = require("./build/client_asset_definitions");

// pull all bootstrappers from bootstrappers folder
const entryPoints = require("./build/bootstrapper-resolver");

// Set the working directory and the output directory
const workingDirectory = path.resolve(__dirname, 'app_scripts');
const outDirectory = path.resolve(__dirname, 'wwwroot/js/dist');

// Export the configuration
module.exports = env =&gt; {
    const plugins = [];
    // ... omitted code ... //

    return {
        mode: !env ? 'development' : env.production ? 'production' : 'development',
        entry: entryPoints,
        output: {
            path: outDirectory,
            publicPath: '/js/dist/',
            filename: '[name].js'
        },
        resolve: {
            alias: {
                'domReady': 'requirejs-domready',
                'text': 'requirejs-text',
                'config': path.resolve(workingDirectory, 'config.js'),
                'charts': 'chart.js',
                'swagger': 'swagger-client',
                'customBindings': path.resolve(workingDirectory, 'customBindings.js'),
                'slick': 'slick-carousel',
                ...assets
            }
        },
        resolveLoader: {
            alias: { 'text': 'html-loader' }
        },
        plugins: plugins,
        externals: {
            'maps': 'Microsoft'
        }
    };
};
</pre>
<h2>Better Developer Experience</h2>
<p>ASP.Net Core includes middleware to handle running a node service for the specific purpose of handling webpack recompilation. This package was included in the project and referenced within the startup for the application.</p>
<pre class="brush: plain; highlight: [5]; title: ; notranslate">
if (env.IsEnvironment("Local")) {
      app.UseDeveloperExceptionPage();

      app.UseBrowserLink();
      app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions());
}
</pre>
<p>Now, as developers make changes to the files referenced by webpack, updates happen immediately and are can be quickly accessed by reloading each page. Eventually, the desire is to get hot module replacement working as well so that all JavaScript code changes occur and are available within the client without any need to reload.</p>
<p>As you can see, this wasn&#8217;t as difficult a process as it seemed when it first came up. The projection was for the work to take well over a week to complete and to get all pages working. However, in just under 3 days all of the code had been converted and testing began. Of course, there were bugs, but most of those came from improperly referenced dependencies that had been in use for years on the project.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2019/10/25/requirejs-to-webpack/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1225</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2019/10/client_folder_structure.png" medium="image">
			<media:title type="html">client_folder_structure</media:title>
		</media:content>
	</item>
		<item>
		<title>First Adventure into Web Components</title>
		<link>https://devspoint.wordpress.com/2017/10/12/first-adventure-into-web-components/</link>
					<comments>https://devspoint.wordpress.com/2017/10/12/first-adventure-into-web-components/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Thu, 12 Oct 2017 12:45:05 +0000</pubDate>
				<category><![CDATA[Development Approaches]]></category>
		<category><![CDATA[Scripting]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=965</guid>

					<description><![CDATA[Web components have been a topic of conversation for some time, but I had never had a reason to really take time to explore building an application using them. There are many different reasons, with the biggest one being that many of the projects I have been working on utilize some of the big component &#8230; <a href="https://devspoint.wordpress.com/2017/10/12/first-adventure-into-web-components/" class="more-link">Continue reading <span class="screen-reader-text">First Adventure into Web&#160;Components</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Web components have been a topic of conversation for some time, but I had never had a reason to really take time to explore building an application using them. There are many different reasons, with the biggest one being that many of the projects I have been working on utilize some of the big component libraries like Kendo or DevExpress. <span id="more-965"></span></p>
<h2>Opportunity Knocks</h2>
<p>A new project is a great opportunity to begin exploring new ideas, and I had the opportunity presented to me by a colleague who had a client that wanted to build a new application, but they wanted to avoid having any downstream customers that might opt to customize the code. This meant the customer wanted to avoid the use of the big component libraries.</p>
<p>Our company has used KnockoutJS in several projects over the years, so it was in consideration as part of the technology stack. After watching a <a href="http://blog.stevensanderson.com/2014/06/11/architecting-large-single-page-applications-with-knockout-js/" target="_blank" rel="noopener">video</a> by Steve Sanderson, I saw this as an opportunity to put my knowledge of KnockoutJS to work in building this new application using an approach to Web Components.</p>
<p>I do want to take a moment to mention some of the other libraries used with this project include ChartJS, Bing Maps V8, RequireJS, Material Design Lite, Swagger JavaScript Client, and jQuery.</p>
<h2>Architecting a Pattern</h2>
<p>One of the first challenges I faced was how granular I should get when breaking apart the wireframes for building the application. The first page I needed to build was an overview dashboard which included a map, several counters, a couple of graphs, and a grid of contextual data related to system activities.</p>
<p>The first pass at decomposing the wireframe ended up with multiple small components that would be put together into a single large component. The components for the first page included a map widget, a counter widget, a double counter widget, a small graph widget, a large graph widget, and a data grid widget. The large component became the dashboard component which utilized all of the smaller components.</p>
<p>Now that each of the elements were decomposed, it became necessary to determine how to build a folder hierarchy that helped future developers understand what each file did. I also wanted to make sure my build system could easily aggregate changes across the application, as each page&#8217;s javascript in the application needed to be compiled down to a single file.</p>
<p>The resulting structure is outlined below.</p>
<ul>
<li>App_Scripts
<ul>
<li> Components
<ul>
<li>WidgetOne
<ul>
<li>WidgetOne.component.js</li>
<li>WidgetOne.registration.js</li>
<li>WidgetOne.template.html</li>
</ul>
</li>
<li>WidgetTwo
<ul>
<li>WidgetTwo.component.js</li>
<li>WidgetTwo.registration.js</li>
<li>WidgetTow.template.html</li>
</ul>
</li>
</ul>
</li>
<li>Modules
<ul>
<li>ModuleOne.module.js</li>
<li>ModuleTwo.module.js</li>
</ul>
</li>
<li>AdminViewModels
<ul>
<li>AdminPageOne.model.js</li>
<li>AdminPageTwo.model.js</li>
</ul>
</li>
<li>ViewModels
<ul>
<li>ViewModelOne.model.js</li>
<li>ViewModelTwo.model.js</li>
</ul>
</li>
<li>config.js</li>
<li>PageOne.page.js</li>
<li>PageTwo.page.js</li>
</ul>
</li>
</ul>
<p>All of the application scripts are stored in the app_scripts folder of the project. At the root level of this folder are all of the initialization scripts for the pages. The components folder holds all of the components for the application. There are two areas for view models, one holds all of the view models used for administrative pages while the other holds all the view models for non-administrative pages. The modules folder holds any modules used within the application. This folder has modules that handle HTTP requests, authentication, etc.</p>
<h2>Configure Bundling</h2>
<p>The project is using Visual Studio 2017 with the new bundleconfig.json file being used to define all of the bundles. </p>
<h3>Bundling Scripts for Each Page</h3>
<p>One of the early challenges I noticed in building a bundle per page was the extreme number of duplicate entries in the application. This helped me identify how to better structure the bundling so adding a new component to the application wouldn&#8217;t always require a visit to every bundle configuration definition in the file.</p>
<p>The first bundle that would be included on all page bundles is the core bundle.</p>
<pre class="brush: jscript; title: ; notranslate">
  // CORE JS BUNDLES
  {
    &quot;outputFileName&quot;: &quot;js_build/core.js&quot;,
    &quot;inputFiles&quot;: [
      &quot;app_scripts/modules/sessionstoragemanager.js&quot;,
      &quot;app_scripts/modules/snackbarmanager.module.js&quot;,
      // ... MORE CORE ITEMS ... //
      &quot;app_scripts/components/alertpanel/alertpanel.component.js&quot;,
      &quot;app_scripts/components/alertpanel/alertpanel.registration.js&quot;
    ],
    &quot;minify&quot;: { &quot;enabled&quot;: false }
  },
</pre>
<p>Now I can simply include the core bundle into my page bundle.</p>
<pre class="brush: jscript; title: ; notranslate">
  {
    &quot;outputFileName&quot;: &quot;wwwroot/js/app/admin_UserList.page.js&quot;,
    &quot;inputFiles&quot;: [
      &quot;app_scripts/config.js&quot;,
      &quot;app_scripts/customBindings.js&quot;,
      &quot;js_build/core.js&quot;, // HERE IS THE CORE BUNDLE //
      &quot;app_scripts/AdminModels/users_List.model.js&quot;,
      &quot;app_scripts/admin_UserList.page.js&quot;
    ]
  },
</pre>
<p>Inside my project, when the bundles are built the files will include all of the scripts I&#8217;ve defined for that page. The only thing that remained in setting up bundling was getting the html templates into the application.</p>
<h3>HTML Templates</h3>
<p>In order to get the templates for the components, I am using the RequireJS Text plug-in. All of the templates are stored in the root of the output folder. This is also handled pretty easily by updating the bundleconfig.json file.</p>
<pre class="brush: jscript; title: ; notranslate">
  {
    &quot;outputFileName&quot;: &quot;wwwroot/js/app/alertpanel.template.html&quot;,
    &quot;inputFiles&quot;: [ &quot;app_scripts/components/alertpanel/alertpanel.template.html&quot;],
    &quot;minify&quot;: {
      &quot;enabled&quot;: true,
      &quot;isFragmentOnly&quot;: true,
      &quot;removeHtmlComments&quot;: true
    }
  }
</pre>
<p>Now my templates and scripts are all within the js/app folder on my server. </p>
<h2>A Simple Component</h2>
<p>Back to the components, I started with a very simple component that would display a tile that includes a counter, a label, and a subtitle. The first step I took was defining the template in the smallcounter.template.html file.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;div class=&quot;client-small-widget mdl-shadow--2dp&quot;&gt;
    &lt;div class=&quot;client-small-widget--counter&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;client-small-widget--subtitle-text&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;client-small-widget--title-text&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</pre>
<p> <br />
The small counter card is very simple and includes a div to contain the value, a div to contain the title, and a div to contain the text.</p>
<p>Knockout Components are defined as an object definition. Since I&#8217;m using RequireJS in the project, I declare the component as a RequireJS module definition in the file smallcounter.component.js. </p>
<pre class="brush: jscript; title: ; notranslate">
define('smallCounter', ['knockout', 'text!./js/app/smallCounter.template.html'], function (ko, htmlString) {
    function SmallCounter(params, componentInfo) {
        this.title = params.title;
        this.value = params.value || ko.observable(0);
        this.subtitle = params.subtitle;
    }

    return { viewModel: SmallCounter, template: htmlString };
});
</pre>
<p>Knockout will pass a params object to the component when the component is bound to the model. This object contains all of the parameters that were passed to the component through the markup where the component is used.</p>
<p>Now I revisit the template and add the Knockout bindings to the markup.</p>
<pre class="brush: xml; highlight: [2,3,4]; title: ; notranslate">
&lt;div class=&quot;att-small-widget mdl-shadow--2dp&quot;&gt;
    &lt;div class=&quot;att-small-widget--counter&quot; data-bind=&quot;text: value&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;att-small-widget--subtitle-text&quot; data-bind=&quot;text: subtitle&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;att-small-widget--title-text&quot; data-bind=&quot;text: title&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>The counter div has the text bound to the value property of the component, the subtitle is bound to the subtitle property, and the title is bound to the title property. </p>
<p>Finally, to make the component easy to use, it needs to be registered as a component with Knockout. This is where the registration JavaScript file comes into focus. I created a final file named smallcounter.registration.js and placed the following code within the file.</p>
<pre class="brush: jscript; title: ; notranslate">
require(['smallCounter', 'knockout'], function (counter, ko) {
    ko.components.register('client-small-counter', smallCounter);
});
</pre>
<h2>Using the Component</h2>
<p>Now that the component is completed, I wanted to test it out and make sure that it behaves as I expected it to behave. The easiest way is to use it on a page.</p>
<p>I created a simple test page that used the component binding defined in the regitration file. </p>
<pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
   &lt;head&gt;
      &lt;script src=&quot;require.js&quot; data-main=&quot;/js/app/samplepage.page&quot;/&gt;
   &lt;/head&gt;
   &lt;body&gt;
      &lt;client-small-counter params=&quot;value:value, title:'Test', subtitle:'Value Test'&quot;&gt;
      &lt;/client-small-counter&gt;
   &lt;/body&gt;
&lt;/html&gt;
</pre>
<p>along with the page bundle script. The script for the page instantiates a simple view model that is used to pass values to the component.</p>
<pre class="brush: jscript; title: ; notranslate">
require(['knockout', function(ko){
     var simpleModel = {
          value: ko.observable(121)
     };

     ko.applyBindings(simpleModel);
});
</pre>
<p>When the page is run, a div is placed on the page and all of the values are bound as expected. </p>
<p>If you want to see a simple sample of the idea that was the starting place for this component, you can view the CodePen <a href="https://codepen.io/cquick/full/OxEdbY/" target="_blank">here</a>.</p>
<h2>Getting More Complex</h2>
<p>Now that some of my simple components were up and running, it was time to get a little more complex. I needed a component that incorporates Bing Maps to render a map for use within the application. This was not as trivial as the simple counter demonstrated above.</p>
<p>One of the challenges with the Bing Maps API is the fact that the component is loaded dynamically by its own module loader. However, I need to know when the map modules are loaded so I can handle different operations of the component. </p>
<p>Thankfully, KnockOut has a pattern that allows you to get some more information about components when they are created. It is also at this time you can inject your own methods to the component. </p>
<p>Within the module definition for the map, I used the following definition.</p>
<pre class="brush: jscript; title: ; notranslate">
define('BingMapComponent', 
['knockout','text!./js/app/map.template.html', 'jquery', 'maps'],
function (ko, htmlTemplate, $) {
   function MapComponent(params, componentInfo) {
   }

   function MapComponentFactory(params, componentInfo) {
   }

   return {viewModel: { createViewModel: MapComponentFactory }, template: htmlTemplate };
}
</pre>
<p>This component makes use of a function to create the component. I&#8217;m not going to go into all the details of the Bing Maps API, but here is the code that is used to create the map within the component.</p>
<pre class="brush: jscript; title: ; notranslate">
define('BingMapComponent', 
['knockout','text!./js/app/map.template.html', 'jquery', 'maps'],
function (ko, htmlTemplate, $) {
   function MapComponent(params, componentInfo) {
       /// ... component details would be here ... ///
   }

   function MapComponentFactory(params, componentInfo) {
      var element = $(componentInfo.element);
      var mapContainer = $(&quot;&lt;div/&gt;&quot;);
      mapContainer.appendTo(element);

      var component = new AssetLocationMapComponent(params, componentInfo);

      function checkReadyState() {
         if (Microsoft.Maps &amp;&amp; Microsoft.Maps.Map &amp;&amp; Microsoft.Maps.Location &amp;&amp; Microsoft.Maps.Pushpin &amp;&amp; Microsoft.Maps.Point) {
            injectMap();
         } else {
            setTimeout(checkReadyState.bind(this), 150);
         }
      }

      function injectMap() {
         var options = {
           credentials: params.key
         };

         var map = new Microsoft.Maps.Map(mapContainer[0], options);
         var clusterLayer;

         Microsoft.Maps.loadModule('Microsoft.Maps.Clustering', function () {
            clusterLayer = new Microsoft.Maps.ClusterLayer([], {
            clusteredPinCallback: component.__generateClusterOptions,
            gridSize: 100
         });
         map.layers.insert(clusterLayer);
         component.placePins();
      });
      component.setMap(map);
      component.setDefaultView();
      if (params.trackViewChanges) {
             component.registerTrackViewChanges();
      }
   }

   checkReadyState();

            return component;   
   }

   return {viewModel: { createViewModel: MapComponentFactory }, template: htmlTemplate };
}
</pre>
<p>There is a lot going on in the code above. First the element that contains the component is discovered with jQuery. A new div is added to the element and the component is created. Because I don&#8217;t know when the modules will be available, I set up a function to check every 150ms to make sure all the modules I need for the map area available. Once they are available, the map is injected into the component. You will see some calls to various methods on the component. These methods do things like set up any pushpins that are in the data collection for the map, register the map to store it&#8217;s last state for the user to a storage location, set up the default view based on the last stored information for that user. This is all possible because the map API is now available to the component.</p>
<h2>The Value of Components</h2>
<p>The big value of using components in this application have come in the form of enhancements to the system. Early on, I defined a component to contain all the necessary markup to create a text box using Material Design Lite. This was wrapped up in a component so anywhere I needed a text box on a form, I would just include it in the page instead of having to copy and paste all of the markup for the MDL text box. </p>
<p>While there isn&#8217;t a lot of markup to create a text box for MDL, there are some situations where custom validation was needed and this was not handled as easily by the standard out of the box MDL text box. All of this custom functionality was wrapped up into a component and used throughout the application. </p>
<p>A new requirement was registered by the client where they wanted a label to appear below the text box with some help text. If I had not wrapped up my textboxes into a component, I would need to visit every page that would have help text and add additional HTML markup to support it. However, I was using the component so I simply added it to the component, exposed a new parameter, and updated the text boxes that needed the help text.</p>
<p>Prior to the update, my component was defined this way.</p>
<p>HTML</p>
<pre class="brush: xml; title: ; notranslate">
&lt;div class=&quot;client-form--field client-form--textField&quot;&gt;
    &lt;div class=&quot;client-form--field-control&quot;&gt;
        &lt;div class=&quot;mdl-textfield mdl-js-textfield mdl-textfield--full-width mdl-textfield--floating-label&quot; data-bind=&quot;css:{isInvalid: hasErrors() === true }&quot;&gt;
            &lt;input class=&quot;mdl-textfield__input&quot; type=&quot;text&quot; data-bind=&quot;textInput: value, attr: {id: componentId}, hasFocus: hasFocus, event: {blur: handleBlurValidation}&quot; /&gt;
            &lt;label class=&quot;mdl-textfield__label&quot; data-bind=&quot;html: displayLabel, attr:{for:componentId}&quot;&gt;&lt;/label&gt;
            &lt;!-- ko if:hasErrors()--&gt;
            &lt;!-- ko foreach: errors --&gt;
            &lt;span class=&quot;mdl-textfield__error&quot; data-bind=&quot;text:$data&quot; style=&quot;visibility: visible;&quot;&gt;&lt;/span&gt;
            &lt;!-- /ko --&gt;
            &lt;!-- /ko --&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</pre>
<p>JavaScript</p>
<pre class="brush: jscript; title: ; notranslate">
define('TextBoxComponent', ['knockout', 'text!./js/app/textbox.template.html', 'jquery'], function (ko, htmlString, $) {
    function TextBoxComponent(params, element) {
        var self = this;
        var el = $(element);
        var mdlInputBox = el.find(&quot;.mdl-textfield&quot;);

        // capture parameters
        self.value = params.value || ko.observable();
        self.label = ko.observable(ko.unwrap(params.label)) || ko.observable();
        self.isRequired = params.isRequired || false;
        self.maxLength = params.maxLength || -1;
        self.minLength = params.minLength || -1;
        self.hasFocus = params.hasFocus !== undefined ? ko.observable(ko.unwrap(params.hasFocus)) : ko.observable(false);
        self.trackChanges = params.trackChanges || ko.observable(false);

        self.displayLabel = ko.pureComputed(function () {
            var isRequired = ko.unwrap(self.isRequired);
            if (isRequired) {
                return ko.unwrap(self.label) + &quot; &lt;b&gt;*&lt;/b&gt;&quot;;
            }
            return ko.unwrap(self.label);
        }, self);

        self.originalValue = ko.observable(ko.unwrap(self.value));
        self.hasEdits = ko.pureComputed(function () {
            return ko.unwrap(self.value) !== ko.unwrap(self.originalValue);
        }, this);

        self.hasErrors = ko.observable(false);
        self.errors = ko.observableArray([]);
		self.hasErrors = ko.pureComputed(function(){
			returh this.errors().length &gt; 0;
		},self);

        self.trackChangesListener = self.trackChanges.subscribe(function (newValue) {
            if (newValue) {
                self.originalValue(ko.unwrap(self.value));
            }
        }, this);

        self.valueChangeListener = self.value.subscribe(function (newValue) {
            self.__validate();
            if (newValue &amp;&amp; newValue !== '') {
                mdlInputBox.addClass('is-dirty');
            } else {
                mdlInputBox.removeClass('is-dirty');
            }
        });

        self.componentId = ko.observable(self.__createUniqueId());

    }

    TextBoxComponent.prototype.handleBlurValidation = function (item, e) {
        item.__validate();
    };

    TextBoxComponent.prototype.__createUniqueId = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r &amp; 0x3 | 0x8);
            return v.toString(16);
        });
    };

    TextBoxComponent.prototype.__validate = function () {
        var item = this;
        if (!ko.unwrap(item.trackChanges)) {
            return;
        }
        item.errors.removeAll();
        item.hasErrors(false);
        if (item.isRequired &amp;&amp; ko.unwrap(item.value) === '') {
            item.errors.push(ko.unwrap(item.label) + &quot; is required.&quot;);
            item.hasErrors(true);
            return;
        }
    };

    function TextBoxComponentFactory(params, componentInfo) {
        var el = componentInfo.element;
        var component = new TextBoxComponent(params, el);
        return component;
    };

    return { viewModel: { createViewModel: TextBoxComponentFactory }, template: htmlString };
});
</pre>
<p>The change to the component was really easy at this point. </p>
<p>HTML</p>
<pre class="brush: xml; highlight: [11,12,13,14,15]; title: ; notranslate">
&lt;div class=&quot;client-form--field client-form--textField&quot;&gt;
    &lt;div class=&quot;client-form--field-control&quot;&gt;
        &lt;div class=&quot;mdl-textfield mdl-js-textfield mdl-textfield--full-width mdl-textfield--floating-label&quot; data-bind=&quot;css:{isInvalid: hasErrors() === true }&quot;&gt;
            &lt;input class=&quot;mdl-textfield__input&quot; type=&quot;text&quot; data-bind=&quot;textInput: value, attr: {id: componentId}, hasFocus: hasFocus, event: {blur: handleBlurValidation}&quot; /&gt;
            &lt;label class=&quot;mdl-textfield__label&quot; data-bind=&quot;html: displayLabel, attr:{for:componentId}&quot;&gt;&lt;/label&gt;
            &lt;!-- ko if:hasErrors()--&gt;
            &lt;!-- ko foreach: errors --&gt;
            &lt;span class=&quot;mdl-textfield__error&quot; data-bind=&quot;text:$data&quot; style=&quot;visibility: visible;&quot;&gt;&lt;/span&gt;
            &lt;!-- /ko --&gt;
            &lt;!-- /ko --&gt;
            &lt;!-- ko ifnot: hasErrors --&gt;
            &lt;!-- ko if: hasHelpText --&gt;
            &lt;span class=&quot;mdl-textfield__hint&quot; data-bind=&quot;text: helpText&quot;&gt;&lt;/span&gt;
            &lt;!-- /ko --&gt;
            &lt;!-- /ko --&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</pre>
<p>JavaScript</p>
<pre class="brush: jscript; highlight: [15,16,17,18]; title: ; notranslate">
define('TextBoxComponent', ['knockout', 'text!./js/app/textbox.template.html', 'jquery'], function (ko, htmlString, $) {
    function TextBoxComponent(params, element) {
        var self = this;
        var el = $(element);
        var mdlInputBox = el.find(&quot;.mdl-textfield&quot;);

        // capture parameters
        self.value = params.value || ko.observable();
        self.label = ko.observable(ko.unwrap(params.label)) || ko.observable();
        self.isRequired = params.isRequired || false;
        self.maxLength = params.maxLength || -1;
        self.minLength = params.minLength || -1;
        self.hasFocus = params.hasFocus !== undefined ? ko.observable(ko.unwrap(params.hasFocus)) : ko.observable(false);
        self.trackChanges = params.trackChanges || ko.observable(false);
        self.helpText = params.helpText || '';
        self.hasHelpText = ko.pureComputed(function () {
            return ko.unwrap(this.helpText);
        }, self);
		
        self.displayLabel = ko.pureComputed(function () {
            var isRequired = ko.unwrap(self.isRequired);
            if (isRequired) {
                return ko.unwrap(self.label) + &quot; &lt;b&gt;*&lt;/b&gt;&quot;;
            }
            return ko.unwrap(self.label);
        }, self);

        self.originalValue = ko.observable(ko.unwrap(self.value));
        self.hasEdits = ko.pureComputed(function () {
            return ko.unwrap(self.value) !== ko.unwrap(self.originalValue);
        }, this);

        self.hasErrors = ko.observable(false);
        self.errors = ko.observableArray([]);
		self.hasErrors = ko.pureComputed(function(){
			returh this.errors().length &gt; 0;
		},self);

        self.trackChangesListener = self.trackChanges.subscribe(function (newValue) {
            if (newValue) {
                self.originalValue(ko.unwrap(self.value));
            }
        }, this);

        self.valueChangeListener = self.value.subscribe(function (newValue) {
            self.__validate();
            if (newValue &amp;&amp; newValue !== '') {
                mdlInputBox.addClass('is-dirty');
            } else {
                mdlInputBox.removeClass('is-dirty');
            }
        });

        self.componentId = ko.observable(self.__createUniqueId());

    }

    TextBoxComponent.prototype.handleBlurValidation = function (item, e) {
        item.__validate();
    };

    TextBoxComponent.prototype.__createUniqueId = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r &amp; 0x3 | 0x8);
            return v.toString(16);
        });
    };

    TextBoxComponent.prototype.__validate = function () {
        var item = this;
        if (!ko.unwrap(item.trackChanges)) {
            return;
        }
        item.errors.removeAll();
        item.hasErrors(false);
        if (item.isRequired &amp;&amp; ko.unwrap(item.value) === '') {
            item.errors.push(ko.unwrap(item.label) + &quot; is required.&quot;);
            item.hasErrors(true);
            return;
        }
    };

    function TextBoxComponentFactory(params, componentInfo) {
        var el = componentInfo.element;
        var component = new TextBoxComponent(params, el);
        return component;
    };

    return { viewModel: { createViewModel: TextBoxComponentFactory }, template: htmlString };
});
</pre>
<p>After the change, I just visit the pages and add the helpText parameter and it will show up on the control with minimal effort.</p>
<pre class="brush: xml; title: ; notranslate">
            &lt;client-text-box params=&quot;label: 'Item ID',
                          value:ItemId,
                          isRequired:true,
                          hasFocus: true,
                          trackChanges: dataLoaded,
                          helpText: 'This can be any unique identifier'&quot;&gt;
            &lt;/client-text-box&gt;
</pre>
<h2>Wrap Up</h2>
<p>Now that I have had a chance to create my own components using KnockoutJS, I can honestly say that I am convinced this is a great pattern for web applications. I can see why it is used as part of the strategy for the Microsoft Azure Portal and why they are at the heart of frameworks like Angular and React.</p>
<p>However, this journey wasn&#8217;t without some initial challenges and frustrations. Listed below are just a few of the more notable challenges that frustrated me early on.</p>
<ul>
<li><b>Bundling and Configuration</b><br />This took some time to really understand. With a goal of having every page with script specific to that page all bundled into a single file, setting up the initial bundle for a page was tedious and error-prone. There have been a number of times that I have created a new component and struggled to get it working because I had typed an incorrect path for one of the bundles. Some of this challenge was alleviated when I created core bundles, but the HTML templates still prove to be a challenge as they are bundled independently of the JavaScript.</li>
<li><b>File Sprawl</b><br />One challenge I didn&#8217;t anticipate is the large number of files I needed to have open at times to make changes to my application. At times, a single edit form in the application would require me to have five or six files open. This became confusing at times, especially when I needed to modify several pages.</li>
<li><b>Team Member Understanding</b><br />I did not anticipate some of my team having difficulties understanding how components worked. I had several conversations throughout the project with various team members where I thought I had explained the purpose and value proposition of using them, only to find out later that I didn&#8217;t do as good a job as I had hoped. I&#8217;m still working with my team to communicate the benefits that I experienced on this project using components, which is part of the reason I attempted this post.</li>
</ul>
<p>I have learned a lot over the past three or so months as I&#8217;ve worked through building this application. I am hoping to take some of the lessons learned forward into future projects where I hope it will streamline the development of applications for other clients.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2017/10/12/first-adventure-into-web-components/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">965</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>
	</item>
		<item>
		<title>Using Workflow Definition Language with Microsoft Flow</title>
		<link>https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/</link>
					<comments>https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Wed, 12 Apr 2017 15:54:15 +0000</pubDate>
				<category><![CDATA[Microsoft Flow]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=955</guid>

					<description><![CDATA[Microsoft Flow is a very powerful product and it is getting better all the time. However, I wanted to build a flow that would automatically create a date in the future when creating tasks on my Trello board. There isn&#8217;t anything built into Microsoft Flow Actions that does this (at the time of this blog &#8230; <a href="https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/" class="more-link">Continue reading <span class="screen-reader-text">Using Workflow Definition Language with Microsoft&#160;Flow</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Microsoft Flow is a very powerful product and it is getting better all the time. However, I wanted to build a flow that would automatically create a date in the future when creating tasks on my Trello board. There isn&#8217;t anything built into Microsoft Flow Actions that does this (at the time of this blog post), but it can be done using <a href="https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-workflow-definition-language" target="_blank">Microsoft&#8217;s Workflow Definition Language</a>.</p>
<p><span id="more-955"></span></p>
<p>The workflow definition language is used when building <a href="https://azure.microsoft.com/en-us/services/logic-apps/" target="_blank">Azure Logic Apps</a>. Since Microsoft Flow makes use of Logic Apps, we can leverage this to do the calculation. The key to using these functions is understanding how to pass them into your flow actions. This is where I had my biggest disconnect, so I will share what I&#8217;ve learned to this point. I&#8217;m sure there are better ways to do these steps, but I&#8217;m just at the beginning of discovering how.</p>
<p>First, I logged into Microsoft Flow and created a new flow. When working on solving new problems, my goal is to build something very simple that solves my immediate problem and then back up to include it in the final solution. This flow is the simple solution, so I called it Simple Time Calculation Testing. The idea is to send a notification with an updated (and formatted) time 15 days from the time the flow ran.</p>
<p>To start, I added a recurrence trigger set to run every 15 minutes. The frequency doesn&#8217;t really matter as I will typically run it each time I want to test it.</p>
<p><img loading="lazy" data-attachment-id="956" data-permalink="https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/flowblock1/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png" data-orig-size="489,202" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="FlowBlock1" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png?w=489" class="alignnone size-full wp-image-956" src="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png" alt="" width="489" height="202" srcset="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png 489w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png?w=150&amp;h=62 150w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png?w=300&amp;h=124 300w" sizes="(max-width: 489px) 100vw, 489px" /></p>
<p>I read on the <a href="https://powerusers.microsoft.com/t5/Building-Flows/Using-the-Workflow-definition-language-in-Flow/m-p/20307#M998" target="_blank">Flow Community Board</a> that the Data &#8211; Compose operation can be used to execute the workflow definition language formulas. Using this information, I added a Data &#8211; Compose action to my flow and set it to use add 15 days to the current time. The trick here is to use a single @ and enclose the entire function call in quotes.</p>
<pre class="brush: plain; title: ; notranslate">
&quot;@formatDateTime(adddays(utcnow(),15),'D')&quot;
</pre>
<p>The formula starts with a call to formatDateTime. Notice the second formula, adddays, is not preceeded by @, nor is the third function utcnow. This call will get the current date and time using the utcnow function. It will then have 15 days added to it by the addays function. Finally, it is formatted into the long date format by using the second parameter of the formatDateTime function.</p>
<p><img loading="lazy" data-attachment-id="957" data-permalink="https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/flowblock2/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png" data-orig-size="487,140" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="flowblock2" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png?w=487" class="alignnone size-full wp-image-957" src="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png" alt="" width="487" height="140" srcset="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png 487w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png?w=150&amp;h=43 150w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png?w=300&amp;h=86 300w" sizes="(max-width: 487px) 100vw, 487px" /></p>
<p>In the image above, the flow has already run so the quotes do not appear. When the block was added, it originally had the quotation marks.</p>
<p>Now we have a flow that runs every 15 minutes and gets the current time formatted in a long date. To see the date, I added a notification block to send an email of the contents. The final flow appears below.</p>
<p><img loading="lazy" data-attachment-id="958" data-permalink="https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/flowfinal/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png" data-orig-size="488,601" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="flowfinal" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png?w=244" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png?w=488" class="alignnone size-full wp-image-958" src="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png" alt="" width="488" height="601" srcset="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png 488w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png?w=122&amp;h=150 122w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png?w=244&amp;h=300 244w" sizes="(max-width: 488px) 100vw, 488px" /></p>
<p>Testing the flow, I get the expected result in an email notification.</p>
<p><img loading="lazy" data-attachment-id="959" data-permalink="https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/flownotification/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png" data-orig-size="889,308" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="flownotification" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=700" class="alignnone size-full wp-image-959" src="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png" alt="" width="611" height="212" srcset="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=611&amp;h=212 611w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=150&amp;h=52 150w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=300&amp;h=104 300w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png?w=768&amp;h=266 768w, https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png 889w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<hr />
<p>In summary, as you can see here it is possible to make use of the Workflow Definition Language from Logic Apps within your Microsoft Flows. The challenge I&#8217;ve found is just simply knowing what you CAN do as the documentation leaves you with the impression that you are limited to what is provided in the available triggers and actions. As you can see here, Microsoft Flow does have some hidden capabilities that you can leverage as a developer within your organization.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2017/04/12/using-workflow-definition-language-with-microsoft-flow/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">955</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock1.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowblock2.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flowfinal.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2017/04/flownotification.png" medium="image" />
	</item>
		<item>
		<title>Relationships in PowerApps and the Common Data Model question&#8230;</title>
		<link>https://devspoint.wordpress.com/2016/10/21/relationships-in-powerapps-and-the-common-data-model-question/</link>
					<comments>https://devspoint.wordpress.com/2016/10/21/relationships-in-powerapps-and-the-common-data-model-question/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Fri, 21 Oct 2016 19:08:40 +0000</pubDate>
				<category><![CDATA[Development Approaches]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=942</guid>

					<description><![CDATA[Since I am unable to post comments directly to this page because Disqus is refusing to send a verification email, I am posting the question here and hope someone can provide answers. I understand building relationships, but I need to be able to do more than use a relationship in a lookup. I need to &#8230; <a href="https://devspoint.wordpress.com/2016/10/21/relationships-in-powerapps-and-the-common-data-model-question/" class="more-link">Continue reading <span class="screen-reader-text">Relationships in PowerApps and the Common Data Model&#160;question&#8230;</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Since I am unable to post comments directly to <a href="https://powerapps.microsoft.com/en-us/tutorials/data-platform-entity-lookup/" target="_blank">this page</a> because Disqus is refusing to send a verification email, I am posting the question here and hope someone can provide answers.</p>
<hr />
<p>I understand building relationships, but I need to be able to do more than use a relationship in a lookup. I need to be able to display related records from an entity based on a currently selected entity in a parent table.</p>
<p>For example, say I am building a work order tracking system for techs and customers. I have a work order entity and a work order history entity I would like to use. The work order tracks the original request from the customer while the history tracks the individual touch points a tech has made with the customer. I have a screen that displays the details of the work order. I want to be able to display the full history of the work order (and add more records) by going from the currently selected entity. This scenario is almost entirely missing, and in my opinion a much more common use of relationships in entities.</p>
<p>Are there plans to support related data in a much more comprehensive manner? If not, this could be a very limiting factor with using both the common data model and PowerApps since a lot of data structures make use of relationships to expose related data.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2016/10/21/relationships-in-powerapps-and-the-common-data-model-question/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">942</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>
	</item>
		<item>
		<title>Microsoft Flow : Building a Simple Twitter to Trello Flow</title>
		<link>https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/</link>
					<comments>https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Mon, 29 Aug 2016 15:15:15 +0000</pubDate>
				<category><![CDATA[Business Automation]]></category>
		<category><![CDATA[Microsoft Flow]]></category>
		<category><![CDATA[O365]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=915</guid>

					<description><![CDATA[In this post, I hope to give you some ideas of how easy it is to use Microsoft Flow as I walk through how I created a really simple flow. The flow looks at my Twitter timeline and captures Tweets that contain a specific keyword. When it finds tweets that match, a card is created on my Trello &#8230; <a href="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/" class="more-link">Continue reading <span class="screen-reader-text">Microsoft Flow : Building a Simple Twitter to Trello&#160;Flow</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>In this post, I hope to give you some ideas of how easy it is to use <a href="https://flow.microsoft.com/" target="_blank">Microsoft Flow</a> as I walk through how I created a really simple flow. The flow looks at my Twitter timeline and captures Tweets that contain a specific keyword. When it finds tweets that match, a card is created on my Trello board so I can review it later. This scenario could easily be expanded for an organization that wants to monitor a product launch or needs to field support requests from customers or clients and have them tracked by a team in Trello.</p>
<p>Before we begin, let me introduce you to Microsoft Flow. It is a new service currently in preview that allows you to &#8220;Create automated workflows between your favorite apps and services to get notifications, synchronize files, collect data, and more.&#8221; If you have used web sites like <a href="http://ifttt.com" target="_blank">IFTTT </a>or <a href="https://zapier.com" target="_blank">Zapier</a>, you may be familiar with the general idea of Microsoft Flow. These tools allow you to build workflows between various internet based applications.</p>
<p>For example, I have an IFTTT recipe that automatically puts my cell phone on silent any time I reach my office building and another IFTTT recipe that notifies me if my Nest thermostat detects my house has reached 80 degrees (uh oh, time to call the A/C repairman). The focus of these tools is to make it easy for a user to automate a small task.</p>
<p><span id="more-915"></span></p>
<p>I&#8217;ve already mentioned a few tasks that I commonly have another internet automation tool complete for me, so what is different about Microsoft Flow? Yes, in a lot of ways it looks to be yet another internet automation entry into a flooded space of alternatives. The key difference for this service, in my opinion, it that it is focused on automation in the enterprise. This means it taps into the power of Azure and O365. Using gateways, it also has the potential to connect to on-premise enterprise systems extending the reach of flows beyond those currently on the market. This makes it quite clear it is not just another entry into the space.</p>
<hr />
<p>From the homepage of flow, there is an option to see <strong>my flows</strong> (You must be logged in with a Work or School account to see this option, sign-up is free!).</p>
<p><img loading="lazy" data-attachment-id="917" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f1/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png" data-orig-size="1005,205" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f1" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=700" class="alignnone size-full wp-image-917" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png" alt="f1" width="611" height="125" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=611&amp;h=125 611w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=150&amp;h=31 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=300&amp;h=61 300w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png?w=768&amp;h=157 768w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png 1005w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<p>This takes me to the screen to create a new blank flow or select a flow from an existing template. There are a large number of templates that you can use to start building flows. If you have not previously connected to a service represented in the template, you will be required to create the connection before you can continue.</p>
<p>Creating a blank flow loads the designer and prompts for the trigger. After typing <em>Twitter</em> in the trigger box, the results are filtered making it easy to find the specific trigger needed.</p>
<p><img loading="lazy" data-attachment-id="918" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f2/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png" data-orig-size="994,320" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f2" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=700" class="alignnone size-full wp-image-918" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png" alt="f2" width="611" height="197" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=611&amp;h=197 611w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=150&amp;h=48 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=300&amp;h=97 300w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png?w=768&amp;h=247 768w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png 994w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<p>As I mentioned previously, if the service has not yet been connected a request will be presented to create the new connection.</p>
<p><img loading="lazy" data-attachment-id="919" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f3/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png" data-orig-size="502,194" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f3" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png?w=502" class="alignnone size-full wp-image-919" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png" alt="f3" width="502" height="194" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png 502w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png?w=150&amp;h=58 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png?w=300&amp;h=116 300w" sizes="(max-width: 502px) 100vw, 502px" /></p>
<p>Simply sign into the service and then authorize Flow to be able to use it. This is a standard OAuth authorization request currently used across the web.</p>
<p>In my scenario, the service should be watching for Tweets containing Microsoft Flow. A basic understanding of how the service utilizes search lets me know the phrase will need to be contained within quotation marks. If the desire is to monitor for specific users or hashtags, the search team would be the users&#8217; twitter name (i.e. @cquick) or the desired hashtag (#Rio2016).</p>
<p><img loading="lazy" data-attachment-id="920" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f4/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png" data-orig-size="532,297" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f4" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png?w=532" class="alignnone size-full wp-image-920" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png" alt="f4" width="532" height="297" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png 532w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png?w=150&amp;h=84 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png?w=300&amp;h=167 300w" sizes="(max-width: 532px) 100vw, 532px" /></p>
<p>This completes the configuration for the Flow Trigger. Unfortunately, there is no ability to currently test the flow to see what is returned. I hope that feature is added at some point because I can see scenarios where you might have triggers coming from a custom API (HTTP + SWAGGER) or Azure Service Bus and you need to make sure you are getting the correct data without running the flow first.</p>
<p>The next step in creating this simple flow is to add an action to take after the trigger is successfully run. Click on New Step and a couple of options are presented. (I have clicked on More so those options are visible as well).</p>
<p><img loading="lazy" data-attachment-id="921" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f5/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png" data-orig-size="503,265" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f5" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png?w=503" class="alignnone size-full wp-image-921" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png" alt="f5" width="503" height="265" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png 503w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png?w=150&amp;h=79 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png?w=300&amp;h=158 300w" sizes="(max-width: 503px) 100vw, 503px" /></p>
<p>By far, the most common action will either be adding some type of condition or going straight to some type of action. This shows there is an ability to iterate over multiple items that a single trigger could return.</p>
<p>In this simple flow, creating an action to Trello is the desired path. Clicking on Add an action presents the action window. It looks identical to the trigger so the same steps can be taken to locate the desired action. In this case, typing <em>Trello</em> provides the option for creating a new card.</p>
<p><img loading="lazy" data-attachment-id="922" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f6/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png" data-orig-size="469,384" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f6" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png?w=469" class="alignnone size-full wp-image-922" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png" alt="f6" width="469" height="384" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png 469w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png?w=150&amp;h=123 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png?w=300&amp;h=246 300w" sizes="(max-width: 469px) 100vw, 469px" /></p>
<p>Again, if the service has not yet been connected it will need to be connected and authorized before it can be used by Flow.</p>
<p>In this step, I encountered one of the bugs in the system. I could easily select a Trello board, but selecting the specific list failed. Remember, this is still a preview so there will be the occasional hiccup.</p>
<p><img loading="lazy" data-attachment-id="923" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f7/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png" data-orig-size="448,147" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f7" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png?w=448" class="alignnone size-full wp-image-923" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png" alt="f7" width="448" height="147" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png 448w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png?w=150&amp;h=49 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png?w=300&amp;h=98 300w" sizes="(max-width: 448px) 100vw, 448px" /></p>
<p>It is actually pretty easy to solve since Trello offers the ability to export a board as JSON data. I just need to export the data and look for the id of the list to use. Since that is outside the scope of the article, I&#8217;m not going to focus on each of the steps to accomplish the goal. I am mentioning it because I want to explain why the configuration of the flow shows a globally unique identifier instead of the friendly name of the Trello list.</p>
<p>Configuring the action is actually pretty easy (aside from the note above). The trigger passes data to the action. It is easy to identify the source when clicking on a text area. Flow attempts to give you quick access to the most commonly used properties from the trigger. In the screenshot, the action is configured to utilize the Flow Tweets board and a list has been specified. The title of each card will contain <em>A new tweet by </em>and then the name of the user. The description for the Trello card will be the content of the Tweet.</p>
<p><img loading="lazy" data-attachment-id="924" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f8/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png" data-orig-size="470,563" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f8" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png?w=250" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png?w=470" class="alignnone size-full wp-image-924" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png" alt="f8" width="470" height="563" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png 470w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png?w=125&amp;h=150 125w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png?w=250&amp;h=300 250w" sizes="(max-width: 470px) 100vw, 470px" /></p>
<p>This completes the steps for my flow. Now the flow needs a name. This is done by clicking at the top of the flow on <em>Untitled</em> followed by typing the name of the flow.</p>
<p><img loading="lazy" data-attachment-id="925" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f9/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png" data-orig-size="849,49" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f9" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=700" class="alignnone size-full wp-image-925" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png" alt="f9" width="611" height="35" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=611&amp;h=35 611w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=150&amp;h=9 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=300&amp;h=17 300w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png?w=768&amp;h=44 768w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png 849w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<p>Clicking Create Flow will create the flow and start monitoring for the trigger to be fired. You can review the status of all of your flows by clicking on the Activity tab at the top of the flow page. It looks like I maxed out my calls to the Twitter API. I guess I need to add a delay activity to my flow so that doesn&#8217;t happen again!</p>
<p><img loading="lazy" data-attachment-id="926" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f10/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png" data-orig-size="943,695" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f10" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=700" class="alignnone size-full wp-image-926" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png" alt="f10" width="611" height="450" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=611&amp;h=450 611w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=150&amp;h=111 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=300&amp;h=221 300w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png?w=768&amp;h=566 768w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png 943w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<hr />
<p>This was an example of a very simple flow. It demonstrates how easy it is to create a flow and get it working. I started by specifying a trigger, I added an action to take based on that trigger and then I saved the flow. At that point, it began creating cards on my Trello board (<a href="https://trello.com/b/Z66SUFfm" rel="nofollow">https://trello.com/b/Z66SUFfm</a>).</p>
<p>In my opinion, this promises to be a great tool allowing business users to automate some of their day-to-day activities. It is pretty evident even in this preview, there are some really powerful templates being created:</p>
<p><img loading="lazy" data-attachment-id="927" data-permalink="https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/f11/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png" data-orig-size="588,387" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="f11" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png?w=588" class="alignnone size-full wp-image-927" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png" alt="f11" width="588" height="387" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png 588w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png?w=150&amp;h=99 150w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png?w=300&amp;h=197 300w" sizes="(max-width: 588px) 100vw, 588px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2016/08/29/microsoft-flow-building-a-simple-twitter-to-trello-flow/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">915</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f11.png" medium="image">
			<media:title type="html">f1</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f2.png" medium="image">
			<media:title type="html">f2</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f3.png" medium="image">
			<media:title type="html">f3</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f4.png" medium="image">
			<media:title type="html">f4</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f5.png" medium="image">
			<media:title type="html">f5</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f6.png" medium="image">
			<media:title type="html">f6</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f7.png" medium="image">
			<media:title type="html">f7</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f8.png" medium="image">
			<media:title type="html">f8</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f9.png" medium="image">
			<media:title type="html">f9</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f10.png" medium="image">
			<media:title type="html">f10</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/f111.png" medium="image">
			<media:title type="html">f11</media:title>
		</media:content>
	</item>
		<item>
		<title>Quick Tip:Toggle SharePoint 2013/O365 SharePoint Sites Full Screen Mode with JavaScript</title>
		<link>https://devspoint.wordpress.com/2016/08/22/quick-tiptoggle-sharepoint-2013o365-sharepoint-sites-full-screen-mode-with-javascript/</link>
					<comments>https://devspoint.wordpress.com/2016/08/22/quick-tiptoggle-sharepoint-2013o365-sharepoint-sites-full-screen-mode-with-javascript/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Mon, 22 Aug 2016 20:21:14 +0000</pubDate>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Tips and Tricks]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=898</guid>

					<description><![CDATA[This is just a quick little snippet you can add to a SharePoint 2013/O365 SharePoint Site to automatically turn on Full Screen Mode when you load a page within the site and how to turn it back off when you exit that page. If you look near the top of the ribbon, you will see &#8230; <a href="https://devspoint.wordpress.com/2016/08/22/quick-tiptoggle-sharepoint-2013o365-sharepoint-sites-full-screen-mode-with-javascript/" class="more-link">Continue reading <span class="screen-reader-text">Quick Tip:Toggle SharePoint 2013/O365 SharePoint Sites Full Screen Mode with&#160;JavaScript</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>This is just a quick little snippet you can add to a SharePoint 2013/O365 SharePoint Site to automatically turn on Full Screen Mode when you load a page within the site and how to turn it back off when you exit that page.</p>
<p><span id="more-898"></span></p>
<p>If you look near the top of the ribbon, you will see a little icon that allows you to Focus Content. This icon simply calls a JavaScript function to toggle some CSS classes so the visitor gets a focused view of the content. This function can be used in your own calls to help highlight content on a page when the page loads.</p>
<p><img loading="lazy" data-attachment-id="905" data-permalink="https://devspoint.wordpress.com/2016/08/22/quick-tiptoggle-sharepoint-2013o365-sharepoint-sites-full-screen-mode-with-javascript/focuscontentimage/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png" data-orig-size="209,96" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="FocusContentImage" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png?w=209" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png?w=209" class="alignnone size-full wp-image-905" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png" alt="FocusContentImage" width="209" height="96" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png 209w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png?w=150&amp;h=69 150w" sizes="(max-width: 209px) 100vw, 209px" /></p>
<p>This following snippet can be placed into a script editor web part and will cause the screen to go to focused mode when the content is loaded. When the visitor navigates away from the page, they will be returned to a non-focused view of the content.</p>
<pre class="brush: jscript; title: ; notranslate">
window.addEventListener('unload',function() {
    SetFullScreenMode(false);
});

window.addEventListener('load',function() {
    SetFullScreenMode(true);
});
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2016/08/22/quick-tiptoggle-sharepoint-2013o365-sharepoint-sites-full-screen-mode-with-javascript/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">898</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/focuscontentimage.png" medium="image">
			<media:title type="html">FocusContentImage</media:title>
		</media:content>
	</item>
		<item>
		<title>Setting Permissions on an Item to a Group with Nintex Workflow for O365</title>
		<link>https://devspoint.wordpress.com/2016/08/16/setting-permissions-on-an-item-to-a-group-with-nintex-workflow-for-o365/</link>
					<comments>https://devspoint.wordpress.com/2016/08/16/setting-permissions-on-an-item-to-a-group-with-nintex-workflow-for-o365/#comments</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Tue, 16 Aug 2016 18:30:52 +0000</pubDate>
				<category><![CDATA[Nintex]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=867</guid>

					<description><![CDATA[Recently, one of the summer interns at my company was tasked with creating an expense report solution utilizing O365, Nintex Forms for O365 and Nintex Workflow for O365. As the expense report moves through the approval phases, permissions need to be altered so the original report that has been submitted cannot be changed by the &#8230; <a href="https://devspoint.wordpress.com/2016/08/16/setting-permissions-on-an-item-to-a-group-with-nintex-workflow-for-o365/" class="more-link">Continue reading <span class="screen-reader-text">Setting Permissions on an Item to a Group with Nintex Workflow for&#160;O365</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" data-attachment-id="870" data-permalink="https://devspoint.wordpress.com/2016/08/16/setting-permissions-on-an-item-to-a-group-with-nintex-workflow-for-o365/permissionsactionblock/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png" data-orig-size="190,143" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="PermissionsActionBlock" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png?w=190" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png?w=190" class="size-full wp-image-870 alignright" src="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png" alt="PermissionsActionBlock" width="190" height="143" srcset="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png 190w, https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png?w=150&amp;h=113 150w" sizes="(max-width: 190px) 100vw, 190px" />Recently, one of the summer interns at my company was tasked with creating an expense report solution utilizing O365, Nintex Forms for O365 and Nintex Workflow for O365. As the expense report moves through the approval phases, permissions need to be altered so the original report that has been submitted cannot be changed by the author. This allows the approver to review the expense report without alterations occurring after approval.</p>
<p><span id="more-867"></span></p>
<p>In many of the workflows I have built over the years with SharePoint based solutions, I have found this to be a very common requirement. It is easy to understand why there was some initial concern when the intern requested assistance because the Office 365 update item permissions activity in Nintex Workflow for O365 was not working as expected.</p>
<p>The activity is configured to break item inheritance, set the original author with read-only rights and allow a group containing approvers to have access to modify the item. When the workflow executes, it reaches the Office 365 update item permissions where it stops and logs: &#8220;An authentication error has occurred&#8221;. We attempted changing the credentials used by the activity and continued to receive the same error.</p>
<p>Nintex has a community portal that contains many questions and answers in the use of their products. A search on the portal revealed the following support thread: <a href="https://community.nintex.com/thread/3372" target="_blank">Office 365 Update Item Permissions &#8211; Group name</a>. This thread describes the exact issue we faced, whenever the name of the group was typed directly into the Office 365 update item permissions activity, it fails to resolve the group by name.</p>
<p>In the thread, one of the Nintex employees (<a href="https://community.nintex.com/people/eharris04" target="_blank">Eric Harris</a>) mentions they utilized a workflow variable and set the group via a lookup to the variable. I created a simple list in our O365 tenant where Nintex Workflow is installed and created a very simple workflow. The workflow contains a text variable named <em>Group</em> and two activities. The first activity is to set a workflow variable and is configured to set the variable <em>Group<strong> </strong></em>to one of the groups defined on the site. The second activity is the Office 365 update item permissions activity which is set exactly the same as the one in the approval workflow the intern is designing. Instead of typing the name of the group, a lookup to the <em>Group</em> variable is used. This test worked successfully and the permissions for the item in the list changed as intended.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2016/08/16/setting-permissions-on-an-item-to-a-group-with-nintex-workflow-for-o365/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">867</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2016/08/permissionsactionblock.png" medium="image">
			<media:title type="html">PermissionsActionBlock</media:title>
		</media:content>
	</item>
		<item>
		<title>Refocusing this blog</title>
		<link>https://devspoint.wordpress.com/2016/08/16/refocusing-this-blog/</link>
					<comments>https://devspoint.wordpress.com/2016/08/16/refocusing-this-blog/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Tue, 16 Aug 2016 13:15:44 +0000</pubDate>
				<category><![CDATA[Planning]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=875</guid>

					<description><![CDATA[I will admit that I have been very silent over the last year. There are a variety of reasons, but one of the primary reasons is that the focus of my work has been evolving with the new world of the cloud. I am still very involved in SharePoint and O365, building solutions around these &#8230; <a href="https://devspoint.wordpress.com/2016/08/16/refocusing-this-blog/" class="more-link">Continue reading <span class="screen-reader-text">Refocusing this blog</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>I will admit that I have been very silent over the last year. There are a variety of reasons, but one of the primary reasons is that the focus of my work has been evolving with the new world of the cloud.</p>
<p><span id="more-875"></span></p>
<p>I am still very involved in SharePoint and O365, building solutions around these products. I have also included Nintex Workflow and Nintex Forms in some of the newer solutions.</p>
<p>A old area that has been revisited recently is custom application development. The team at my company has been involved in more projects that involve custom applications for our clients. This has given me more exposure to plenty of new technologies, patterns and ways to deliver solutions.</p>
<p>Finally, I have also been investigating new opportunities for solutions built in O365/Azure with the introduction of products like <a href="https://powerapps.microsoft.com/en-us/" target="_blank">PowerApps</a> (in preview), <a href="https://flow.microsoft.com/en-us/" target="_blank">Microsoft Flow</a> (in preview), and <a href="https://azure.microsoft.com/en-us/services/logic-apps/" target="_blank">Logic Apps</a> (just to name a few).</p>
<p>That is why I am going to be refocusing the content of this blog to some degree. There will still be content around SharePoint and O365, but you will start to see more information and details around other technologies as well.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2016/08/16/refocusing-this-blog/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">875</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>
	</item>
		<item>
		<title>Handlebars + JSLink</title>
		<link>https://devspoint.wordpress.com/2015/07/24/handlebars-jslink/</link>
					<comments>https://devspoint.wordpress.com/2015/07/24/handlebars-jslink/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Fri, 24 Jul 2015 14:06:24 +0000</pubDate>
				<category><![CDATA[JS Link]]></category>
		<category><![CDATA[SharePoint 2013]]></category>
		<category><![CDATA[Tips and Tricks]]></category>
		<category><![CDATA[Handlebars.js]]></category>
		<category><![CDATA[JSLink]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=857</guid>

					<description><![CDATA[First, I want to apologize for such a long silence. Project work and life in general has kept me very busy over the last year, so it has been difficult to find a time to write new content. This article describes a specific scenario I experienced as part of my standard project work. It hasn&#8217;t &#8230; <a href="https://devspoint.wordpress.com/2015/07/24/handlebars-jslink/" class="more-link">Continue reading <span class="screen-reader-text">Handlebars + JSLink</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<blockquote><p><em>First, I want to apologize for such a long silence. Project work and life in general has kept me very busy over the last year, so it has been difficult to find a time to write new content.</em></p>
<p><em>This article describes a specific scenario I experienced as part of my standard project work. It hasn&#8217;t been extended or tested for other scenarios (yet!). Handle with care.</em>
</p></blockquote>
<p>Since the release of SharePoint 2013, I have been doing a lot of customizations utilizing JSLink on lists and list views. One of the things that I really do not like about JSLink is all of the string concatenation that tends to happen when building the output for your JSLink customization. It is not a huge challenge when you are working with a small template or limited output, but when you start working with complex HTML strings, it can be a bit unwieldy.<br />
<span id="more-857"></span></p>
<p>In recent years, there have been a lot of JavaScript frameworks that solve this specific problem by providing a way to create a template, or representation of your UI, that is later dynamically populated and put into place. One of these tools is <a href="http://handlebarsjs.com/" target="_blank">Handlebars.js</a>. The challenge with using this framework is that it expects to have a script tag somewhere in the page that has an ID. One way to solve this challenge is to insert a script editor web part into your page with the content of the template. However, I would prefer to have my template isolated as a file that I can modify apart from the script running on the file. The challenge is how to get the template loaded dynamically so that Handlebars.js can make use of it.</p>
<p>After performing a search, I found a great article on how to <a href="http://code-maven.com/handlebars-with-dynamically-loaded-template" target="_blank">dynamically load templates for Handlebars.js</a> using JQuery by Gabor Szabo. The key is using the function with a callback outlined in the improved JavaScript code section of the article.</p>
<p>Using this article as my starting point, I began by building out my handlebars template and placing it in a document library named Site Assets on the site where I needed to use this script. I saved the template with a .handlebars extension (I could have easily just left it as HTML).</p>
<pre class="brush: xml; title: ; notranslate">
&lt;div id=&quot;Vendor_Record&quot;&gt;
	&lt;h1&gt;{{CompanyName}}&lt;/h1&gt;&lt;h2&gt;FEIN: {{FEIN}}&lt;/h2&gt;
		&lt;hr style=&quot;color:red;&quot;/&gt;
	&lt;div class=&quot;address&quot;&gt;
		{{WorkAddress}}&lt;br/&gt;
		{{WorkCity}}, {{State}} {{ZipCode}}
	&lt;/div&gt;
	&lt;div class=&quot;contact&quot;&gt;
		{{ContactName}} {{{ContactEmailAddress}}}&lt;br/&gt;
		{{PhoneNumber}}
	&lt;/div&gt;
	&lt;div class=&quot;status&quot;&gt;
		&lt;table&gt;
			&lt;tr&gt;
				&lt;th&gt;Category&lt;/th&gt;
				&lt;td&gt;{{VendorCategory}}&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
				&lt;th&gt;Status&lt;/th&gt;
				&lt;td&gt;{{VendorStatus}} - {{ApprovalDeniedReason}}&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/table&gt;
	&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>Now the hard part, getting JSLink to work with my template. In my final solution, I need to only show a single record and I am overriding the entire view. I&#8217;ll first include the code and then describe what each part of the code is doing.</p>
<pre class="brush: jscript; title: ; notranslate">
var SomeCompany = SomeCompany || {};

SomeCompany.Product = SomeCompany.Product || {};
SomeCompany.Product.ViewVendorRecord = SomeCompany.Product.ViewVendorRecord || (function() {
	var itemID = null,
		divID = null,
		render = false,
		template;
		
	function getTemplateAjax(path, callback){
		if(!template){
			var source;
			$.ajax({
				url: path,
				cache: true,
				success: function(data) {
					//console.log(data);
					source = data;
					template = Handlebars.compile(source);
					if(callback) callback(template);
				}
			});
		} else
		if(template) {
			if (callback) callback(template);
		}
	};
	
	return {
		renderHeader: function (ctx) {
			return &quot;&lt;input type=\&quot;hidden\&quot; id=\&quot;&quot; + itemID + &quot;\&quot; value=\&quot;&quot; + encodeURI(JSON.stringify(ctx.ListData.Row[0])) + &quot;\&quot;/&gt;&lt;hr/&gt;&quot;;
		},
		renderFooter: function (ctx) {
			return &quot;&lt;hr/&gt;&quot;;
		},
		renderItem: function (ctx) {
			return &quot;&lt;div id=\&quot;&quot; + divID + &quot;\&quot;&gt;&lt;/div&gt;&quot;;
		},
		onPostRender: function (ctx) {
			var data = JSON.parse(decodeURI($(&quot;#&quot; + itemID).val()));
			getTemplateAjax(&quot;/sites/Product/siteassets/handlebartemplates/vendor.handlebar&quot;, function(template) {
				$(&quot;#&quot; + divID).html(template(data));
			});
		},
		onPreRender: function (ctx) {
			render = ctx.ListData.Row.length &gt; 0;
			if(render){
				itemID = &quot;item_&quot; + ctx.ListData.Row[0].ID;
				divID = &quot;div_&quot; + ctx.ListData.Row[0].ID;
			}
		}
	};
})();

(function() {
	var overrideCtx = {};
	
	overrideCtx.Templates = {};

	overrideCtx.Templates.Header = SomeCompany.Product.ViewVendorRecord.renderHeader;
	overrideCtx.Templates.Item = SomeCompany.Product.ViewVendorRecord.renderItem;
	overrideCtx.Templates.Footer = SomeCompany.Product.ViewVendorRecord.renderFooter;
	overrideCtx.Templates.OnPostRender = SomeCompany.Product.ViewVendorRecord.onPostRender;
	overrideCtx.Templates.OnPreRender = SomeCompany.Product.ViewVendorRecord.onPreRender;
	overrideCtx.BaseViewID = 1;
	overrideCtx.ListTemplateType = 100;
	
	SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();
</pre>
<p>This code is setup so that it shouldn&#8217;t pollute the global namespace, something I advocated for in a <a href="https://devspoint.wordpress.com/2014/06/16/js-link-avoid-polluting-the-global-namespace/">previous post</a>.</p>
<p><strong>Lines 10-27:</strong> This is the function used to dynamically load the handlebars template. This is pretty much the same code presented in the article by Gabor Szabo I referenced earlier. The only change is that I store the template in a variable for the module. </p>
<p><strong>Lines 30-32:</strong> The renderHeader outputs a single hidden input and stuffs the contents of the first item in the list into it. This could be omitted as I could perform the same action in the renderItem function (untested at the moment). The change would be to use ctx.CurrentItem instead of ctx.ListData.Rows[0] to get the data. </p>
<p><strong>Lines 36-38:</strong> My only output in the renderItem is a div with an ID to be the target for Handlebars.js.</p>
<p><strong>Lines 45-51:</strong> JSLink give you both an OnPreRender and OnPostRender function that fires for every item rendered in the list for the current view. The onPreRender function simply calculates a unique ID for each item and stores it for later use by the JSLink file. This is another area that would need changes in order to generate a unique ID for Handlebars.js to utilize as a target.</p>
<p><strong>Lines 39-44:</strong> OnPostRender is responsible for making sure the template is loaded (line 41) and provides the callback function with will actually place the rendered content into the target DIV.</p>
<p>I want to stress that this code has only been tested rendering a single item in a list view (as this is the requirement for my project). I will continue to experiment to see if I can reuse this idea for rendering multiple items (or even individual fields) and post any findings. Please feel free to share any of your experiences as well. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2015/07/24/handlebars-jslink/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">857</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>
	</item>
		<item>
		<title>JS Link: Avoid Polluting the Global Namespace</title>
		<link>https://devspoint.wordpress.com/2014/06/16/js-link-avoid-polluting-the-global-namespace/</link>
					<comments>https://devspoint.wordpress.com/2014/06/16/js-link-avoid-polluting-the-global-namespace/#comments</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Mon, 16 Jun 2014 18:54:12 +0000</pubDate>
				<category><![CDATA[Development Approaches]]></category>
		<category><![CDATA[JS Link]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[SharePoint 2013]]></category>
		<category><![CDATA[Tips and Tricks]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=833</guid>

					<description><![CDATA[Over the past few months I have been doing a lot of research around JavaScript frameworks and techniques as I am learning how to create SharePoint 2013/O365 Apps for clients. A recent project had a need to override the default view of a few columns. JS Link proved to be the easiest way to accomplish &#8230; <a href="https://devspoint.wordpress.com/2014/06/16/js-link-avoid-polluting-the-global-namespace/" class="more-link">Continue reading <span class="screen-reader-text">JS Link: Avoid Polluting the Global&#160;Namespace</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Over the past few months I have been doing a lot of research around JavaScript frameworks and techniques as I am learning how to create SharePoint 2013/O365 Apps for clients. A recent project had a need to override the default view of a few columns. <a target="_blank" href="http://www.idubbs.com/blog/2012/js-link-for-sharepoint-2013-web-partsa-quick-functional-primer/">JS Link</a> proved to be the easiest way to accomplish the changes, but every example that I found had all of the JavaScript functions placed squarely in the global namespace. One of the things that I find seems to be common among all of the books and articles I have read over the past three months clearly states that we should do our due diligence to keep the global namespace in JavaScript as clean as possible.
</p>
<p><span id="more-833"></span></p>
<p>Here&#8217;s a pretty good <a target="_blank" href="http://stackoverflow.com/questions/8862665/what-does-it-mean-global-namespace-would-be-polluted">explanation</a> over at StackOverflow. The answer discusses garbage collection as well as the dangers of placing everything in the global namespace. The common practice that I see is to wrap up your code into an immediately invoked function expression – if you study the basic code for a JS Link file, you&#8217;ll see that it makes use of an <a target="_blank" href="http://en.wikipedia.org/wiki/IIFE">IIFE</a> to get things rolling.</p>
<pre class="brush: jscript; title: ; notranslate">
(function () {
	var override = {};
	
	override.Template = {};
	
	override.Template.Fields = {
		&quot;Field&quot;: { 
			&quot;View&quot;: function (ctx) {
					return ctx.CurrentItem.title + &quot;: Overridden&quot;;
			}
		}; 
		
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(override);
)();
</pre>
</p>
<p>The IIFE creates a JavaScript line that is immediately executed when the script is loaded. In the example above, a new variable named <em>override</em> is created which will be a JavaScript object. A template property is attached along with a fields object containing the override behavior for the JS Link file. The JS Link, when executed will return the title of the list item with <strong>:Overridden</strong> appended. This template is registered with the TemplateManager in SharePoint.
</p>
<p>The typical approach to creating JS Link files is to add a bunch of individual functions that are called by the JS Link IIFE.
</p>
<pre class="brush: jscript; title: ; notranslate">
(function() {
	var overrideCtx = {};
	overrideCtx.Templates = {};
	
	overrideCtx.Templates.Fields = {
		'Budget_x0020_ID': {'View': budgetIDField },
		'Actuals':{'View': actualsField },
		'Budget':{'View': budgetField }
	};
	
	SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();

function budgetIDField(ctx){
	var html = &quot;&lt;div&gt;&lt;strong&gt;&quot; + ctx.CurrentItem.Budget_x0020_ID + &quot;&lt;/strong&gt;&lt;/div&gt;&quot;;
	return html;
}

function toUSD(number) {
	/* implementation of conversion to USD */
    return '$' + dollars + '.' + cents.slice(0, 2);
}

function actualsField(ctx) {
	var amount = ctx.CurrentItem.Actuals;
	return toUSD(amount);	
}

function budgetField(ctx) {
	var amount = ctx.CurrentItem.Budget;
	return toUSD(amount);
}
</pre>
<p>Technically, this is not incorrect, but it definitely adds additional functions to the global namespace that may not need to be there. To move this out of the global namespace means that an IIFE needs to be created. Additionally, a typical practice that I&#8217;m getting into is to create a namespace object for storing specific JavaScript functions for a client or solution.
</p>
<p>First, I check to see if I already have an object to store my functions that are specific to this solution.
</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid2.png" alt="" />
	</p>
<p>This line will check the global namespace for an object named AwesomeJSLinkSolution. If a variable with the same name is found, it is used. Otherwise a new object is created. (This is good and responsible use of the global namespace.)</p>
<p>Next, the IIFE declaration is created:
</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid3.png" alt="" />
	</p>
<p>With the IIFE, anything that I want to be scoped publically, must be returned. This includes functions and variables that may need to be used later by the solution.
</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid4.png" alt="" />
	</p>
<p>There is also a helper function called <em>toUSD</em> that is used by the solution. Since this function is not necessary to be called from outside the scope of the IIFE, it can be declared directly inside the IIFE. The final code for the IIFE now looks like the following:
</p>
<pre class="brush: jscript; title: ; notranslate">
var AwesomeJSLinkSolution = AwesomeJSLinkSolution || {};

AwesomeJSLinkSolution.BudgetFields = (function () {
	
	function toUSD(amount) {
		/* convert amount */
		return amount;
	};

	return {
		budgetIDField: function (ctx) {
			var html = &quot;&lt;div&gt;&lt;strong&gt;&quot; + ctx.CurrentItem.Budget_x0020_ID + &quot;&lt;/strong&gt;&lt;/div&gt;&quot;;
			return html;
		},
		actualsField: function (ctx) {
			var amount = ctx.CurrentItem.Actuals;
			return toUSD(amount);	
		},
		budgetField: function (ctx) {
			var amount = ctx.CurrentItem.Budget;
			return toUSD(amount);
		}
	}
})();
</pre>
<p>Now the functions that apply to this specific solution are no longer located in the global namespace. Additionally, a function that is specific to this JS Link file is not even accessible outside of the IIFE.</p>
<p> The only outstanding task it to modify the JS Link file. This is a matter of simply updating the function calls to use the new object created by the IIFE.
</p>
<pre class="brush: jscript; highlight: [6,7,8]; title: ; notranslate">
(function() {
	var overrideCtx = {};
	overrideCtx.Templates = {};
	
	overrideCtx.Templates.Fields = {
		'Budget_x0020_ID': {'View': AwesomeJSLinkSolution.BudgetFields.budgetIDField },
		'Actuals':{'View': AwesomeJSLinkSolution.BudgetFields.actualsField },
		'Budget':{'View': AwesomeJSLinkSolution.BudgetFields.budgetField }
	};
	
	SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();
</pre>
<p>The final file source is:</p>
<pre class="brush: jscript; title: ; notranslate">
var AwesomeJSLinkSolution = AwesomeJSLinkSolution || {};

AwesomeJSLinkSolution.BudgetFields = (function () {
	
	function toUSD(amount) {
		/* convert amount */
		return amount;
	};

	return {
		budgetIDField: function (ctx) {
			var html = &quot;&lt;div&gt;&lt;strong&gt;&quot; + ctx.CurrentItem.Budget_x0020_ID + &quot;&lt;/strong&gt;&lt;/div&gt;&quot;;
			return html;
		},
		actualsField: function (ctx) {
			var amount = ctx.CurrentItem.Actuals;
			return toUSD(amount);	
		},
		budgetField: function (ctx) {
			var amount = ctx.CurrentItem.Budget;
			return toUSD(amount);
		}
	}
})();

(function() {
	var overrideCtx = {};
	overrideCtx.Templates = {};
	
	overrideCtx.Templates.Fields = {
		'Budget_x0020_ID': {'View': AwesomeJSLinkSolution.BudgetFields.budgetIDField },
		'Actuals':{'View': AwesomeJSLinkSolution.BudgetFields.actualsField },
		'Budget':{'View': AwesomeJSLinkSolution.BudgetFields.budgetField }
	};
	
	SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
})();
</pre>
<h2>JS Link Resouces<br />
</h2>
<ul>
<li><a target="_blank" href="http://msdn.microsoft.com/en-us/magazine/dn745867.aspx">Using JSLink with SharePoint 2013</a> by Pritam Baldota
</li>
<li><a target="_blank" href="http://www.idubbs.com/blog/2012/js-link-for-sharepoint-2013-web-partsa-quick-functional-primer/">JS Link for SharePoint 2013 Web Parts–A Quick Functional Primer</a> by Wesley Preston
</li>
<li><a target="_blank" href="http://www.learningsharepoint.com/2013/09/16/set-js-link-webpart-property-in-sharepoint-2013/">Set JS Link Webpart Property in SharePoint 2013</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2014/06/16/js-link-avoid-polluting-the-global-namespace/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">833</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid2.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid3.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/06/061614_1854_jslinkavoid4.png" medium="image" />
	</item>
		<item>
		<title>Validating Data with Data Macros in Access Services 2013</title>
		<link>https://devspoint.wordpress.com/2014/03/26/validating-data-with-data-macros-in-access-services-2013/</link>
					<comments>https://devspoint.wordpress.com/2014/03/26/validating-data-with-data-macros-in-access-services-2013/#comments</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Wed, 26 Mar 2014 23:45:21 +0000</pubDate>
				<category><![CDATA[Access Services]]></category>
		<category><![CDATA[SharePoint 2013]]></category>
		<category><![CDATA[Data Macro]]></category>
		<category><![CDATA[O365]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=819</guid>

					<description><![CDATA[I&#8217;ve recently begun exploring the use of Access Services with O365 just to see what this new capability offers to businesses and developers. Overall, I&#8217;m really impressed with some of the complex things that can be done with Access Services. Setting the stage, let&#8217;s say I am working for a company that will be organizing &#8230; <a href="https://devspoint.wordpress.com/2014/03/26/validating-data-with-data-macros-in-access-services-2013/" class="more-link">Continue reading <span class="screen-reader-text">Validating Data with Data Macros in Access Services&#160;2013</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve recently begun exploring the use of Access Services with O365 just to see what this new capability offers to businesses and developers. Overall, I&#8217;m really impressed with some of the complex things that can be done with Access Services.</p>
<p><span id="more-819"></span></p>
<p>Setting the stage, let&#8217;s say I am working for a company that will be organizing a conference. This conference will be made up of several tracks with multiple sessions. Each room should only support one session per time slot. The conference organizer should not be allowed to double book a room. That would be bad. Based on the listed scenario, the logic to meet this requirement is: <span style="font-size:12pt;"><br />
</span></p>
<ol>
<li>Find out how many sessions are assigned to the room and slot.</li>
<li>If there is more than 1 session assigned to a room and a slot, don&#8217;t let a record get created.</li>
<li>Notify the person inserting or updating the record that the room is already occupied.</li>
</ol>
<p>In the database, the following tables have already been created:</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada1.png" alt="" /></p>
<p>&nbsp;</p>
<p>Rooms is simply a list of available rooms for the conference sessions. There is not much to show with this table.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada2.png" alt="" /></p>
<p>&nbsp;</p>
<p>Sessions are the individual conference break-out sessions. In this table there is a references to rooms and a reference to slots.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada3.png" alt="" /></p>
<p>&nbsp;</p>
<p>The final table, slots is just a simple list of the various time slots.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada4.png" alt="" /></p>
<p>&nbsp;</p>
<p>There is not an easy way to get a count of records with the built in data macros, but this is access and queries are great at this. A query is created that  to find out exactly how many sessions are registered for a specific slot and room combination. Adding a query is done by clicking on the Advanced button and selecting Query. (I would also recommend turning on the Navigation Pane so you can see the structure of the assets that make up the database.)</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada5.png" alt="" /></p>
<p>&nbsp;</p>
<p>The only table that is needed for this query is the Sessions table since it holds the references to both the room and slot tables. Since the query needs to know what room and slot combination to check, two parameters that will be created. This will help later by providing an easy way to request the data from the query. Click on Parameters in the ribbon.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada6.png" alt="" /></p>
<p>&nbsp;</p>
<p>Two parameters need to be created, one for the ID of the room and another for the ID of the slot.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada7.png" alt="" /></p>
<p>&nbsp;</p>
<p>Also, this query is going to be used for calculations. Turn on the totals allows the selection of count and where that will be a part of the query.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada8.png" alt="" /></p>
<p>&nbsp;</p>
<p>The first column will be a simple count of the ID. The next two columns will be Where fields set to the room and slot and utilizing the parameters created previously.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032714_0100_creatingada1.png" alt="" /></p>
<p>&nbsp;</p>
<p>I save the query and give it a really nice developer name: <strong><em>qryCountSessionInRoomAndSlot</em></strong>. Now it&#8217;s time to create the data macro that will be used to help enforce a single session per slot and room. Again, this is found on the advanced tab in the ribbon.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada10.png" alt="" /></p>
<p>&nbsp;</p>
<p>This data macro will need to take the ID of a slot and the ID of a room and execute the query that was created. This means two parameters will also need to be created in the data macro. This is done by clicking on Create Parameter and provided the details of the parameter. Here, I have added both parameters needed.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada11.png" alt="" /></p>
<p>&nbsp;</p>
<p>Now to execute some logic. This macro is going to execute the query, so the <strong>Look Up A Record</strong> action should be used. When the query is selected, it will automatically show the parameters needed for the query. These need to be mapped to the parameters that are going to be passed into the macro.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada12.png" alt="" /></p>
<p>&nbsp;</p>
<p>Finally, the macro needs to output the resulting data. This is done by using the SetReturnVar action. I set the name of the variable to be CountOfFilledSlots and the expression maps to the name of the column that is to be returned (in the query, this is CountOfID).</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada13.png" alt="" /></p>
<p>&nbsp;</p>
<p>Save the data macro and give it a name. I called it <strong>GetRoomSlotCount</strong>. The data macro completes our requirement to find out how many records already exist for a specific slot and room. If we run this macro, it should return 0 or 1, which are good values since a room can either have a single assignment or doesn&#8217;t have an assignment at all. If it returns the value 2, we have a conflict that needs to be resolved. Now we need to enforce that only a single session can be assigned to a room and a slot utilizing this new data macro. Open the Sessions table in design view.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada14.png" alt="" /></p>
<p>&nbsp;</p>
<p>In the ribbon, there are three events that we can use to write macros against. Begin by clicking on On Insert – this will execute when new records are inserted into the table.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada15.png" alt="" /></p>
<p>&nbsp;</p>
<p>The first step in our logic is to execute the data macro that was created. The action needed is RunDataMacro and the macro to execute can be selected from a drop down. Once the macro is selected, it will provide the input and output parameters. They simply need to be mapped to the appropriate fields.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada16.png" alt="" /></p>
<p>&nbsp;</p>
<p>Notice that the output variable <strong>CountOfFilledSlots </strong>automatically requests to set a local variable. Just provide it a name that is meaningful so it can be used in the next step. The next step will be an <strong>If</strong> action. This evaluates a simple logical expression for a true or false evaluation. This macro will execute immediately after the record is updated or inserted into the table. This means, if we have a conflict the FilledSlots varialble will have a value greater than one.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada17.png" alt="" /></p>
<p>&nbsp;</p>
<p>Now, we need to take an action if the condition is satisfied. We want to raise an error which will cause a validation failure and will rollback the requested update or insert.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada18.png" alt="" /></p>
<p>&nbsp;</p>
<p>This completes the logic for the insert. We can just copy the same logic to the On Update event. In the application running in SharePoint, it&#8217;s time to create a record that will cause a conflict:</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada19.png" alt="" /></p>
<p>&nbsp;</p>
<p>When the application detects a conflict when running in SharePoint 2013, the user receives an error message and has a chance to change the data to try again.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada20.png" alt="" /></p>
<p>&nbsp;</p>
<p>Clicking OK allows the record to be corrected.</p>
<p><img src="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada21.png" alt="" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2014/03/26/validating-data-with-data-macros-in-access-services-2013/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">819</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada1.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada2.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada3.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada4.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada5.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada6.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada7.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada8.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032714_0100_creatingada1.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada10.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada11.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada12.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada13.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada14.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada15.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada16.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada17.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada18.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada19.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada20.png" medium="image" />

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2014/03/032614_2343_creatingada21.png" medium="image" />
	</item>
		<item>
		<title>SQL Report Viewer Web Part Issue</title>
		<link>https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/</link>
					<comments>https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/#respond</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Fri, 23 Aug 2013 19:30:02 +0000</pubDate>
				<category><![CDATA[SharePoint 2013]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=788</guid>

					<description><![CDATA[Recently, I was working with a colleague in a SharePoint 2013 On-Primesis environment to troubleshoot an issue when attempting to use the SQL Report View Web Part and a Content By Search or Search Results Web Part on the same page. To our surprise, the report viewer web part would not work with both web &#8230; <a href="https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/" class="more-link">Continue reading <span class="screen-reader-text">SQL Report Viewer Web Part&#160;Issue</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Recently, I was working with a colleague in a SharePoint 2013 On-Primesis environment to troubleshoot an issue when attempting to use the SQL Report View Web Part and a Content By Search or Search Results Web Part on the same page. To our surprise, the report viewer web part would not work with both web parts on the page.<br />
<span id="more-788"></span>The page made use of the query string to pass additional context for the Search Results Web Part and the SQL Report Viewer Web Part. When the SQL Report Viewer Web Part was placed on the page by itself, everything worked correctly. However, when either of the search web parts were placed on the page and configured, the report would stop loading. Upon opening developer tools for IE10, the following error was displayed:</p>
<p><span style="color:#ff0000;">SCRIPT5022: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.</span><br />
<span style="color:#ff0000;"> Details: Error parsing near &#8216;ebPartEditing|false|&lt;script type=&#8217;text/j&#8217;.</span></p>
<p>This appears to be related to the ASP.Net Update Panel. After a little digging in the web part configuration, we found a way to correct the issue.</p>
<p>Begin by opening the search web part and click on configure query.</p>
<p><img loading="lazy" data-attachment-id="789" data-permalink="https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/contentbysearch-webpart-sqlreportviewer/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png" data-orig-size="280,574" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="contentbysearch-webpart-sqlreportviewer" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png?w=146" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png?w=280" class="alignnone size-full wp-image-789" alt="contentbysearch-webpart-sqlreportviewer" src="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png" width="280" height="574" srcset="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png 280w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png?w=73&amp;h=150 73w" sizes="(max-width: 280px) 100vw, 280px" /></p>
<p>The Build Your Query dialog will appear. Go to the Settings page and change the loading behavior to async.</p>
<p><img loading="lazy" data-attachment-id="790" data-permalink="https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/sqlreportviewer-searchsettings/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png" data-orig-size="771,613" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="sqlreportviewer-searchsettings" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=700" class="alignnone size-full wp-image-790" alt="sqlreportviewer-searchsettings" src="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png" width="611" height="485" srcset="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=611&amp;h=486 611w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=150&amp;h=119 150w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=300&amp;h=239 300w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png?w=768&amp;h=611 768w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png 771w" sizes="(max-width: 611px) 100vw, 611px" /></p>
<p>After this change was made, the sql report viewer web part began working properly again.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2013/08/23/sql-report-viewer-web-part-issue/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">788</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2013/08/contentbysearch-webpart-sqlreportviewer.png" medium="image">
			<media:title type="html">contentbysearch-webpart-sqlreportviewer</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2013/08/sqlreportviewer-searchsettings.png" medium="image">
			<media:title type="html">sqlreportviewer-searchsettings</media:title>
		</media:content>
	</item>
		<item>
		<title>Using jQuery DataTables with Search Display Templates</title>
		<link>https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/</link>
					<comments>https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/#comments</comments>
		
		<dc:creator><![CDATA[Chris Quick]]></dc:creator>
		<pubDate>Tue, 20 Aug 2013 13:30:46 +0000</pubDate>
				<category><![CDATA[Display Templates]]></category>
		<category><![CDATA[Search]]></category>
		<category><![CDATA[SharePoint 2013]]></category>
		<category><![CDATA[DataTables]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[SharePoint]]></category>
		<category><![CDATA[SharePoint 2013 Search]]></category>
		<guid isPermaLink="false">http://devspoint.wordpress.com/?p=766</guid>

					<description><![CDATA[The new display templates in SharePoint 2013 allow for some great flexibility for designers and web developers in presenting search results in almost any format desired. For a proof of concept, a client wanted to aggregate tasks from across SharePoint. The company manages multiple assets and would like to roll up all tasks for all &#8230; <a href="https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/" class="more-link">Continue reading <span class="screen-reader-text">Using jQuery DataTables with Search Display&#160;Templates</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>The new display templates in SharePoint 2013 allow for some great flexibility for designers and web developers in presenting search results in almost any format desired. For a proof of concept, a client wanted to aggregate tasks from across SharePoint. The company manages multiple assets and would like to roll up all tasks for all users across all locations in SharePoint in a single location. We were able to accomplish this goal fairly easily thanks to the new search display templates available in SharePoint 2013. This project also makes use of jQuery and DataTables.</p>
<p><span id="more-766"></span></p>
<h3>Overview</h3>
<p>The solution will be a new search results page was created in the search center. The page search results web part will have a predefined search query focused on returning tasks, something like <em>ContentType:Task</em> will be a good starting point.</p>
<p>SharePoint 2013 search results are made up of control templates and item templates (there are others, but this will be the focus for this post). The control template is used to render the outer DOM elements for the search results, and this is a great place to initialize jQuery controls, like DataTables. The item template is used to render individual search results. Since the goal for the client is to render a list of tasks in a tabular format, the control template should create a standard HTML table. The item template will create the individual table rows from the returned search results.</p>
<h3>Control Template</h3>
<p>DataTables <a href="http://datatables.net/usage/" target="_blank">prerequisites</a> are table that includes a <strong>thead</strong> and a <strong>tbody</strong>. To keep things simple, the table will initially only include the title of the task and the source site. The markup should appear something like the following:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Source Site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;!-- ROWS HERE --&gt;&lt;/tbody&gt;
&lt;/table&gt;
</pre>
<p>This creates the necessary markup required by DataTables. The search results should simply be the individual rows, something like:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;tr&gt;&lt;td&gt;&lt;!-- TITLE --&gt;&lt;/td&gt;&lt;td&gt;&lt;!-- SITE NAME AND LINK --&gt;&lt;/td&gt;&lt;/tr&gt;
</pre>
<p>Starting with an existing control makes it easy to get the necessary initial markup. I chose to start with the <em>Control_List.html</em> located in the <em>control templates/content web parts</em> folder of the master page gallery.</p>
<pre class="brush: xml; highlight: [3,9,25,38,43,44,45,46,47,48,49,50,51,52,53]; title: ; notranslate">
&lt;html xmlns:mso=&quot;urn:schemas-microsoft-com:office:office&quot; xmlns:msdt=&quot;uuid:C2F41010-65B3-11d1-A29F-00AA00C14882&quot;&gt;
&lt;head&gt;

&lt;title&gt;Tasks Data Table&lt;/title&gt;

&lt;!--[if gte mso 9]&gt;&lt;xml&gt;
&lt;mso:CustomDocumentProperties&gt;
&lt;mso:TemplateHidden msdt:dt=&quot;string&quot;&gt;0&lt;/mso:TemplateHidden&gt;
&lt;mso:MasterPageDescription msdt:dt=&quot;string&quot;&gt;This template uses jQuery DataTables to display task information.&lt;/mso:MasterPageDescription&gt;
&lt;mso:ContentTypeId msdt:dt=&quot;string&quot;&gt;0x0101002039C03B61C64EC4A04F5361F385106601&lt;/mso:ContentTypeId&gt;
&lt;mso:TargetControlType msdt:dt=&quot;string&quot;&gt;;#Content Web Parts;#&lt;/mso:TargetControlType&gt;
&lt;mso:HtmlDesignAssociated msdt:dt=&quot;string&quot;&gt;1&lt;/mso:HtmlDesignAssociated&gt;
&lt;mso:CrawlerXSLFile msdt:dt=&quot;string&quot;&gt;&lt;/mso:CrawlerXSLFile&gt;
&lt;mso:HtmlDesignPreviewUrl msdt:dt=&quot;string&quot;&gt;&lt;/mso:HtmlDesignPreviewUrl&gt;
&lt;mso:HtmlDesignConversionSucceeded msdt:dt=&quot;string&quot;&gt;True&lt;/mso:HtmlDesignConversionSucceeded&gt;
&lt;mso:HtmlDesignStatusAndPreview msdt:dt=&quot;string&quot;&gt;http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Control_TaskTable.html, Conversion successful.&lt;/mso:HtmlDesignStatusAndPreview&gt;
&lt;/mso:CustomDocumentProperties&gt;
&lt;/xml&gt;&lt;![endif]--&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;script&gt;
    &lt;/script&gt;

    &lt;div id=&quot;Control_TaskDataTable&quot;&gt;

&lt;!--#_
if (!$isNull(ctx.ClientControl) &amp;&amp;
    !$isNull(ctx.ClientControl.shouldRenderControl) &amp;&amp;
    !ctx.ClientControl.shouldRenderControl())
{
    return &quot;&quot;;
}
ctx.ListDataJSONGroupsKey = &quot;ResultTables&quot;;
ctx[&quot;CurrentItems&quot;] = ctx.ListData.ResultTables[0].ResultRows;
var $noResults = Srch.ContentBySearch.getControlTemplateEncodedNoResultsMessage(ctx.ClientControl);

var encodedID = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + &quot;_Table_&quot;);

var noResultsClassName = &quot;ms-srch-result-noResults&quot;;
_#--&gt;

&lt;table class=&quot;artis-tasktable&quot; id=&quot;_#= encodedID =#_&quot;&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;Task&lt;/th&gt;
			&lt;th&gt;Source Site&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		_#= ctx.RenderItems(ctx) =#_
	&lt;/tbody&gt;
&lt;/table&gt;	

    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p><strong>Line 3: </strong> This changes the display name of the template as it appears in the content by search web part.</p>
<p><strong>Line 9: </strong> Provides a clearer description of the display template.</p>
<p><strong>Line 25: </strong> Changes the ID of the control.</p>
<p><strong>Line 38: </strong> Changes the emitted control ID.</p>
<p><strong>Lines 43-53: </strong> The table that will contain search results.</p>
<p>DataTables needs to be initialized after the content is put into the table. In a normal project, you might simply start with the standard jQuery <strong>$(document).ready(&#8230;)</strong>, but this will not work with display templates. However, exploring how the Control_Slideshow.html handles this, this behavior can be handled.</p>
<pre class="brush: jscript; title: ; notranslate">
ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {
	$(&quot;#&quot; + encodedID).dataTable(
	  {
           &quot;bPaginate&quot;: true,
           &quot;bLengthChange&quot;: false,
           &quot;bFilter&quot;: false,
           &quot;bSort&quot;: false,
           &quot;bInfo&quot;: false,
           &quot;bAutoWidth&quot;: false,
	       &quot;bJQueryUI&quot;: true
          }
        );
});
</pre>
<p>ctx.OnPostRender allows us to hook into the execution lifecycle to add additional methods to execute once the control has finished rendering. This is a good place to hook in and initialize DataTables. However, we still need to include the script references or we will wind up with an error message instead of a working display template. In the script above between lines 21-23 we have a place to register scripts. Just simply include the necessary scripts and CSS:</p>
<pre class="brush: jscript; title: ; notranslate">
&lt;script type=&quot;text/javascript&quot;&gt;
        $includeScript(this.url,&quot;~sitecollection/style library/js/jquery-1.10.2.min.js&quot;);
        $includeScript(this.url,&quot;~sitecollection/style library/js/jquery.dataTables.min.js&quot;);
        $includeCSS(this.url,&quot;~sitecollection/style library/css/jquery.dataTables.css&quot;);
&lt;/script&gt;
</pre>
<p>In this example, all of the scripts and css are located in the style library of the current site collection. Using the ASP.Net placeholder, we get the full URL to the current site collection. This will include the necessary scripts to render DataTables successfully.</p>
<p>The final markup:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;html xmlns:mso=&quot;urn:schemas-microsoft-com:office:office&quot; xmlns:msdt=&quot;uuid:C2F41010-65B3-11d1-A29F-00AA00C14882&quot;&gt;
&lt;head&gt;
&lt;title&gt;Tasks Data Table&lt;/title&gt;

&lt;!--[if gte mso 9]&gt;&lt;xml&gt;
&lt;mso:CustomDocumentProperties&gt;
&lt;mso:TemplateHidden msdt:dt=&quot;string&quot;&gt;0&lt;/mso:TemplateHidden&gt;
&lt;mso:MasterPageDescription msdt:dt=&quot;string&quot;&gt;This template uses jQuery DataTables to display task information.&lt;/mso:MasterPageDescription&gt;
&lt;mso:ContentTypeId msdt:dt=&quot;string&quot;&gt;0x0101002039C03B61C64EC4A04F5361F385106601&lt;/mso:ContentTypeId&gt;
&lt;mso:TargetControlType msdt:dt=&quot;string&quot;&gt;;#Content Web Parts;#&lt;/mso:TargetControlType&gt;
&lt;mso:HtmlDesignAssociated msdt:dt=&quot;string&quot;&gt;1&lt;/mso:HtmlDesignAssociated&gt;
&lt;mso:CrawlerXSLFile msdt:dt=&quot;string&quot;&gt;&lt;/mso:CrawlerXSLFile&gt;
&lt;mso:HtmlDesignPreviewUrl msdt:dt=&quot;string&quot;&gt;&lt;/mso:HtmlDesignPreviewUrl&gt;
&lt;mso:HtmlDesignConversionSucceeded msdt:dt=&quot;string&quot;&gt;True&lt;/mso:HtmlDesignConversionSucceeded&gt;
&lt;mso:HtmlDesignStatusAndPreview msdt:dt=&quot;string&quot;&gt;http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Control_TaskTable.html, Conversion successful.&lt;/mso:HtmlDesignStatusAndPreview&gt;
&lt;/mso:CustomDocumentProperties&gt;
&lt;/xml&gt;&lt;![endif]--&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;script&gt;
        $includeScript(this.url,&quot;~sitecollection/style library/js/jquery-1.10.2.min.js&quot;);
        $includeScript(this.url,&quot;~sitecollection/style library/js/jquery.dataTables.min.js&quot;);
        $includeCSS(this.url,&quot;~sitecollection/style library/css/jquery.dataTables.css&quot;);
    &lt;/script&gt;

    &lt;div id=&quot;Control_TaskDataTable&quot;&gt;

&lt;!--#_
if (!$isNull(ctx.ClientControl) &amp;&amp;
    !$isNull(ctx.ClientControl.shouldRenderControl) &amp;&amp;
    !ctx.ClientControl.shouldRenderControl())
{
    return &quot;&quot;;
}
ctx.ListDataJSONGroupsKey = &quot;ResultTables&quot;;
ctx[&quot;CurrentItems&quot;] = ctx.ListData.ResultTables[0].ResultRows;
var $noResults = Srch.ContentBySearch.getControlTemplateEncodedNoResultsMessage(ctx.ClientControl);

var encodedID = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + &quot;_Table_&quot;);

var noResultsClassName = &quot;ms-srch-result-noResults&quot;;
ctx.OnPostRender = [];

ctx.OnPostRender.push(function () {
	$(&quot;#&quot; + encodedID).dataTable(
	{
		&quot;bPaginate&quot;: true,
        &quot;bLengthChange&quot;: false,
        &quot;bFilter&quot;: false,
        &quot;bSort&quot;: false,
        &quot;bInfo&quot;: false,
        &quot;bAutoWidth&quot;: false,
		&quot;bJQueryUI&quot;: true
     }
     );
});
_#--&gt;

&lt;table class=&quot;artis-tasktable&quot; id=&quot;_#= encodedID =#_&quot;&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;Task&lt;/th&gt;
			&lt;th&gt;Source Site&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		_#= ctx.RenderItems(ctx) =#_
	&lt;/tbody&gt;
&lt;/table&gt;	

    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<h3>Item Template</h3>
<p>Now attention is turned on the item template for the individual results. The item template will need to include managed properties to include in the final rendered result. Managed properties are defined either by a search administrator or by a site collection administrator.</p>
<p>Since we are keeping this simple, the item template will only include the Title of the task, a link to the task (available from the built in Title managed property), the title of the site (from the Site managed property) and a link to the site (from the SPSiteUrl managed property). The final template should look something like:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;tr&gt;
	&lt;td&gt;
		&lt;a href=&quot;TaskURL&quot;&gt;Task Title&lt;/a&gt;
	&lt;/td&gt;
	&lt;td&gt;
		&lt;a href=&quot;SiteURL&quot;&gt;Site&lt;/a&gt;
	&lt;/td&gt;
&lt;/tr&gt;
</pre>
<p>Again, I simply borrowed from an existing template to get started (<em>Item_Default.html</em>) to build the control. This gives me many of the managed properties I will need.</p>
<p>You will notice in the final markup that managed properties are mapped in a format similar to &#8216;Property'{Property}:&#8217;Property&#8217;. On MSDN, it is stated, &#8220;The property is a comma-delimited list of values that uses the following format: &#8216;property display name'{property name}:&#8217;managed property&#8217;.&#8221; The property display name is what will be displayed in the editing pane while the web part is being customized. The property name maps to a localized resource that is used for property binding. The managed property is a list of managed properties to use for the value.</p>
<pre class="brush: xml; highlight: [3,8,25,40,41,42,43]; title: ; notranslate">
&lt;html xmlns:mso=&quot;urn:schemas-microsoft-com:office:office&quot; xmlns:msdt=&quot;uuid:C2F41010-65B3-11d1-A29F-00AA00C14882&quot;&gt;
&lt;head&gt;
&lt;title&gt;Data Table Task Row&lt;/title&gt;

&lt;!--[if gte mso 9]&gt;&lt;xml&gt;
&lt;mso:CustomDocumentProperties&gt;
&lt;mso:TemplateHidden msdt:dt=&quot;string&quot;&gt;0&lt;/mso:TemplateHidden&gt;
&lt;mso:ManagedPropertyMapping msdt:dt=&quot;string&quot;&gt;'Link URL'{Link URL}:'Path','Task Title'{Task Title}:'Title','SecondaryFileExtension','ContentTypeId','ListItemID','SiteTitle','SitePath','Site','SPSiteURL'&lt;/mso:ManagedPropertyMapping&gt;
&lt;mso:MasterPageDescription msdt:dt=&quot;string&quot;&gt;This Item Display Template will show a single task on a single row. This must be used with the Tasks Data Table control.&lt;/mso:MasterPageDescription&gt;
&lt;mso:ContentTypeId msdt:dt=&quot;string&quot;&gt;0x0101002039C03B61C64EC4A04F5361F385106603&lt;/mso:ContentTypeId&gt;
&lt;mso:TargetControlType msdt:dt=&quot;string&quot;&gt;;#Content Web Parts;#&lt;/mso:TargetControlType&gt;
&lt;mso:HtmlDesignAssociated msdt:dt=&quot;string&quot;&gt;1&lt;/mso:HtmlDesignAssociated&gt;
&lt;mso:CrawlerXSLFile msdt:dt=&quot;string&quot;&gt;&lt;/mso:CrawlerXSLFile&gt;
&lt;mso:HtmlDesignPreviewUrl msdt:dt=&quot;string&quot;&gt;&lt;/mso:HtmlDesignPreviewUrl&gt;
&lt;mso:HtmlDesignStatusAndPreview msdt:dt=&quot;string&quot;&gt;http://connect.qrinc.com/poc/aip/_catalogs/masterpage/Display Templates/Content Web Parts/Item_TaskTableRow.html, Conversion successful.&lt;/mso:HtmlDesignStatusAndPreview&gt;
&lt;mso:HtmlDesignConversionSucceeded msdt:dt=&quot;string&quot;&gt;True&lt;/mso:HtmlDesignConversionSucceeded&gt;
&lt;/mso:CustomDocumentProperties&gt;
&lt;/xml&gt;&lt;![endif]--&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;script&gt;
    &lt;/script&gt;

    &lt;div id=&quot;Item_DataTaskRow&quot;&gt;
&lt;!--#_
var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + &quot;_row_&quot;);

var linkURL = $getItemValue(ctx, &quot;Link URL&quot;);
linkURL.overrideValueRenderer($urlHtmlEncode);

var taskTitle = $getItemValue(ctx, &quot;Task Title&quot;);
var siteTitle = $getItemValue(ctx, &quot;SiteTitle&quot;);

var linkId = encodedId + &quot;link&quot;;
var titleId = encodedId + &quot;title&quot;;
var siteId = encodedId + &quot;site&quot;;

_#--&gt;
		&lt;tr id=&quot;_#= encodedId =#_&quot; data-listitemid=&quot;_#= ctx.CurrentItem.ListItemID =#_&quot;&gt;
			&lt;td&gt;&lt;a href=&quot;_#= linkURL =#_&quot; id=&quot;_#= linkId =#_&quot; data-role=&quot;modalOpen&quot;&gt;_#= taskTitle =#_&lt;/a&gt;&lt;/td&gt;
			&lt;td&gt;&lt;a href=&quot;_#= ctx.CurrentItem.SPSiteURL =#_&quot;&gt;_#= siteTitle =#_&lt;/a&gt;&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

</pre>
<p><strong>Line 3: </strong> Changes the display name.</p>
<p><strong>Line 8: </strong> Set the managed properties to include.</p>
<p><strong>Line 25: </strong> Changes the ID of the control.</p>
<p><strong>Line 40-43: </strong> The individual search results will be rendered with the template.</p>
<p>This will provide the final markup required. On the original search page, the content by search web part is changed to utilize the new control and item templates.</p>
<p><img loading="lazy" data-attachment-id="781" data-permalink="https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/displaytemplates-datatables-search/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png" data-orig-size="235,147" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="DisplayTemplates-DataTables-Search" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png?w=235" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png?w=235" class="size-full wp-image-781 alignnone" src="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png" alt="DisplayTemplates-DataTables-Search" width="235" height="147" srcset="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png 235w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png?w=150&amp;h=94 150w" sizes="(max-width: 235px) 100vw, 235px" /></p>
<p>And the control is finally rendered and enhanced with DataTables:</p>
<p><img loading="lazy" data-attachment-id="782" data-permalink="https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/displaytemplates-datatables-rendered/" data-orig-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png" data-orig-size="534,160" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}" data-image-title="DisplayTemplates-DataTables-Rendered" data-image-description="" data-image-caption="" data-medium-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png?w=300" data-large-file="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png?w=534" class="alignnone size-full wp-image-782" src="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png" alt="DisplayTemplates-DataTables-Rendered" width="534" height="160" srcset="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png 534w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png?w=150&amp;h=45 150w, https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png?w=300&amp;h=90 300w" sizes="(max-width: 534px) 100vw, 534px" /></p>
<h3>More information:</h3>
<ul>
<li><a href="http://msdn.microsoft.com/library/jj945138.aspx" target="_blank">MSDN: SharePoint 2013 Design Manager display templates</a></li>
<li><a href="http://borderingdotnet.blogspot.com/2013/03/the-anatomy-of-sharepoint-2013-display.html" target="_blank">The Anatomy of SharePoint 2013 Display Templates</a></li>
</ul>
<h3>Be an over-achiever</h3>
<ul>
<li><a href="http://en.share-gate.com/blog/image-slider-with-sharepoint-2013-search-results" target="_blank">Build an Image Slider with SharePoint 2013 Search Results Web Part</a></li>
</ul>
<div style="font-size:.95em;"><strong style="color:red;">Edit 8/20/2013 @ 3:48 PM:</strong> Fixed the code regions of the post.</div>
<div style="font-size:.95em;"><strong style="color:red;">Update 6/12/2014:</strong> It appears a reader, Peter, has found a solution to fix the issue where the script hasn&#8217;t fully loaded prior to the initialize function getting called. Check out his comment <a href="#comment-849">here</a>.</div>
]]></content:encoded>
					
					<wfw:commentRss>https://devspoint.wordpress.com/2013/08/20/using-jquery-datatables-with-search-display-templates/feed/</wfw:commentRss>
			<slash:comments>27</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">766</post-id>
		<media:content url="https://1.gravatar.com/avatar/a8520ad3f093c6fa56fa0ab84c18993ea4ccdbf110b4e7f45058af98823b546d?s=96&#38;d=&#38;r=G" medium="image">
			<media:title type="html">cquick001</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-search.png" medium="image">
			<media:title type="html">DisplayTemplates-DataTables-Search</media:title>
		</media:content>

		<media:content url="https://devspoint.wordpress.com/wp-content/uploads/2013/08/displaytemplates-datatables-rendered.png" medium="image">
			<media:title type="html">DisplayTemplates-DataTables-Rendered</media:title>
		</media:content>
	</item>
	</channel>
</rss>
