How to publish an asset within a widget in Yii2 - yii2

In Yii 1 it was possible to publish an asset with:
Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('ext.MyWidget.assets'));
How can I publish an asset within a widget in Yii2?

In your view of your widget:
app\assets\AppAsset::register($this); // $this == the View object
Check the docs.

Simplest way of doing this:
app\assets\AppAsset::register($this->getView());
It is the same way you would publish it in a view.

The problem for this case in the question.
In Yii2 AssetBandle should be unique because it avoids duplication on load same files on a web page. In Yii1 it was a problem.
Almost all of the suggested answers here do not solve this problem.
That why the answer should be like this:
You should create new asset bundle(yii\web\AssetBundle) with a unique name that globally identifies it among all asset bundles used in an application. The unique name will be checked by fully qualified class name. After uses follow:
YourAsset::register($view);
or
$view->registerAssetBundle(InterrogateDeviceButtonAsset::class);

Nearly the same :
Yii::$app->getAssetManager()->publish('/path/to/assets'); // you can use an alias
http://www.yiiframework.com/doc-2.0/yii-web-assetmanager.html#publish()-detail
But if you want to publish css and js, it should be better to create an asset bundle, read more here : http://www.yiiframework.com/doc-2.0/guide-structure-assets.html#defining-asset-bundles

I have implemented this function in my BaseController.php which is extended by all my controllers. It's a function to publish a single asset.
/**
* Publish an asset and return url
*
* #param $src
*
* #return mixed
*/
public function publishAsset( $src ) {
$path = Yii::getAlias( $src );
if ( ! $this->assetManager ) {
$this->assetManager = new AssetManager();
}
$return = $this->assetManager->publish( $path );
return $return[1];
}
The only thing I'm trying to adjust is making it possible to include the full URL scheme. I've posted my question about that here Yii2 AssetManager published path include URL scheme

Related

AngularJs Dynamic/Multiple HTML Templates

