<?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/"
	>

<channel>
	<title>Backoffice extension Archives - Blog IT</title>
	<atom:link href="https://blogit.create.pt/tag/backoffice-extension/feed/" rel="self" type="application/rss+xml" />
	<link>https://blogit.create.pt/tag/backoffice-extension/</link>
	<description>Create IT blogger community</description>
	<lastBuildDate>Thu, 10 Jan 2019 12:46:22 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>
	<item>
		<title>KendoUI Grid, OData, WebAPI and Entity Framework</title>
		<link>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/</link>
					<comments>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Wed, 05 Jul 2017 10:42:53 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[entity framework]]></category>
		<category><![CDATA[grid]]></category>
		<category><![CDATA[kendoui]]></category>
		<category><![CDATA[odata]]></category>
		<category><![CDATA[sql database]]></category>
		<category><![CDATA[webapi]]></category>
		<category><![CDATA[webapp]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1764</guid>

					<description><![CDATA[<p>In the latest project I was involved with, there was a need to present database tables data directly in the Umbraco&#8217;s backoffice, where all CRUD operations must be supported. This tables could be something like 1000 to 1 million rows and both the website and database would be hosted in Azure, in a WebApp and [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/">KendoUI Grid, OData, WebAPI and Entity Framework</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">I</span>n the latest project I was involved with, there was a need to present database tables data directly in the Umbraco&#8217;s backoffice, where all CRUD operations must be supported. This tables could be something like 1000 to 1 million rows and both the website and database would be hosted in Azure, in a WebApp and SQL Database respectively. Users should be able to create complex filters and order the data as they wish as well as update the data individually and in batch.</p>
<p>From the get-go there were some challenges we needed to overcome in order to create an usable system, that was user-friendly, fast, reliable, and where no conflitcts would come up when data was being updated from multiple users.</p>
<p>The solution was a new Umbraco Package we called <em>Search Manager</em> (since the database tables ultimately feed an Azure Search index) that uses <a href="http://demos.telerik.com/kendo-ui/grid/index">Telerik&#8217;s Kendo UI Grid</a>, WebAPI and Entity Framework. The following presents the process it took to reach the end result.</p>
<p><span id="more-6483"></span></p>
<h2>Step 1</h2>
<p>We created a simple Umbraco Package that implemented a Kendo UI Grid which got its data from a WebAPI method that returned all rows from a table using Petapoco.</p>
<p>This allowed us to have the complete table data in memory using a Kendo UI Grid. It worked great but had major problems:</p>
<ul>
<li>A table with around 40k rows was actually a 40 MBs json download in order to populate the grid</li>
<li>Users could be working with outdated data, since it was loaded in memory and changes could have been made to the source</li>
</ul>
<h2>Step 2</h2>
<p>We started using the server paging feature of the Kendo Grid. This dropped the download size greatly, but we still had problems:</p>
<ul>
<li>Even though we are working with only a subset of results client-side, we were still getting the complete table data server-side, using petapoco.</li>
</ul>
<p>We needed an easy way to translate the client-side queries, such as filters, paging, order, etc to SQL queries.</p>
<h2>Step 3</h2>
<p>We changed the type of queries being generated by the Grid to OData. Using the <a href="https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData">OData WebAPI nuget package</a> we are able to map the odata query  to a C# object and then directly apply the query to any IQueryable data.</p>
<p>Petapoco does not provide any IQueryable data support, so we changed our ORM to Entity Framework, which does, and <em>voilá</em>!</p>
<h2>Finally</h2>
<p>With everything in place, we are able to have a filtered subset of results in a Grid, that gets exactly what it needs to display from a dynamically generated SQL query.</p>
<ul>
<li>It is fast because we only get exactly what we need:</li>
</ul>
<p>An OData query generated by the Kendo UI Grid looks like this:</p>
<pre class="brush: plain; title: ; notranslate">
http://localhost:14231/umbraco/backoffice/SearchManager/PricesApi/Read?%24inlinecount=allpages&%24top=20&%24skip=40&%24orderby=ProviderName+desc&%24filter=(NatureProcCode+eq+%27exames%27+and+AvgAmtClaimed+gt+10)
</pre>
<p>Which is translated to the follwing SQL query:</p>
<pre class="brush: sql; title: ; notranslate">
exec sp_executesql N'SELECT 
    &#x5B;Project1].&#x5B;Approved] AS &#x5B;Approved], 
    &#x5B;Project1].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
    &#x5B;Project1].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
    &#x5B;Project1].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
    &#x5B;Project1].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
    &#x5B;Project1].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
    &#x5B;Project1].&#x5B;Network] AS &#x5B;Network], 
    &#x5B;Project1].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
    &#x5B;Project1].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
    &#x5B;Project1].&#x5B;IsImported] AS &#x5B;IsImported], 
    &#x5B;Project1].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
    &#x5B;Project1].&#x5B;AmtContract] AS &#x5B;AmtContract], 
    &#x5B;Project1].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
    &#x5B;Project1].&#x5B;ProviderName] AS &#x5B;ProviderName], 
    &#x5B;Project1].&#x5B;ClinicName] AS &#x5B;ClinicName]
    FROM ( SELECT 
        &#x5B;Extent1].&#x5B;Approved] AS &#x5B;Approved], 
        &#x5B;Extent1].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
        &#x5B;Extent1].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
        &#x5B;Extent1].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
        &#x5B;Extent1].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
        &#x5B;Extent1].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
        &#x5B;Extent1].&#x5B;Network] AS &#x5B;Network], 
        &#x5B;Extent1].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
        &#x5B;Extent1].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
        &#x5B;Extent1].&#x5B;IsImported] AS &#x5B;IsImported], 
        &#x5B;Extent1].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
        &#x5B;Extent1].&#x5B;AmtContract] AS &#x5B;AmtContract], 
        &#x5B;Extent1].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
        &#x5B;Extent1].&#x5B;ProviderName] AS &#x5B;ProviderName], 
        &#x5B;Extent1].&#x5B;ClinicName] AS &#x5B;ClinicName]
        FROM (SELECT 
    &#x5B;PriceGrid].&#x5B;Approved] AS &#x5B;Approved], 
    &#x5B;PriceGrid].&#x5B;DiffWeightedRate] AS &#x5B;DiffWeightedRate], 
    &#x5B;PriceGrid].&#x5B;SubmitPrvId] AS &#x5B;SubmitPrvId], 
    &#x5B;PriceGrid].&#x5B;PracticeSeq] AS &#x5B;PracticeSeq], 
    &#x5B;PriceGrid].&#x5B;NatureProcCode] AS &#x5B;NatureProcCode], 
    &#x5B;PriceGrid].&#x5B;GroupProcCode] AS &#x5B;GroupProcCode], 
    &#x5B;PriceGrid].&#x5B;Network] AS &#x5B;Network], 
    &#x5B;PriceGrid].&#x5B;AmtClaimedWeighted] AS &#x5B;AmtClaimedWeighted], 
    &#x5B;PriceGrid].&#x5B;ManualPrice] AS &#x5B;ManualPrice], 
    &#x5B;PriceGrid].&#x5B;IsImported] AS &#x5B;IsImported], 
    &#x5B;PriceGrid].&#x5B;DiffCalculatedRate] AS &#x5B;DiffCalculatedRate], 
    &#x5B;PriceGrid].&#x5B;AmtContract] AS &#x5B;AmtContract], 
    &#x5B;PriceGrid].&#x5B;AvgAmtClaimed] AS &#x5B;AvgAmtClaimed], 
    &#x5B;PriceGrid].&#x5B;ProviderName] AS &#x5B;ProviderName], 
    &#x5B;PriceGrid].&#x5B;ClinicName] AS &#x5B;ClinicName]
    FROM &#x5B;dbo].&#x5B;PriceGrid] AS &#x5B;PriceGrid]) AS &#x5B;Extent1]
        WHERE (&#x5B;Extent1].&#x5B;NatureProcCode] = @p__linq__0) AND (&#x5B;Extent1].&#x5B;AvgAmtClaimed] &gt; @p__linq__1)
    )  AS &#x5B;Project1]
    ORDER BY &#x5B;Project1].&#x5B;ProviderName] DESC, &#x5B;Project1].&#x5B;AmtClaimedWeighted] ASC, &#x5B;Project1].&#x5B;AmtContract] ASC, &#x5B;Project1].&#x5B;Approved] ASC, &#x5B;Project1].&#x5B;AvgAmtClaimed] ASC, &#x5B;Project1].&#x5B;ClinicName] ASC, &#x5B;Project1].&#x5B;DiffCalculatedRate] ASC, &#x5B;Project1].&#x5B;DiffWeightedRate] ASC, &#x5B;Project1].&#x5B;GroupProcCode] ASC, &#x5B;Project1].&#x5B;IsImported] ASC, &#x5B;Project1].&#x5B;ManualPrice] ASC, &#x5B;Project1].&#x5B;NatureProcCode] ASC, &#x5B;Project1].&#x5B;Network] ASC, &#x5B;Project1].&#x5B;PracticeSeq] ASC, &#x5B;Project1].&#x5B;SubmitPrvId] ASC
    OFFSET @p__linq__2 ROWS FETCH NEXT @p__linq__3 ROWS ONLY  option (Recompile)',N'@p__linq__0 varchar(8000),@p__linq__1 decimal(2,0),@p__linq__2 int,@p__linq__3 int',@p__linq__0='exames',@p__linq__1=10,@p__linq__2=40,@p__linq__3=20
</pre>
<ul>
<li>It&#8217;s user-friendly and reliable because users can filter the data as if they were using an excel file on steroids</li>
<li>It avoids conflicts because we only get a subset of the results each time and each change is commited in a single transaction</li>
</ul>
<h2>Code samples:</h2>
<p>&nbsp;</p>
<pre class="brush: jscript; title: grid setup; notranslate">
grid = $(&quot;#grid&quot;).kendoGrid({
                dataSource: new kendo.data.DataSource({
                    transport: transport,
                    schema: schema,
                    serverFiltering: true,
                    serverPaging: true,
                    serverSorting: true,
                    type: &quot;odata&quot;,
                    pageSize: 20
                }),
                dataBound: onDataBound,
                pageable: true,
                filterable: true,
                sortable: true,
                scrollable: false,
                navigatable: true,
                resizable: true,
                batch: true,
                columnMenu: true,
                editable: true,
                toolbar: &#x5B;
					{ template: '&lt;a class=&quot;k-button k-button-icontext k-grid-save-batch&quot; href=&quot;\\#&quot;&quot;&gt;&lt;span class=&quot;k-icon k-i-update&quot;&gt;&lt;/span&gt;Guardar Alterações&lt;/a&gt;' },
					{ name: &quot;cancel&quot;, text: &quot;Cancelar&quot; },
                ],
                columns: &#x5B;...]
            });
</pre>
<p>&nbsp;</p>
<pre class="brush: csharp; title: WebAPI; notranslate">
&#x5B;HttpGet]
        public IHttpActionResult Read(ODataQueryOptions&lt;PriceGrid&gt; opts)
        {
            Mapper.CreateMap&lt;PriceGrid, PriceGridBO&gt;();

            using (var context = new EntitiesContext())
            {
                using (var qh = new HintScope(context, QueryHintType.Recompile))
                {
                    List&lt;PriceGrid&gt; results = opts
                        .ApplyTo(context.PriceGrids)
                        .Cast&lt;PriceGrid&gt;()
                        .ToList();

                    List&lt;PriceGridBO&gt; boEntities = results.Select(x =&gt; Mapper.Map&lt;PriceGrid, PriceGridBO&gt;(x)).ToList();

                    PriceResponse response = new PriceResponse()
                    {
                        Entities = boEntities,
                        Total = Request.ODataProperties().TotalCount.HasValue ? Request.ODataProperties().TotalCount.Value : 0
                    };

                    return Ok(response);
                }
            }
        }
</pre>
<p>The post <a href="https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/">KendoUI Grid, OData, WebAPI and Entity Framework</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2017/07/05/kendoui-grid-odata-webapi-and-entity-framework/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Automatic generation of Umbraco packages with Grunt</title>
		<link>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/</link>
					<comments>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 07 Mar 2016 16:03:50 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[automatization]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[grunt]]></category>
		<category><![CDATA[npm]]></category>
		<category><![CDATA[NPM Scripts Task Runner]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[visual studio]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=1161</guid>

					<description><![CDATA[<p>Manually creating Umbraco packages can be tiresome. If you&#8217;re continuously building upon the same package, doing it manually is wasting time that can be more useful developing new features. This problem presented itself to me when improving Approve It. In order to create the Umbraco package I need several things: The main assembly The new dashboard [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/">Automatic generation of Umbraco packages with Grunt</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Manually creating Umbraco packages can be tiresome.</p>
<p>If you&#8217;re continuously building upon the same package, doing it manually is wasting time that can be more useful developing new features.</p>
<p>This problem presented itself to me when improving <a href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">Approve It</a>. In order to create the Umbraco package I need several things:</p>
<ul>
<li>The main assembly</li>
<li>The new dashboard section html file</li>
<li>The App_Plugins folder which contains every angular controller and views, javascript frameworks and custom css</li>
<li>The <a href="http://packageactioncontrib.codeplex.com/" target="_blank" rel="noopener">Package Actions Contrib</a> assembly and its respective actions that enable me to bundle some translations in the package</li>
</ul>
<p>I decided to go with <a href="http://gruntjs.com/getting-started" target="_blank" rel="noopener">Grunt</a> and the <a href="https://www.npmjs.com/package/grunt-umbraco-package" target="_blank" rel="noopener">Grunt Umbraco Package</a> task. A grunt file is a script that typically automates the process of executing some tasks. These scripts can be executed on top of <a href="https://www.npmjs.com/" target="_blank" rel="noopener">NPM</a>, a package manager for javscript that provides a command line tool to run its packages. To streamline even more the package creation, I installed a Visual Studio extension called <a href="https://visualstudiogallery.msdn.microsoft.com/8f2f2cbc-4da5-43ba-9de2-c9d08ade4941" target="_blank" rel="noopener">NPM Scripts Task Runner</a>, that detects the grunt file and provides a simples UI to handle its tasks.</p>
<p><span id="more-6479"></span></p>
<h3>Step 1 &#8211; Install NPM</h3>
<p>With NPM installed we are able to execute a Grunt file.</p>
<h3>Step 2 &#8211; Install NPM Scripts Task Runner</h3>
<p>The Visual Studio extension:</p>
<figure id="attachment_1241" aria-describedby="caption-attachment-1241" style="width: 471px" class="wp-caption alignnone"><a href="http://blogit-create.com/wp-content/uploads/2016/03/npmscriptsvs.png" rel="attachment wp-att-1241"><img fetchpriority="high" decoding="async" class="wp-image-1241 size-full" src="http://blogit-create.com/wp-content/uploads/2016/03/npmscriptsvs.png" alt="NPM Scripts Task Runner Visual Studio extension" width="471" height="235" /></a><figcaption id="caption-attachment-1241" class="wp-caption-text">NPM Scripts Task Runner Visual Studio extension</figcaption></figure>
<h3>Step 3 &#8211; Create a Grunt file</h3>
<p>And place it in your project:</p>
<pre class="brush: jscript; title: Gruntfile.js; notranslate">
module.exports = function (grunt) {

    // Setup
    var pkg = grunt.file.readJSON('package.json');
    var projectRoot = 'C:/path/to/project/ApproveIt/';
    var packageNamespace = &quot;Create.Plugin&quot;;

    // Grunt Configuration
    grunt.initConfig({
        pkg: pkg,
        clean: {
            files: &#x5B;
                'bld/App_Plugins',
                'bld/bin',
                'bld/Umbraco'
            ]
        },
        copy: {
            release: {
                files: &#x5B;
                    {
                        expand: true,
                        cwd: projectRoot + 'bin/',
                        src: &#x5B;
                            packageNamespace + '.' + pkg.name + '.dll',
                            'PackageActionsContrib.dll'
                        ],
                        dest: 'bld/bin/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'App_Plugins/',
                        src: &#x5B;'**'],
                        dest: 'bld/App_Plugins/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'Dashboard/Views/dashboard/approveIt/',
                        src: &#x5B;'approveItdashboardintro.html'],
                        dest: &quot;bld/Umbraco/Views/dashboard/approveIt/&quot;
                    }
                ]
            }
        },
        umbracoPackage: {
            release: {
                src: 'bld/',
                dest: 'bin/umbraco',
                options: {
                    name: pkg.name,
                    version: pkg.version,
                    url: pkg.url,
                    license: pkg.license.name,
                    licenseUrl: pkg.license.url,
                    author: pkg.author.name,
                    authorUrl: pkg.author.url,
                    readme: pkg.readme,
                    outputName: pkg.name + '.v' + pkg.version + '.zip',
                    manifest: 'package.xml'
                }
            }
        }
    });

    // Loading of Grunt Tasks
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-umbraco-package');
};
</pre>
<p>This file has three sections, which I&#8217;ll explain in a different order:</p>
<ol>
<li>Setup</li>
<li>Grunt configuration</li>
<li>Loading of grunt tasks</li>
</ol>
<h4>Loading of Grunt Tasks</h4>
<p>Most actions we need a grunt file to execute are simplified by the use of existing Grunt tasks. In my case, I only need 3:</p>
<ol>
<li><em>grunt-contrib-clean</em>: deletes files and folders</li>
<li><em>grunt-contrib-copy</em>: copies files and folders</li>
<li><em>grunt-umbraco-package</em>: generates an umbraco package</li>
</ol>
<h4>Grunt File Setup</h4>
<p>Here I define a set of variables and load an external configuration file that contains project properties I use later on:</p>
<pre class="brush: jscript; title: package.json; notranslate">
{
  &quot;author&quot;: {
    &quot;name&quot;: &quot;André Santos&quot;,
    &quot;url&quot;: &quot;http://www.create.pt&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;grunt&quot;: &quot;~0.4.5&quot;,
    &quot;grunt-contrib-clean&quot;: &quot;^0.6.0&quot;,
    &quot;grunt-contrib-copy&quot;: &quot;~0.4.1&quot;,
    &quot;grunt-umbraco-package&quot;: &quot;1.0.0&quot;
  },
  &quot;license&quot;: {
    &quot;name&quot;: &quot;MIT&quot;,
    &quot;url&quot;: &quot;http://opensource.org/licenses/MIT&quot;
  },
  &quot;name&quot;: &quot;ApproveIt&quot;,
  &quot;readme&quot;: &quot;Umbraco Plugin that creates a section that shows all the content that is waiting approval for publishing.&quot;,
  &quot;url&quot;: &quot;https://our.umbraco.org/projects/backoffice-extensions/approve-it/&quot;,
  &quot;version&quot;: &quot;1.0.1&quot;
}
</pre>
<h4>Grunt Configuration</h4>
<p>This section is where everything comes together. The <em>package.json</em> configuration file is loaded and the three aforementioned grunt tasks are implemented:</p>
<h5>Clean</h5>
<p>In here, the directories used as the source for the umbraco package creation, are configured so that when we can empty them at will</p>
<h5>Copy</h5>
<p>This task copies every file needed by the plugin to a centralized location, creating a snapshot of our plugin. We copy full directories and hand picked files as needed.</p>
<h5>Umbraco Package</h5>
<p>Finally, this task picks up every file we have copied and creates the final Umbraco package. It uses the configuration properties set in <em>package.json</em> and uses a custom <em>package.xml</em> file so that I can include my custom post umbraco package install actions (using Package Actions Contrib):</p>
<pre class="brush: xml; title: package.xml; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;
&lt;umbPackage&gt;
  &lt;info&gt;
    &lt;package&gt;
      &lt;name&gt;&lt;%= name %&gt;&lt;/name&gt;
      &lt;version&gt;&lt;%= version %&gt;&lt;/version&gt;
      &lt;license url=&quot;&lt;%= licenseUrl %&gt;&quot;&gt;&lt;%= license %&gt;&lt;/license&gt;
      &lt;url&gt;&lt;%= url %&gt;&lt;/url&gt;
      &lt;requirements&gt;
        &lt;major&gt;3&lt;/major&gt;
        &lt;minor&gt;0&lt;/minor&gt;
        &lt;patch&gt;0&lt;/patch&gt;
      &lt;/requirements&gt;
    &lt;/package&gt;
    &lt;author&gt;
      &lt;name&gt;&lt;%= author %&gt;&lt;/name&gt;
      &lt;website&gt;&lt;%= authorUrl %&gt;&lt;/website&gt;
    &lt;/author&gt;
    &lt;readme&gt;&lt;!&#x5B;CDATA&#x5B;&lt;%= readme %&gt;]]&gt;&lt;/readme&gt;
  &lt;/info&gt;
  &lt;DocumentTypes /&gt;
  &lt;Templates /&gt;
  &lt;Stylesheets /&gt;
  &lt;Macros /&gt;
  &lt;DictionaryItems /&gt;
  &lt;Languages /&gt;
  &lt;DataTypes /&gt;
  &lt;control /&gt;
  &lt;Actions&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;addDashboardSection&quot; dashboardAlias=&quot;StartupApproveItDashboardSection&quot;&gt;
      &lt;section&gt;
        &lt;areas&gt;
          &lt;area&gt;approveIt&lt;/area&gt;
        &lt;/areas&gt;
        &lt;tab caption=&quot;Get Started&quot;&gt;
          &lt;control showOnce=&quot;true&quot; addPanel=&quot;true&quot; panelCaption=&quot;&quot;&gt;
            views/dashboard/approveIt/approveItdashboardintro.html
          &lt;/control&gt;
        &lt;/tab&gt;
      &lt;/section&gt;
    &lt;/Action&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pt&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;cs&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;da&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en_us&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;es&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;fr&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;he&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;it&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ja&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ko&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;nl&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;no&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pl&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ru&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;sv&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;zh&quot; position=&quot;end&quot; area=&quot;sections&quot; key=&quot;approveIt&quot; value=&quot;Approve It&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pt&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Editador por&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;cs&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;da&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;en_us&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;es&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;fr&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;he&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;it&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ja&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ko&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;nl&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;no&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;pl&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;ru&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;sv&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
    &lt;Action runat=&quot;install&quot; undo=&quot;true&quot; alias=&quot;AddLanguageFileKey&quot; language=&quot;zh&quot; position=&quot;end&quot; area=&quot;general&quot; key=&quot;approveitupdatedBy&quot; value=&quot;Updated by&quot; /&gt;
  &lt;/Actions&gt;
  &lt;files&gt;
  	&lt;% files.forEach(function(file) { %&gt;
  	&lt;file&gt;
      &lt;guid&gt;&lt;%= file.guid %&gt;&lt;/guid&gt;
      &lt;orgPath&gt;&lt;%= file.dir %&gt;&lt;/orgPath&gt;
      &lt;orgName&gt;&lt;%= file.name %&gt;&lt;/orgName&gt;
    &lt;/file&gt;
	&lt;% }); %&gt;
  &lt;/files&gt;
&lt;/umbPackage&gt;
</pre>
<p>This file uses the Grunt templating engine so that, not only can I include the Package Action Contrib custom actions, but also some custom variables, such as:</p>
<ul>
<li>The mandatory list of files that is automatically updated when a new file is added to the project output</li>
<li>The <em>package.json</em> variables</li>
</ul>
<p>That&#8217;s it! We can now create new versions of Approve It without having to go into the Umbraco backoffice and doing it manually every time we need a new package version.</p>
<p><em>You can find the complete source code in my GitHub: <a href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>. You can download the Approve It package here: <a href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/">Automatic generation of Umbraco packages with Grunt</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2016/03/07/automatic-generation-of-umbraco-packages-with-grunt/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Building an Umbraco 7 backoffice extension – Part III</title>
		<link>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/</link>
					<comments>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 30 Nov 2015 15:02:06 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AngularJS]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[WebMatrix]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=841</guid>

					<description><![CDATA[<p>The previous post (Part II) showed how to populate the content tree of a custom Umbraco backoffice section. This one presents a way for content managers to quickly handle each post pending approval, which means we&#8217;re going to create our own AngularJS controller and view. AngularJS is a MVC javascript framework mainly mantained by Google which [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/">Building an Umbraco 7 backoffice extension – Part III</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">T</span>he previous post (<a title="Building an Umbraco 7 backoffice extension - Part II" href="http://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii" target="_blank" rel="noopener">Part II</a>) showed how to populate the content tree of a custom Umbraco backoffice section. This one presents a way for content managers to quickly handle each post pending approval, which means we&#8217;re going to create our own AngularJS controller and view.</p>
<p><a title="Angular.JS" href="https://angularjs.org/" target="_blank" rel="noopener">AngularJS</a> is a MVC javascript framework mainly mantained by Google which primary goal is to aid in the development of single-page applications. It&#8217;s use on the Umbraco backoffice was the most notable change when Umbraco 7 was first released.</p>
<p>When the content manager selects one node pending approval we want him to be able to take three quick actions:</p>
<ul>
<li>get some information about that node;</li>
<li>quickly access it in the content section;</li>
<li>quickly publish it.</li>
</ul>
<p><span id="more-6478"></span></p>
<p>New Angular controllers and views must be placed in a specific Umbraco directory, following a specific directory structure:</p>
<ul>
<li>the main directory must reside at ~\App_Plugins\ApproveIt &#8211; the plugin name</li>
<li>the views and controllers must reside at App_Plugins\ApproveIt\backoffice\approvalTree\ &#8211; the tree controller name</li>
</ul>
<p>First things first:</p>
<p>Create a package manifest so that Umbraco knows there are new javascript files available, and place it in the root of App_Plugins\ApproveIt directory.</p>
<pre class="brush: plain; title: package.manifest; notranslate">

{
	javascript: &#x5B;
		'~/App_Plugins/ApproveIt/backoffice/approvalTree/edit.controller.js',
		'~/App_Plugins/ApproveIt/approval.resource.js'
	]
}

</pre>
<p>Then we must create the javascript files referenced by the manifest and the respective angular view.</p>
<pre class="brush: jscript; title: approval.resource.js; notranslate">

angular.module(&quot;umbraco.resources&quot;)
	.factory(&quot;approvalResource&quot;, function ($http) {
	    return {
	        getById: function (id) {
	            return $http.get(&quot;backoffice/ApproveIt/ApprovalApi/GetById?id=&quot; + id);
	        },
	        publish: function (id) {
	            return $http.post(&quot;backoffice/ApproveIt/ApprovalApi/PostPublish?id=&quot; + id);
	        }
	    };
	});
</pre>
<p>This javascript file provides a new <a title="Angular Service" href="https://docs.angularjs.org/guide/services">Angular Service</a> that can then be used by Angular controllers. This service is no more than a facade for the ApprovalApiController created on <a title="Building an Umbraco 7 backoffice extension - Part II" href="http://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii" target="_blank" rel="noopener">Part II</a>. The methods it connects to are created further on.</p>
<pre class="brush: jscript; title: edit.controller.js; notranslate">

angular.module(&quot;umbraco&quot;).controller(&quot;Approval.ApprovalEditController&quot;,
	function ($scope, $routeParams, approvalResource, notificationsService, navigationService) {

	    $scope.loaded = false;

	    if ($routeParams.id == -1) {
	        $scope.node = {};
	        $scope.loaded = true;
	    }
	    else {
	        approvalResource.getById($routeParams.id).then(function (response) {
	            $scope.node = response.data;
	            $scope.loaded = true;
	        });
	    }

	    $scope.publish = function (node) {
	        approvalResource.publish(node.Id).then(function (response) {
	            $scope.node = response.data;
	            $scope.contentForm.$dirty = false;
	            navigationService.syncTree({ tree: 'approvalTree', path: &#x5B;-1, -1], forceReload: true });
	            notificationsService.success(&quot;Success&quot;, node.Name + &quot; has been published&quot;);
	        });
	    };

	});

</pre>
<p>This Angular controller is called when the Approve It section is being used:</p>
<ul>
<li>If no node is selected (for instance, when we access this section for the first time) we do nothing;</li>
<li>If a node is selected (<em>$routeParams.id != -1</em>) we request the node details through our approval.resource service facade</li>
<li>Finally, the quick publish method is also implemented here. This methods calls the ApprovalResource service which then invokes the publish method of the ApprovalApiController.</li>
</ul>
<pre class="brush: xml; title: edit.html; notranslate">

&lt;form name=&quot;contentForm&quot;
      ng-controller=&quot;Approval.ApprovalEditController&quot;
      ng-show=&quot;loaded&quot;
      ng-submit=&quot;publish(node)&quot;
      val-form-manager&gt;
    &lt;umb-panel&gt;
        &lt;umb-header&gt;
            &lt;div class=&quot;span7&quot;&gt;
                &lt;umb-content-name placeholder=&quot;@placeholders_entername&quot;
                                  ng-model=&quot;node.Name&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;span5&quot;&gt;
                &lt;div class=&quot;btn-toolbar pull-right umb-btn-toolbar&quot;&gt;
                    &lt;umb-options-menu ng-show=&quot;currentNode&quot;
                                      current-node=&quot;currentNode&quot;
                                      current-section=&quot;{{currentSection}}&quot;&gt;
                    &lt;/umb-options-menu&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/umb-header&gt;
        &lt;div class=&quot;umb-panel-body umb-scrollable row-fluid&quot;&gt;
            &lt;div class=&quot;tab-content form-horizontal&quot; style=&quot;padding-bottom: 90px&quot;&gt;
                &lt;div class=&quot;umb-pane&quot;&gt;

                    &lt;umb-control-group label=&quot;Name&quot; description=&quot;Content's name&quot;&gt;
                        &lt;input type=&quot;text&quot; class=&quot;umb-editor umb-textstring&quot; ng-model=&quot;node.Name&quot; required /&gt;
                    &lt;/umb-control-group&gt;

                    &lt;div class=&quot;umb-tab-buttons&quot; detect-fold&gt;
                        &lt;div class=&quot;btn-group&quot;&gt;
                            &lt;a class=&quot;btn&quot; ng-href=&quot;#/content/content/edit/{{node.Id}}&quot;&gt;
                                Show
                            &lt;/a&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;btn-group&quot;&gt;
                            &lt;button type=&quot;submit&quot; data-hotkey=&quot;ctrl+s&quot; class=&quot;btn btn-success&quot;&gt;
                                Publish
                            &lt;/button&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/umb-panel&gt;
&lt;/form&gt;
</pre>
<p>The node view presents the waiting approval page name and two buttons: one to quickly navigate to it&#8217;s details and another to publish it.</p>
<p>Finally, we need to update the ApprovalApiController.cs to include two new methods:</p>
<pre class="brush: csharp; title: ; notranslate">

public IContent GetById(int id)
{
    IContent content = ApplicationContext.Services.ContentService.GetById(id);
    return content;
}

public IContent PostPublish(int id)
{
    IContent node = ApplicationContext.Services.ContentService.GetById(id);
    ApplicationContext.Services.ContentService.Publish(node);

    return node;
}
</pre>
<ul>
<li>GetById: returns the node details to the angular view</li>
<li>PostPublish: publishes the node being viewed</li>
</ul>
<p>By following these steps we are able to create a complete new Umbraco backoffice section that provides us with a way to manage every site content waiting approval for publishing:</p>
<p><a href="http://blogit-create.com/wp-content/uploads/2015/11/approveit1.0.png"><img decoding="async" class="aligncenter wp-image-1031" src="http://blogit-create.com/wp-content/uploads/2015/11/approveit1.0.png" alt="approveit1.0" width="600" height="410" /></a></p>
<p><em>If you want to see how this plugin as been evolving, you can check its source code in github: <a title="https://github.com/ViGiLnT/ApproveIt" href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>. You can also download the plugin ready to be installed at Umbraco&#8217;s extensions repository: <a title="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>. Other Umbraco 7 tutorials and information can be found here: <a title="http://umbraco.github.io/Belle" href="http://umbraco.github.io/Belle" target="_blank" rel="noopener">http://umbraco.github.io/Belle</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/">Building an Umbraco 7 backoffice extension – Part III</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2015/11/30/building-an-umbraco-7-backoffice-extension-part-iii/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title>Building an Umbraco 7 backoffice extension – Part II</title>
		<link>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/</link>
					<comments>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/#respond</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 23 Nov 2015 10:00:56 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AngularJS]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Section]]></category>
		<category><![CDATA[WebMatrix]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=721</guid>

					<description><![CDATA[<p>The previous post (Building an Umbraco 7 backoffice extension – Part I), demonstrated how easy it is to create a new section in Umbraco&#8217;s backoffice. This post will show how we can populate this new section with meaningful content coming directly from the backoffice. To begin with, we must reference a few more assemblies (also [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/">Building an Umbraco 7 backoffice extension – Part II</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">T</span>he previous post (<a title="Building an Umbraco 7 backoffice extension – Part I" href="http://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/" target="_blank" rel="noopener">Building an Umbraco 7 backoffice extension – Part I</a>), demonstrated how easy it is to create a new section in Umbraco&#8217;s backoffice. This post will show how we can populate this new section with meaningful content coming directly from the backoffice.</p>
<p>To begin with, we must reference a few more assemblies (also available at the Umbraco&#8217;s installation <em>bin</em> directory):</p>
<ul>
<li>umbraco.dll</li>
<li>cms.dll</li>
<li>Umbraco.Core.dll</li>
<li>System.Web.Http</li>
<li>System.Net.Http.Formatting</li>
</ul>
<p>Then we can populate our section with a tree containing content awaiting approval. To accomplish this we must extend an Umbraco class named TreeController and decorate it with information about our <strong>Approve It</strong> plugin:</p>
<p><span id="more-6471"></span></p>
<pre class="brush: csharp; title: ; notranslate">

&#x5B;Tree(&quot;approveIt&quot;, &quot;approvalTree&quot;, &quot;Content for Approval&quot;)]
&#x5B;PluginController(&quot;ApproveIt&quot;)]
public class ApprovalTreeController : TreeController
{
   protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
   {
       var ctrl = new ApprovalApiController();
       var nodes = new TreeNodeCollection();

       IUser user = UmbracoContext.Security.CurrentUser;

       //check if we're rendering the root node's children
       if (id == Constants.System.Root.ToInvariantString())
       {
           foreach (IContent content in ctrl.GetAll(user))
           {
                var node = CreateTreeNode(
                    content.Id.ToString(),
                    &quot;-1&quot;,
                    queryStrings,
                    content.Name,
                    &quot;icon-document&quot;,
                    false);

               nodes.Add(node);
            }
       }

        return nodes;
    }

    protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
    {
        var menu = new MenuItemCollection();

        if (id == Constants.System.Root.ToInvariantString())
        {
            // root actions
            menu.Items.Add&lt;RefreshNode, ActionRefresh&gt;(
                ApplicationContext.Services.TextService.Localize(ActionRefresh.Instance.Alias));
        }

        return menu;
    }
}
</pre>
<p>To create a tree we must implement two methods:</p>
<ol>
<li>GetTreeNodes: in here we call the GetAll method of the ApprovalApiController that we&#8217;ll create in the following. This GetAll method returns a list of content waiting approval and we just iterate through it all and create tree nodes;</li>
<li>GetMenuForNode: presents a dropdown menu when we right click the root node to refresh the list.</li>
</ol>
<p>Next we create our custom WebAPI and name it ApprovalApiController. For it to be available in our site, we need only extend the UmbracoAuthorizedJsonController.</p>
<pre class="brush: csharp; title: ; notranslate">

&#x5B;PluginController(&quot;ApproveIt&quot;)]
public class ApprovalApiController : UmbracoAuthorizedJsonController
{
    public IList&lt;IContent&gt; UnpublishedContent { get; set; }

    public IEnumerable&lt;IContent&gt; GetAll(IUser user)
    {
        UnpublishedContent = new List&lt;IContent&gt;();

        IList&lt;IContent&gt; root = ApplicationContext.Services.ContentService.GetRootContent().ToList();

        foreach (IContent content in root)
        {
            GetNode(content);
        }

        return UnpublishedContent;
    }

    private void GetNode(IContent node)
    {
        if (!node.Published)
        {
            UnpublishedContent.Add(node);
        }

        foreach (IContent child in ApplicationContext.Services.ContentService.GetChildren(node.Id))
        {
            GetNode(child);
        }
    }
}

</pre>
<p>The GetAll method, iterates through all the nodes in the content section and finds the ones that are not published, that is, that are waiting approval.</p>
<p>With these two classes we are able to populate our section with a tree containing the content nodes that are waiting approval:</p>
<p><a href="http://blogit-create.com/wp-content/uploads/2015/11/approveit2.png"><img decoding="async" class="alignnone size-medium wp-image-791" src="http://blogit-create.com/wp-content/uploads/2015/11/approveit2-167x300.png" alt="approveit2" width="167" height="300" /></a></p>
<p>The next post in the series will show how to create the Angular view and controller that will enable content managers to actually manage the content waiting approval.</p>
<p><em>The Approve It extension can be found here: <a title="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>. The complete source code can be found here: <a title="https://github.com/ViGiLnT/ApproveIt" href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/">Building an Umbraco 7 backoffice extension – Part II</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2015/11/23/building-an-umbraco-7-backoffice-extension-part-ii/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building an Umbraco 7 backoffice extension &#8211; Part I</title>
		<link>https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/</link>
					<comments>https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/#comments</comments>
		
		<dc:creator><![CDATA[André Santos]]></dc:creator>
		<pubDate>Mon, 16 Nov 2015 15:34:25 +0000</pubDate>
				<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AngularJS]]></category>
		<category><![CDATA[Backoffice extension]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[package]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[WebMatrix]]></category>
		<guid isPermaLink="false">http://blogit.create.pt/andresantos/?p=461</guid>

					<description><![CDATA[<p>Umbraco (https://umbraco.com/) is a lean and powerful CMS built on top of current .NET technologies and Javascript frameworks. It provides developers with a varied and simple to use collection of APIs, it is easy to customize and doesn&#8217;t get in the way between the coded markup and the HTML that is actually rendered. The next series [&#8230;]</p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/">Building an Umbraco 7 backoffice extension &#8211; Part I</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><span class="dropcap dropcap3">U</span>mbraco (<a href="https://umbraco.com/">https://umbraco.com/</a>) is a lean and powerful CMS built on top of current .NET technologies and Javascript frameworks. It provides developers with a varied and simple to use collection of APIs, it is easy to customize and doesn&#8217;t get in the way between the coded markup and the HTML that is actually rendered.</p>
<p>The next series of posts will show how to build plugins that extend the default Umbraco backoffice capabilities.</p>
<p><span id="more-6467"></span></p>
<h2>How is it built?</h2>
<p>The release of Umbraco&#8217;s version 7 presented a completely redesigned backoffice. It went from a  typical MVC website to a single page application built using <a title="AngularJS" href="https://angularjs.org/" target="_blank" rel="noopener">AngularJS</a>. Umbraco custom WebAPIs are used by Angular controllers in order to read information from the backoffice.</p>
<h2>How can we change it?</h2>
<p>A typical Umbraco 7 backoffice extension requires:</p>
<ol>
<li>Creation of Angular controllers, views and probably models;</li>
<li>Umbraco WebAPI extensions in order to access Umbraco&#8217;s backoffice and for custom server side development;</li>
<li>Extension of some Umbraco classes to customize common backoffice functionalities.</li>
</ol>
<h2>What to do?</h2>
<p>These series of posts will gradually present a way to build a new backoffice section, that will provide content managers with a centralized area where they can approve changes made to content. This backoffice extension was named<strong> Approve It</strong>. Natively, Umbraco sends an email to each content approver when changes are made to content nodes but, if an approver wants to see what nodes were updated just by visiting the backoffice, he must traverse every node to see which ones present a change waiting approval. This gets harder as the number of content nodes increase, and the changed nodes can be difficult to spot as we can see from the image below:</p>
<figure id="attachment_541" aria-describedby="caption-attachment-541" style="width: 285px" class="wp-caption aligncenter"><a href="http://blogit-create.com/wp-content/uploads/2015/11/changedcontent.png"><img decoding="async" class="wp-image-541 size-medium" src="http://blogit-create.com/wp-content/uploads/2015/11/changedcontent-285x300.png" alt="changedcontent" width="285" height="300" /></a><figcaption id="caption-attachment-541" class="wp-caption-text">The red arrows point to changed content &#8211; we can&#8217;t tell by the parent node if one of its children has changed</figcaption></figure>
<h2>Preparing the development environment</h2>
<ol>
<li>Download the latest and greatest version of Umbraco 7 (currently at v7.3.1): <a title="https://our.umbraco.org/contribute/releases/731/" href="https://our.umbraco.org/contribute/releases/731/ " target="_blank" rel="noopener">https://our.umbraco.org/contribute/releases/731/ </a></li>
<li>Download and setup WebMatrix: <a title="http://www.microsoft.com/web/webmatrix/" href="http://www.microsoft.com/web/webmatrix/" target="_blank" rel="noopener">http://www.microsoft.com/web/webmatrix/</a></li>
<li>Create a new web site in WebMatrix selecting the folder which contains Umbraco&#8217;s downloaded .zip file contents and go through the Setup
<ol>
<li>Select customize</li>
<li>Use a SQL CE database</li>
<li>Choose any starter kit (I chose Txt Starter Kit)</li>
</ol>
</li>
<li>Create a new Empty ASP.NET project in Visual Studio (I&#8217;m using VS2015) and add folders and core references for MVC</li>
</ol>
<h2>Creating a new backoffice section</h2>
<p>Creating a new section in Umbraco is easy:</p>
<ol>
<li>Add references to the following three assemblies available in the <em>bin</em> Umbraco directory:
<ul>
<li>businesslogic.dll</li>
<li>interfaces.dll</li>
<li>System.Web.Mvc.dll (need to replace the one Visual Studio automatically referenced)</li>
</ul>
</li>
<li>Create a new .cs file in the project with the following code:</li>
</ol>
<pre class="brush: csharp; title: ; notranslate">

&#x5B;Application(&quot;approveIt&quot;, &quot;ApproveIt&quot;, &quot;icon-people&quot;, 15)]
public class Section : IApplication
{
}

</pre>
<p>Simply implementing the IApplication interface, creates a new Umbraco backoffice section with the properties defined above.</p>
<p>The next step is translating the section name. If we don&#8217;t translate it, it will show up as [approveIt]. So, we must edit the file<em> \Umbraco\Config\Lang\en.xml</em> (for the UK version), and append the &#8220;approveIt&#8221; line in the &#8220;sections&#8221; area:</p>
<pre class="brush: xml; title: ; notranslate">

&amp;amp;amp;lt;area alias=&quot;sections&quot;&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;concierge&quot;&amp;amp;amp;gt;Concierge&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;content&quot;&amp;amp;amp;gt;Content&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;courier&quot;&amp;amp;amp;gt;Courier&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;developer&quot;&amp;amp;amp;gt;Developer&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;installer&quot;&amp;amp;amp;gt;Umbraco Configuration Wizard&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;media&quot;&amp;amp;amp;gt;Media&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;member&quot;&amp;amp;amp;gt;Members&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;newsletters&quot;&amp;amp;amp;gt;Newsletters&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;settings&quot;&amp;amp;amp;gt;Settings&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;statistics&quot;&amp;amp;amp;gt;Statistics&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;translation&quot;&amp;amp;amp;gt;Translation&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;users&quot;&amp;amp;amp;gt;Users&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;help&quot; version=&quot;7.0&quot;&amp;amp;amp;gt;Help&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;forms&quot;&amp;amp;amp;gt;Forms&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;analytics&quot;&amp;amp;amp;gt;Analytics&amp;amp;amp;lt;/key&amp;amp;amp;gt;
  &amp;amp;amp;lt;key alias=&quot;approveIt&quot;&amp;amp;amp;gt;Approve It&amp;amp;amp;lt;/key&amp;amp;amp;gt;
&amp;amp;amp;lt;/area&amp;amp;amp;gt;

</pre>
<p><strong>Note</strong>: for the new section to appear you must add it to the available sections your user can see. Furthermore, because of caching, you probably need to restart the website and open a browser window in private mode in order to see the new section.</p>
<p>This is how it looks:<br />
<a href="http://blogit-create.com/wp-content/uploads/2015/11/approveit.png"><img decoding="async" class="alignnone size-full wp-image-681" src="http://blogit-create.com/wp-content/uploads/2015/11/approveit.png" alt="approveit" width="112" height="271" /></a></p>
<p>So, we have a new section, but there&#8217;s nothing we can do with it. The next post in the series will show how we can add some content to this newly created Umbraco backoffice area.</p>
<p><em>The Approve It extension can be found here: <a title="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" href="https://our.umbraco.org/projects/backoffice-extensions/approve-it/" target="_blank" rel="noopener">https://our.umbraco.org/projects/backoffice-extensions/approve-it/</a>. The complete source code can be found here: <a title="https://github.com/ViGiLnT/ApproveIt" href="https://github.com/ViGiLnT/ApproveIt" target="_blank" rel="noopener">https://github.com/ViGiLnT/ApproveIt</a>.</em></p>
<p>The post <a href="https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/">Building an Umbraco 7 backoffice extension &#8211; Part I</a> appeared first on <a href="https://blogit.create.pt">Blog IT</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
