Jak na Widgety v NopCommerce

Posted on Posted in NopCommerce

Widgety v NopCommerce

NopCommerce je modulární systém a podporuje několik druhů modulů a rozšíření, které si můžete napsat. Ještě před tím, než se pustíme do psaní vlastního wigdetu, je nutné se pozastavit  a prozkoumat, jaké rozšíření NopCommerce nabízí a v čem se liší.

  1. IExternalAuthenticationMethod. Používá se pro externí autorizaci uživatele. Např. pro Facebook,Twitter a podobné.
  2. IWidgetPlugin. Používá se pro vytváření widgetu. Widgety jsou moduly, které se vykreslují na stránce v předem definované oblasti.
  3. IExchangeRateProvider. Používá se pro zjištění aktuálního platného kurzu.
  4. IDiscountRequirementRule. Používá se pro vytváření nových pravidel pro slevy.
  5. IPaymentMethod. Používá se pro vytvoření platební metody.
  6. IShippingRateComputationMethod. Používá se pro získání  metod dopravy a nákladu spojených s dopravou.
  7. ITaxProvider. Používá se pro získání daňových sazeb.

Jak sami vidíte, způsobů, jak rozšířit NopCommerce, je poměrně hodně. Proč je dobré psát moduly, když máte dostupný zdrojový kód, který si můžete sami upravit ? Před samotnou instalací systému je nutné se zamyslet, jakým způsobem vlastně chcete z dlouhodobého hlediska NopCommerce používat.

NopCommerce má velkou komunitu a vývoj je zde poměrně rychlý, to znamená, že pokud chcete zachovat snadnou možnost upgradu na novější verzi, bude nejvhodnější dělat změny právě pomocí modulů, které jsou zpětně kompatibilní.

Widget zóny

Při procházení souborů .cshtml jste si již možná všimli, že mnoho pohledů obsahuje widget zóny. Tyto oblasti jsou pevně definovány na nejrůznějších místech systému a umožňují vykreslení widgetů. To znamená, že naše widgety můžete nechat vykreslit v libovolné části.

 

Widget

Wigdet je komponenta pro grafické rozhraní, která může být umístěna do widget zóny. NopCommerce nabízí po instalaci dva widgety a to Nivo slider a Google Analytics. Právě tyto dva widgety jsou typickými komponenty, které se řeší pomocí widgetů. Widget může být například i okno pro chat nebo jen javascript.

Pro první Widget jsem vymyslel zadání, jak rozšířit příspěvek blogu o strukturovaná data pro vyhledávače (schema.org). V praxi to znamená, že potřebujeme za každý příspěvek vložit trochu javascript kódu. Způsobů, jak docílit přidání strukturovaných dat do html, je více. Může být pro Vás jednodušší přidat tagy do html značek. Ale pro nás je důležitá přenositelnost, protože widget budeme moci nainstalovat na jakýkoliv NopCommerce systém.

Začínáme

Jako první musíme vytvořit nový projekt typu”Class library”, který vložíte do složky Plugins, kde je dobré udržovat všechny moduly pro Vaše řešení. Je dobré dodržet jmenné konvence, takže se náš modul bude jmenovat  Nop.Plugin.Widgets.Schema.BlogPosting.

Po vytvoření projektu se Vám musí zobrazit projekt ve složce (solution folder) Plugins. Když máte projekt vytvořený můžete v něm udělat strukturu, na kterou jste zvyklí z MVC (Controllers,Models,Views).  Jakmile máte vytvořené prázdné složky je nutné vytvořit soubor Description.txt. Soubor slouží pro instalaci do systému NopCommerce.

Struktura souboru Description.txt :

Group: Widgets
FriendlyName: SchemaOrg.BlogPosting
SystemName: Nop.Plugin.Widgets.Schema.BlogPosting
Version: 1.00
SupportedVersions: 3.90
Author: Tomas Horvath
DisplayOrder: 1
FileName: Nop.Plugin.Widgets.Schema.BlogPosting.dll
Description: Widget allow add structured data to blog post

Soubor web.config můžete zkopírovat z NivoSlider modulu nebo Google Analytics. Pokud máte připravenou strukturu projektu, je nutné přidat reference na knihovny NopCommerce. Když máme všechny reference, tak se můžeme pustit konečně do psaní widgetu.

 