I'm working on an AngularJs/MVC app with Web API etc. which is using a CDN. I have managed to whitelist two URLs for Angular to use, a local CDN and a live CDN (web app hosted in Azure).
I can successfully ng-include a template from my local CDN domain, but the problem arises when I push the site to a UAT / Live environment, I cant be using a template on Localhost.
I need a way to be able to dynamically get the base url for the templates. The location on the server will always be the same, eg: rooturl/html/templates. I just need to be able to change the rooturl depending on the environment.
I was thinking if there was some way to store a global variable, possibly on the $rootScope somewhere that I can get to when using the templates and then set that to the url via Web API which will get return a config setting.
For example on my dev machine the var could be http://Localhost:52920/ but on my uat server it could be https://uat-cdn.com/
Any help would be greatly appreciated as I don't want to store Js, css, fonts etc on the CDN but not the HTML as it feels nasty.
Thanks I'm advance!
I think it's good practice to keep environment and global config stuff outside of Angular altogether, so it's not part of the normal build process and is harder to accidentally blow away during a deploy. One way is to include a script file containing just a single global variable:
var config = {
myBaseUrl: '/templates/',
otherStuff: 'whatever'
}
...and expose it to Angular via a service:
angular.module('myApp')
.factory('config', function () {
var config = window.config ? window.config : {}; // (or throw an error if it's not found)
// set defaults here if useful
config.myBaseUrl = config.myBaseUrl || 'defaultBaseUrlValue';
// etc
return config;
}
...so it's now injectable as a dependency anywhere you need it:
.controller('fooController', function (config, $scope), {
$scope.myBaseUrl = config.myBaseUrl;
}
Functionally speaking, this is not terribly different from dumping a global variable into $rootScope but I feel like it's a cleaner separation of app from environment.
If you decide to create a factory then it would look like this:
angular.module('myModule', [])
.factory('baseUrl', ['$location', function ($location) {
return {
getBaseUrl: function () {
return $location.hostname;
}
};
}]);
A provider could be handy if you want to make any type of customization during config.
Maybe you want to build the baseurl manually instead of using hostname property.
If you want to use it on the templates then you need to create a filter that reuses it:
angular.module('myModule').filter('anchorBuilder', ['baseUrl', function (baseUrl) {
return function (path) {
return baseUrl.getBaseUrl() + path;
}
}]);
And on the template:
EDIT
The above example was to create links but if you want to use it on a ng-include directive then you will have a function on your controller that uses the factory and returns the url.
// Template
<div ng-include src="urlBuilder('path')"></div>
//Controller
$scope.urlBuilder = function (path) {
return BaseUrl.getBaseUrl() + path;
};
Make sure to inject the factory in the controller

Referencing resources in a global way either from a virtual directory or the web root?

Let's say I have an MVC/WebAPI/AngularJS site that I'm running locally, e.g. ;
localhost/Test/
which I then want to move to
www.test.com
While local, I have a lot of references to various directories (jsfiles, etc) of the following format (in either JS or HTML files)
app.directive('rpdbSpinner', function() {
return {
restrict: 'E',
**templateUrl: '/Test/templates/directives/spinner.html',**
scope: {
isLoading:'='
}
}
})
when updating/web publishing, I'd have to change everything to:
app.directive('rpdbSpinner', function() {
return {
restrict: 'E',
**templateUrl: '/templates/directives/spinner.html',**
scope: {
isLoading:'='
}
}
})
I can do this manually (which is what I've been doing),but the larger the project grows, the harder it becomes. I could, of course, only change it once and then excluded the files during publishing phase (web.config/rest), but it still feels like I am going about it the wrong way. Using "~/" wouldn't work on plain HTML/JS files as far as I'm aware, and this I can't really use it...
Any suggestions to map to paths globally regardless of whether in a Virtual Directory or the root of a project?
Thanks :)
If you simply care about getting the root/base url of the site so you can append that to get the other url you are after, you may simply use / as the first character of your url.
var getUsersUrl = "/api/users";
Here is an alternate approach if you want more than just the app root (Ex : Specific urls( built using mvc helper methods such as Url.RouteUrl etc)
You should not hard code your app base path like that. You may use the Url.Content or Url.RouteUrl helper methods in your razor view to generate the url to the app base. It will take care of correctly building the url regardless of your current page/path.Once you get this value, assign it to a javascript variable and use that in your other js code to build your other urls. Always make sure to use javascript namespacing when doing so to avoid possible issues with global javascript variables.
So in your razor view (Layout file or specific view), you may do this.
<script>
var myApp = myApp || {};
myApp.Urls = myApp.Urls || {};
myApp.Urls.baseUrl = '#Url.Content("~")';
myApp.Urls.userListUrl = '#Url.Action("Index","User")';
</script>
<script src="~/Scripts/NonAngularJavaScript.js"></script>
<script src="~/Scripts/AngularControllerForPage.js"></script>
<script>
var a = angular.module("app").value("appSettings", myApp);
</script>
In your angular controller, you can access it like,
var app = angular.module("app", []);
var ctrl = function (appSettings) {
var vm = this;
console.log(appSettings.Urls.userListUrl);
vm.baseUrl = appSettings.Urls.baseUrl;
//build other urls using the base url now
var getUsersUrl = vm.baseUrl + "api/users";
console.log(getUsersUrl);
};
app.controller("ctrl", ctrl)
You can also access this in your data services, directives etc.
In your non angular java script files.
// With the base url, you may safely add the remaining url route.
var urlToJobIndex2= myApp.Urls.baseUrl+"jobs/GetIndex";
Using "~/" wouldn't work on plain HTML/JS files as far as I'm aware,
and this I can't really use it...
Yes, but you could inject it in your main server-side served webpage as a variable:
<script>
var baseUrl = ... get the base url from the server using ~/
</script>
and then in your external scripts simply concatenate the relative urls with it. As far as static html files are concerned, then it could be a little more problematic. You could serve them through some special server side handler that will take care of injecting this logic.
You can use module.constant to create an injectable which you can use.
app.constant("URL_BASE", "/Test");
app.directive('rpdbSpinner', function(URL_BASE) {
return {
restrict: 'E',
**templateUrl: URL_BASE + '/templates/directives/spinner.html',**
scope: {
isLoading:'='
}
}
})
You can also use module.value if you register it before you register your directive.
For more information see AngularJS Module Guide -- configuration.

Magento Front End 404 Error

I am a newbie to magento, I have installed and put a few products, but then later I was getting Error:404 page not found in the front end. Backend is all OK, I am able to access everything but all of a sudden I don't know how this happened. I tried all the solutions like Flush Cache, replacing .htaccess, is_active field in database etc but all proved futile. Then lately I have put in system->Configuration->Web Base_url as http://sportiva.no/index.php/ (Previously it was http://sportiva.no/) and all is completely changed all the styles went away and I am not able to save anything. Please help, I am ready to give backend credentails.
Please help
Go to System > Configuration > Web > Default Pages and check "CMS Home Page" field value. If it is "404 Not Found", then change it to any of the CMS page available on the drop-down and save the configuration.
Refere to this link by Alan Storm,
http://magento-quickies.alanstorm.com/post/6462298634/404-debugging
excerpt from it,
Put this code in function _validateControllerClassName
<?php
#File: app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
/**
* Generating and validating class file name,
* class and if evrything ok do include if needed and return of class name
*
* #return mixed
*/
protected function _validateControllerClassName($realModule, $controller)
{
$controllerFileName = $this->getControllerFileName($realModule, $controller);
if (!$this->validateControllerFileName($controllerFileName)) {
var_dump($controllerFileName);
return false;
}
$controllerClassName = $this->getControllerClassName($realModule, $controller);
if (!$controllerClassName) {
var_dump($controllerClassName);
return false;
}
// include controller file if needed
if (!$this->_includeControllerClass($controllerFileName, $controllerClassName)) {
var_dump($controllerFileName . '----' . $controllerClassName);
return false;
}
return $controllerClassName;
}

How can a website call a file that no longer exists?

I have a website. I want to update my CSS file. After 20 minutes, uploading it 20 times, every browser still pulls the same CSS file. I delete the file off the server. There is literally no CSS file. I clear all my browser's cache's and history; everything. I load the website. It still pulls the old CSS file. Observations:
This is obviously a browser issue: when I load from mobile, the new file is there. So how can I give my Browser's a hint and actually make them clear cookies/cache etc?
Now I go to another computer that has never accessed the website, and it pulls old CSS file.
Please explain to me how, and why this is possible. Is it my host? I am using Netfirms, but how long can it possibly take to update a CSS file?
Its perfectly normal for browsers to cache content unless you take complete control over how long you want your pages and their resources to get cached.
What you want to do in these cases is implement something called "cache busting". What cache busting does is force browsers to treat a changed file on the server as a new request by simply making the browsers think its a different file or request.
The easiest way to do that is to append a query string like ?v=123 to your resource (JavaScript, CSS) URL. But do take care that some proxies will ignore to refresh their cached content if you use query strings. Therefore my preferred way of handling cache busting is to use rewrite rules to point requests for files like style-1391836063.css to a file which always has the same name on the server, i.e. style.css. The rewrite rule for that can be as simple as
RewriteRule (.+)-(\d+).(css|js)$ $1.$3 [L]
The whole magic is in the 1391836063 part which is actually a timestamp generated with filemtime(). That timestamp represents the last time the file was changed so it will retain the same value as long as style.css stays the same. In that case browsers will simply see the same resource name and will have no need to redownload its since its already cached and considered up to date.
However, if style.css does change the modiefied timestamp will also change which will result in a different numerical part, i.e. style-1391867247.css. If that happens browsers will be forced to discard any cached data and treat that request as a completely new resource.
However, for this to work you would also need server side support or in other words a script which will be doing all the fingerprinting (fingerprinting is actually the correct way to reference this technique). No matter what server side technology you use the process would be as follows.
With a DOM parser you look up for all the references to CSS and/or JavaScript files.
For every reference you find you check if the file exists on the server and if it does you read its modified timestamp with filemtime(). Then you append it to the actual resource name (style.css becomes something like style-1391867247.css)
You returne the fingerpinted code to the browser.
Here is a PHP class I wrote to perform fingeprinting on most of my projects. Please note that in order to avoid unnecessary processing this class should be used with some form of server-side caching to avoid fingeprinting if its not required. Additionally the code references some external constants and classes which should be disregarded as the code simply tries to demonstrate one way of how fingerprinting can be done.
/**
* CacheBusting class responsible for fingerprinting CSS and JavaScript resources in order to prevent caching issues.
*/
class CacheBuster {
/**
* Parses the loaded (X)HTML code and fingerprints all resources with their Last Modified timestamp.
*
* #param string $content XHTML content to fingerprint.
* #return mixed Either fingerprinted $content on success or false on failure.
*/
public static function fingerprint($content){
/**
* ExtendedDOMDocument for manipulating content data (something written by me to replace the gimpy DOMDocument class)
*
* #var ExtendedDOMDocument
*/
$dom;
/**
* XPath responsible for handling $dom.
*
* #var DOMXPath
*/
$xpath;
/**
* List of extracted DOM nodes.
*
* #var DOMNodeList
*/
$nodes;
/**
* Helper variable containing current resource URI.
*
* #var string
*/
$resource = '';
/**
* Helper variable containing all the results from regex matches.
*
* #var array
*/
$matches = array();
/**
* Array of resource URIs with their corresponding fingerprint versions.
*
* #var array
*/
$fingerprints = array();
// In case $content is not provided false is returned.
if(!strlen($content)){
return false;
}
// Loading $content into DOMDocument parser.
$dom = new ExtendedDOMDocument();
$dom->loadHTML($content);
// Extracting <script> and <link> nodes.
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//script|//link');
// Traversing the extracted nodes in order to find out the exact names of the CSS and JavaScript resources and then create the appropriate fingerprint.
foreach($nodes as $node){
//Only local resources with specified "src" or "href"" are taken into account.
switch($node->getAttribute('type')){
case 'text/javascript' :
$resource = $node->getAttribute('src');
break;
case 'text/css' :
$resource = $node->getAttribute('href');
break;
default:
// In case no type is specified we probe for either "src" or "href" but if nothing is found we skip this node.
/**
* Value of the 'src' attribute for the current node, if any.
*
* #var string
*/
$src = $node->getAttribute('src');
/**
* Value of the 'href' attribute for the current node, if any.
*
* #var string
*/
$href = $node->getAttribute('href');
if(strlen($src) && strpos($src, '.js')){
$resource = $src;
} else if(strlen($href) && strpos($href, '.css')) {
$resource = $href;
} else {
//No luck? Skipping the current node.
continue;
}
}
// Generating fingerprint pairs.
if(!strlen(trim($resource)) || (stripos($resource, 'http://') !== false) || (stripos($resource, 'https://') !== false)){
// Skipping inline and remote scripts.
continue;
} else {
// Fingerprinting resources...
preg_match('/(.+)\.(css|js)/', $resource, $matches);
if(file_exists(APP_FOLDER . $matches[0])){ // Resource exists.
$fingerprints[] = array(
'original' => $resource,
'fingerprinted' => $matches[1] . '-' . filemtime(APP_FOLDER . $matches[0]) . '.' . $matches[2]
);
}
}
}
// Time to make fingerprint pair replacements.
foreach($fingerprints as $pair){
$content = str_replace($pair['original'], $pair['fingerprinted'], $content);
}
return $content;
}
}
You can use the class as simple as calling it anywhere in the code.
$output = CacheBuster::fingerprint($MARKUP);
I think this is a classic case off "unintentionall" serverside caching. That meens that the server keeps a copy of your files to shorten the clients loading time.
Depening on the caching solution you could possibly turn the caching of with .htaccess.
Hope this helps!

Rendering an email throws a TemplateCompilationException using RazorEngine 3 in a non-MVC project

I am trying to render emails in a windows service host.
I use RazorEngine 3 forked by coxp which has support for Razor 2.
https://github.com/coxp/RazorEngine/tree/release-3.0/src
This works fine for a couple of emailtemplates but there is one causing me problems.
#model string
Click here to enter a new password for your account.
This throws a CompilationException: The name 'WriteAttribute' does not exist in the current context. So passing in a string as model and putting it in the href-attribute causes problems.
I can make it work by changing this line by:
#Raw(string.Format("Klik hier.", #Model))
but this makes the template very unreadable and harder to pass along to a marketing department for further styling.
I like to add that referencing the RazorEngine by using a Nuget package is not a solution since it is based on Razor 1 and somewhere along the process the DLL for system.web.razor gets replaced by version 2 which breaks any code using RazorEngine. It seems more interesting to use Razor 2 to benefit from the new features and to be up to date.
Any suggestions on how to fix this would be great. Sharing your experiences is also very welcome.
UPDATE 1
It seems like calling SetTemplateBaseType might help, but this method does not exist anymore, so I wonder how to be able to bind the templatebasetype?
//Missing method in the new RazorEngine build from coxp.
Razor.SetTemplateBaseType(typeof(HtmlTemplateBase<>));
I use Windsor to inject the template service rather than using the Razor object. Here is a simplified part of the code that shows how to set the base template type.
private static ITemplateService CreateTemplateService()
{
var config = new TemplateServiceConfiguration
{
BaseTemplateType = typeof (HtmlTemplateBase<>),
};
return new TemplateService(config);
}
RazorEngine 3.1.0
Little bit modified example based on coxp answer without the injection:
private static bool _razorInitialized;
private static void InitializeRazor()
{
if (_razorInitialized) return;
_razorInitialized = true;
Razor.SetTemplateService(CreateTemplateService());
}
private static ITemplateService CreateTemplateService()
{
var config = new TemplateServiceConfiguration
{
BaseTemplateType = typeof (HtmlTemplateBase<>),
};
return new TemplateService(config);
}
public static string ParseTemplate(string name, object model)
{
InitializeRazor();
var appFileName = "~/EmailTemplates/" + name + ".cshtml";
var template = File.ReadAllText(HttpContext.Current.Server.MapPath(appFileName));
return RazorEngine.Razor.Parse(template, model);
}