Nejdřív musíme vytvořit třídu, která bude implementovat rozhraní IWidgetPlugin. Třída má pouze pět metod. Metoda GetWidgetZones určuje, do jakých oblastí (widget zón) bude widget přidaný. Metody GetConfigurationRoute a GetDisplayWidgetRoute určuji, jaké routy se mají použít pro konfiguraci widgetu v administrační zóně a na frontendu. Metody Install a Uninstall slouží například pro konfigurační data při instalaci nebo odinstalaci pluginu. V případě, že chcete mít pro widget konfigurační data, je nejlepší využít interface ISettings (ukážeme si v dalším článku).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Web.Routing;
using Nop.Core;
using Nop.Core.Plugins;
using Nop.Services.Cms;
using Nop.Services.Configuration;
using Nop.Services.Localization;
using Nop.Services.Media;
using Nop.Services.Common;
namespace Nop.Plugin.Widgets.Schema.BlogPosting
{
	/// <summary>
	/// PLugin
	/// </summary>
	public class BlogPostingWidget : BasePlugin, IWidgetPlugin
	{
		/// <summary>
		/// Gets widget zones where this widget should be rendered
		/// </summary>
		/// <returns>Widget zones</returns>
		public IList<string> GetWidgetZones()
		{
			return new List<string> { "blogpost_page_bottom" };
		}
		/// <summary>
		/// Gets a route for provider configuration
		/// </summary>
		/// <param name="actionName">Action name</param>
		/// <param name="controllerName">Controller name</param>
		/// <param name="routeValues">Route values</param>
		public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues)
		{
			actionName = "Configure";
			controllerName = "BlogPostingWidget";
			routeValues = new RouteValueDictionary { { "Namespaces", "Nop.Plugin.Widgets.Schema.BlogPosting.Controllers" }, { "area", null } };
		}
		/// <summary>
		/// Gets a route for displaying widget
		/// </summary>
		/// <param name="widgetZone">Widget zone where it's displayed</param>
		/// <param name="actionName">Action name</param>
		/// <param name="controllerName">Controller name</param>
		/// <param name="routeValues">Route values</param>
		public void GetDisplayWidgetRoute(string widgetZone, out string actionName, out string controllerName, out RouteValueDictionary routeValues)
		{
			actionName = "PublicInfo";
			controllerName = "BlogPostingWidget";
			routeValues = new RouteValueDictionary
			{
				{"Namespaces", "Nop.Plugin.Widgets.Schema.BlogPosting.Controllers"},
				{"area", null},
				{"widgetZone", widgetZone}
			};
		}
		/// <summary>
		/// Install plugin
		/// </summary>
		public override void Install()
		{
			PluginManager.MarkPluginAsInstalled(this.PluginDescriptor.SystemName);
		}
		/// <summary>
		/// Uninstall plugin
		/// </summary>
		public override void Uninstall()
		{
			PluginManager.MarkPluginAsUninstalled(this.PluginDescriptor.SystemName);
		}
	}
}

V případě, že máme hotový základní plugin, můžeme se vrhnout na vytvoření controlleru, který bude obsluhovat zobrazení a konfiguraci. Třída obsahuje dvě metody pro administrační rozhraní a frontend. V konstruktoru se používá několik služeb, které jsou pomocí dependency injection předány do konstruktoru. Metoda Configure není zajímavá v tomto případě, protože widget nebude mít administrační část.  Metoda PublicInfo je už přece jenom trochu zajímavější. Jedná se pouze o ukázku, takže by šel kód učesat 🙂 . Důležité je, že vyzobeme data z databáze a předhodíme je pohledu.

using Nop.Services.Security;
using Nop.Core.Domain.Localization;
using Nop.Core.Data;
using Nop.Core;
using Nop.Web.Models.Common;
using Nop.Core.Caching;
using Nop.Web.Infrastructure.Cache;
using Nop.Core.Infrastructure;

namespace Nop.Plugin.Widgets.Schema.BlogPosting.Controllers
{
	public class BlogPostingWidgetController : BasePluginController
	{
		private readonly Nop.Web.Factories.IBlogModelFactory blogFactory;
		private readonly Nop.Services.Blogs.IBlogService blogService;
		private readonly Nop.Core.IWorkContext workContext;
		private readonly Nop.Core.IStoreContext storeContext;
		private readonly Nop.Web.Factories.ICommonModelFactory commonFactory;
		private readonly Nop.Core.Caching.ICacheManager cacheManager;
		private readonly Nop.Services.Seo.IUrlRecordService urlService;

		public BlogPostingWidgetController(Nop.Web.Factories.IBlogModelFactory blogFactory,
			Nop.Services.Blogs.IBlogService blogService,
			Nop.Core.IWorkContext workContext,
			Nop.Core.IStoreContext storeContext,
			Nop.Core.Caching.ICacheManager cacheManager,
			Nop.Web.Factories.ICommonModelFactory commonFactory,
			Nop.Services.Seo.IUrlRecordService urlService
			)
		{
			this.blogFactory = blogFactory;
			this.blogService = blogService;
			this.workContext = workContext;
			this.storeContext = storeContext;
			this.cacheManager = cacheManager;
			this.commonFactory = commonFactory;
			this.urlService = urlService;
		}

		[AdminAuthorize]
		public ActionResult Configure()
		{
			return View("/Plugins/Widgets.Schema.BlogPosting/Views/Configure.cshtml");
		}
		public ActionResult PublicInfo(string widgetZone, object additionalData = null)
		{
			var blogPostId = (int)additionalData;
			var post = blogService.GetBlogPostById(blogPostId);
			Nop.Web.Models.Blogs.BlogPostModel BlogPostModel = new Web.Models.Blogs.BlogPostModel(); 
			blogFactory.PrepareBlogPostModel(BlogPostModel,post,false);
			var store = storeContext.CurrentStore;
			var logo = commonFactory.PrepareLogoModel();
			var blogPostSlug = urlService.GetActiveSlug(blogPostId, "BlogPost", 2);

			Models.BlogPostingViewModel model = new Models.BlogPostingViewModel();

			model.BlogPost = BlogPostModel;
			model.Store = store;
			model.Logo = logo;
			model.blogPostUrl = store.Url + "/" + blogPostSlug;
			model.logoUrl = logo.LogoPath;
			return View("/Plugins/Widgets.Schema.BlogPosting/Views/PublicInfo.cshtml",model);
		}
	}
}

Model je velim jednoduchý. Potřebujeme informace o blog příspěvku, informace o obchodu a pár url adres.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nop.Plugin.Widgets.Schema.BlogPosting.Models
{
	public class BlogPostingViewModel
	{
		public Nop.Web.Models.Blogs.BlogPostModel BlogPost { get; set; }
		public Nop.Core.Domain.Stores.Store Store {get;set;}
		public Nop.Web.Models.Common.LogoModel Logo { get; set; }
		public string blogPostUrl { get; set; }
		public string logoUrl { get; set; }
	}
}

V pohledu pouze nastavíme správná data na správné místo a je hotovo.

@model Nop.Plugin.Widgets.Schema.BlogPosting.Models.BlogPostingViewModel

<script type="application/ld+json">
	{
	"@@context": "http://schema.org",
	"@@type": "BlogPosting",
	"mainEntityOfPage": {
	"@@type": "WebPage"

	},
	"headline": "@Model.BlogPost.Title",

	"datePublished": "@Model.BlogPost.CreatedOn.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz");",
	"url": "@Model.blogPostUrl",
	"author": {
	"@@type": "Organization",
	"name": "@Model.Store.Name"
	},
	"publisher": {
	"@@type": "Organization",
	"name": "@Model.Store.Name",
	"logo": {
	"@@type": "ImageObject",
	"url": "@Model.logoUrl",
	"width": 168,
	"height": 105
	}
	},
	"description": "@Model.BlogPost.BodyOverview"
	}
</script>

V současné chvíli máme skoro vše připravené pro instalaci widgetu. Ještě nezapomeňte vytvořit klidně jenom prázdný pohled pro konfiguraci (Configure.cshtml)
Již nám zbývá pouze zkompilovat projekt a nakopírovat jej do příslušné složky a nainstalovat. Pokud jste stejně zapomnětlivý jako já, tak doporučuji nastavit, aby se Vám výsledný build přímo přenášel do určené složky. V našem případě (..\..\Presentation\Nop.Web\Plugins\Widgets.Schema.BlogPosting\). Nezapomeňte, že pro NopCommerce je důležitý i soubor description.txt, takže ho buď nakopírujte nebo nastavte, aby se vždy kopíroval po spuštění kompilace.

Pokud máte vše ve správné složce, tak můžete přejít do administrace nopCommerce do sekce lokalní pluginy. Obnovíte seznam pluginů a můžete nainstalovat Váš nový widget

Vše je nainstalováno a funkční 🙂 Krásná práce. Závěrem je nutné dodat, že se jedná pouze o ukázku. Určitě by bylo lepší použít komponentu pro cache, abychom nemuseli pořád otravovat databázi. Každopádně jsme společně otevřeli cestu pro nepřeberné množství nápadů a Widgetů, které si můžete napsat nebo je i nabídnou dalším uživatelům komunity zde. Zdrojový kód najdete zde