What is the proper way to add a custom dashboard "box" in the Magento backend without editing default templates? - templates

I am working on creating what I hope one day will be a publicly available Magento extension (this part I mention because it's important to me that I do the "right thing" here). One of the things I would like to do is add a box in the default Magento dashboard, basically a new "box" exactly like "Top 5 Search Terms" except with my own content. I would like my new custom box to be the last box that is displayed (ideally).
The issue that I'm running into is that the template that is responsible for rendering the dashboard calls out specific blocks to be rendered, and these blocks are nested inside of html. In other words, where often there is an area where child blocks will be rendered into a nice reasonable HTML element, in this situation it appears that specific blocks are rendered. Here is the contents of /app/design/adminhtml/default/default/template/dashboard/index.phtml
<div class="dashboard-container">
<?php echo $this->getChildHtml('store_switcher') ?>
<table cellspacing="25" width="100%">
<tr>
<td><?php echo $this->getChildHtml('sales') ?>
<div class="entry-edit">
<div class="entry-edit-head"><h4><?php echo $this->__('Last 5 Orders') ?></h4></div>
<fieldset class="np"><?php echo $this->getChildHtml('lastOrders'); ?></fieldset>
</div>
<div class="entry-edit">
<div class="entry-edit-head"><h4><?php echo $this->__('Last 5 Search Terms') ?></h4></div>
<fieldset class="np"><?php echo $this->getChildHtml('lastSearches'); ?></fieldset>
</div>
<div class="entry-edit">
<div class="entry-edit-head"><h4><?php echo $this->__('Top 5 Search Terms') ?></h4></div>
<fieldset class="np"><?php echo $this->getChildHtml('topSearches'); ?></fieldset>
</div>
</td>
<td>
<div class="entry-edit" style="border:1px solid #ccc;">
<?php echo $this->getChildHtml('diagrams') ?>
<?php if (is_array($this->getChild('diagrams')->getTabsIds())) : ?>
<div id="diagram_tab_content"></div>
<?php endif; ?>
<div style="margin:20px;">
<?php echo $this->getChildHtml('totals') ?>
</div>
<div style="margin:20px;">
<?php echo $this->getChildHtml('grids') ?>
<div id="grid_tab_content"></div>
</div>
</div>
</td>
</tr>
</table>
</div>
If I was doing this in my own store, I believe I could achieve this relatively easily by editing the base Magento dashboard template index.phtml above, add what I need to have my block render, something like:
<div class="entry-edit">
<div class="entry-edit-head">
<h4><?php echo $this->__('Top 5 Search Terms') ?></h4></div>
<fieldset class="np"><?php echo $this->getChildHtml('myDashboardBox'); ?></fieldset>
</div>
</div>
But, this isn't my own store, so this doesn't really seem like an option.
Now, after some thought my options seem to be as follows (note that most of these seem "bad" and not something I'd be super proud to have seen in the public):
0) Something obvious that I'm not seeing that you will tell me is the perfect/right solution
1) I might (maybe?) be able to add my custom block inside of one of these other blocks in this area ("topSearches", "sales", etc) and have my block rendered. This does not seem very "clean"
2) I might be able to have the block rendered somewhere else on the dashboard page, and then move it with javascript to the correct place. This would be fairly easy I'm guessing, but feels very "hacky" for obvious reasons.
Does anybody have any feedback on the way to do this, or IF there is a way? Keep in mind that again I would like to release this module publicly so my goal is to do a good job and do as little "hacking" as possible.
Thank you very much for reading!

There is no really clean option, as you said, the template is coded in a non-extendable way, so there will always be some degree of hackiness. This is my personal preferred way of doing it by using event observers. This way it at least doesn't conflict with other modules.
First, add an observer for the core_block_abstract_prepare_layout_after and core_block_abstract_to_html_after event.
<adminhtml>
<events>
<core_block_abstract_prepare_layout_after>
<observers>
<your_module>
<class>your_module/observer</class>
<method>coreBlockAbstractPrepareLayoutAfter</method>
</your_module>
</observers>
</core_block_abstract_prepare_layout_after>
<core_block_abstract_to_html_after>
<observers>
<your_module>
<class>your_module/observer</class>
<method>coreBlockAbstractToHtmlAfter</method>
</your_module>
</observers>
</core_block_abstract_to_html_after>
</events>
</adminhtml>
These two events are dispatched for every block that is instantiated and rendered in Mage_Core_Block_Abstract. In my experience it's not such an issue using them in the adminhtml interface, but on the frontend observers for these events add too much overhead.
Back to the task at hand, you need to create the observer class.
class Your_Module_Model_Observer
{
public function coreBlockAbstractPrepareLayoutAfter(Varien_Event_Observer $observer)
{
if (Mage::app()->getFrontController()->getAction()->getFullActionName() === 'adminhtml_dashboard_index')
{
$block = $observer->getBlock();
if ($block->getNameInLayout() === 'dashboard')
{
$block->getChild('topSearches')->setUseAsDashboardHook(true);
}
}
}
public function coreBlockAbstractToHtmlAfter(Varien_Event_Observer $observer)
{
if (Mage::app()->getFrontController()->getAction()->getFullActionName() === 'adminhtml_dashboard_index')
{
if ($observer->getBlock()->getUseAsDashboardHook())
{
$html = $observer->getTransport()->getHtml();
$myBlock = $observer->getBlock()->getLayout()
->createBlock('you_module/block')
->setTheValuesAndTemplateYouNeed('HA!');
$html .= $myBlock->toHtml();
$observer->getTransport()->setHtml($html);
}
}
}
}
Your template will need to accomodate for the fact that you are inserting a sibling <div> from inside the sibling, but otherwise you should be fine.
</fieldset></div>
<div class="entry-edit">
<div class="entry-edit-head"><h4>Your Module</h4></div>
<fieldset class="np">Your Content
Leave it at that, because the parent template will be closing the <fieldset> and the <div> for you (ugly as heck, I know).

Related

Drupal 8 - Broken default image link in view

I'm new to Drupal and I've been given a project to fix a couple of bugs on...
I've got a view with several fields, it displays OK when there is a picture but when there is no picture (and thus the need to display a default picture), then a broken image link appears...For some reason the path points to a different directory...
This works:
<div class="item-header"> <div class="views-field views-field-field-selection-photos"> ... <div class="content">
<div class="field field--name-field-photo field--type-image field--label-hidden field--item"> <img src="/sites/default/files/somepicture.jpeg?itok=sJ6SOjXo" width="445" height="334"/>
This does not, due to the path pointing to ../sites instead of /sites:
<div class="item-header">
<div class="views-field views-field-field-selection-photos"><div class="field-content"><img src="../sites/default/files/structures/photos/defaultpic.jpg" width="100%" />
I've been looking at theming fields but that seems overkill, I wonder if there's a nice and clean way to sort this out rather than override a template or anything else that would seem a bit too much...
Thanks

Inner renderings not displaying

In a page, I have a rendering with a placeholder, Offcanvas.cshtml
<div data-ads-offcanvas-id="#Model.Id" class="ads-offcanvas #Html.GetCssStyles()">
#{
<a data-ads-offcanvas-trigger-id="#Model.Id" class="ads-close-button" href="#0">X</a>
#Html.Sitecore().Placeholder(LayoutHelper.GetPlaceholder(Placeholders.AccordionSection, Model.Id))
}
and inside that rendering, I plan to put another rendering with a placeholder named Modal.cshtml
<div data-ads-modal-container="#Model.Id" class="ads-modal-container">
<div class="ads-modal-dialog">
<div class="ads-close-box">
<a data-ads-modal-id="#Model.Id" href="#0">X</a>
</div>
<div class="ads-modal-content">
#Html.Sitecore().Placeholder(LayoutHelper.GetPlaceholder(Placeholders.AccordionSection, Model.Id))
</div>
</div>
So it looks like:
-Offcanvas
+-Modal
++-Text content
When I put text content inside the Modal rendering, the content is not rendered. I'm guessing that it only renders that first few renderings and fail to do so when the renderings became highly nested.
Is there a solution to render components to the very-most child?
Thanks guys!
EDIT:
Here's the code for GetPlaceholder:
public static string GetPlaceholder(string name, Guid id)
{
return $"{name}_{id}";
}
Dynamic placeholders are a bit more complicated than generating a unique placeholder key. The generated key still needs to resolve back to a real placeholder that is defined in Sitecore.
Check out the Integrated Dynamic Placeholder module.
This will provide an Html helper similar to your current implementation:
#Html.Sitecore().DynamicPlaceholder("[Placeholder Name]")

How to add a PHP if statement within an echo?

I would like to place an if statement within an echo and I am not quite sure how to do it. Here is the echo:
if(!$hideProduct) {
echo '
<div class="gearBorder">
<img title="Sold Out" alt="Sold Out" class="soldOut" src="soldout.png" />':"").'
<div class="gearInfo">
<h4>' . $productName . '</h4>
<p class="gearDesc">'. $productDescription .'</p>
<p class="cost">$' . $productPrice . '</div>
</div>
</div>
';}
On line 3, I would like to wrap the image in an if statement:
if($productStatus = '0') {
}
What would be the best way to wrap the image in that statement? Thanks!
You can actually end control flow blocks like if statements outside of the same PHP block they were opened in. For example, this should work:
<?php if (!$hideProduct) { ?>
<div class="gearBorder">
<?php if ($productStatus == '0') { ?>
<img title="Sold Out" ... />
<?php } ?>
...HTML...
</div>
<?php } ?>
If you don't like the curly braces, you can also replace them with a colon (:) and endif, respectively. See this link for more information.
Use an array to hold the CSS classes (with background-image) for each $productStatus
Fast and efficient. There is a performance hot when you toggle from HTML mode to PHP mode. This method eliminates the if elseif performance hit in the Intel micro code.
<style type="text/css">
.soldout{width:40px;height:40px;background-image: url('soldout.png');}
.backorder{width:40px;height:40px;background-image: url('backorder.png');}
.instock{width:40px;height:40px;background-image: url('instock.png');}
</style>
$icon = array(' class="soldout" ',' class="backorder" ',' class="instock" ');.
echo '<div class="gearBorder"><div ' . $icon[$productStatus] . '></div><div class="gearInfo"> ... ';
I would also use a 4-bit color GIF icon and convert it to Base64 MIME
Make sure page is served with gZip and there will be little to no penalty for the Base64.
If you want to stick with image files, make sure images are served with a large cache max-age value.
background-image: url('data:image/gif;base64,R0lGODlhKAAoAK...');

Incorporating slideshow / carousel into custom tumblr theme

Got a very particular problem here:
I've been developing a tumblr-hosted site locally, using the API to pull in posts without having to copy and paste the project into tumblr a million times. I decided I liked the API better and would just use that in production, but now that it's time to deploy I realize that I have to go back to the custom theme, {block:Posts} method.
I have the post feeding into a Cycle2 slideshow, with 3 slides containing 3 posts each for a total of 9 playlists viewable without going back to the archive. This method works perfectly with the api, but is getting messed up in the custom theme. Here's my current code:
<div class="cycle-slideshow">
{block:Posts}
{block:Text}
<div class="slide-wrapper">
<div class="post">
{block:Post1}
{block:Title}<h2>{Title}</h2>{/block:Title}
<div class="blog_item">
{Body}
</div>
{/block:Post1}
</div>
<!--two more posts before end of slide... -->
</div>
{/block:Text}
{/block:Posts}
</div> <!--end of slide wrapper - 2 more of these before end of slideshow div..
I also tried scrapping the post numbers, but still no dice. In tumblr's docs, they say that
Example: {block:Post5}I'm the fifth post!{/block:Post5} will only be rendered on the fifth post being displayed.
I'm wondering if "being displayed" refers to the html visibility of the post, and if so, if that's interfering with the cycle plugin? The results are one ill-formatted post per slide, and then after cycling through 2 blank slides, the next oldest post takes its place. I'll be pleasantly surprised if anybody has ever had a similar problem but I would kill for some advice. Here's the development site for reference (and the second carousel is working because it's still hooked up to the api). thanks!!
Generally speaking, the following code is what you'd want to have 3 slideshows with 3 posts each.
Note that in the Additional Settings on the Customize screen, you'd have to set the post count to 9 per page in order for this to work properly. I wrapped it in an Index Page block, otherwise this is going to look nasty on a Permalink Page.
{block:IndexPage}
{block:Posts}
{block:Post1}<div class="cycle-slideshow">{/block:Post1}
{block:Post4}<div class="cycle-slideshow">{/block:Post4}
{block:Post7}<div class="cycle-slideshow">{/block:Post7}
<div class="slide-wrapper">
{block:Text}
<div class="post">
{block:Title}<h2>{Title}</h2>{/block:Title}
<div class="blog_item">
{Body}
</div>
</div>
{/block:Text}
{block:Photo}
...
{/block:Photo}
...
</div>
{block:Post3}</div>{/block:Post3}
{block:Post6}</div>{/block:Post6}
{block:Post9}</div>{/block:Post9}
{/block:Posts}
{/block:IndexPage}
However, if you're wanting 3 slideshows with the post types split between the slideshows, the code would look more like the following.
Note that in this scenario, if you were to have 4 texts posts out of 9, all 4 text posts would end up in the Text slideshow. You'd have to use Javascript or CSS to remove or hide the additional posts if you're very strict about your 3.
{block:IndexPage}
<div class="cycle-slideshow">
{block:Posts}
{block:Text}
<div class="slide-wrapper">
<div class="post">
{block:Title}<h2>{Title}</h2>{/block:Title}
<div class="blog_item">
{Body}
</div>
</div>
</div>
{/block:Text}
{/block:Posts}
</div>
<div class="cycle-slideshow">
{block:Posts}
{block:Photo}
<div class="slide-wrapper">
...
</div>
{/block:Photo}
{/block:Posts}
</div>
{/block:IndexPage}
If you need me to clarify anything, let me know.

if else statement in AngularJS templates

I want to do a condition in an AngularJS template. I fetch a video list from the Youtube API. Some of the videos are in 16:9 ratio and some are in 4:3 ratio.
I want to make a condition like this:
if video.yt$aspectRatio equals widescreen then
element's attr height="270px"
else
element's attr height="360px"
I'm iterating the videos using ng-repeat. Have no idea what should I do for this condition:
Add a function in the scope?
Do it in template?
Angularjs (versions below 1.1.5) does not provide the if/else functionality . Following are a few options to consider for what you want to achieve:
(Jump to the update below (#5) if you are using version 1.1.5 or greater)
1. Ternary operator:
As suggested by #Kirk in the comments, the cleanest way of doing this would be to use a ternary operator as follows:
<span>{{isLarge ? 'video.large' : 'video.small'}}</span>
2. ng-switch directive:
can be used something like the following.
<div ng-switch on="video">
<div ng-switch-when="video.large">
<!-- code to render a large video block-->
</div>
<div ng-switch-default>
<!-- code to render the regular video block -->
</div>
</div>
3. ng-hide / ng-show directives
Alternatively, you might also use ng-show/ng-hide but using this will actually render both a large video and a small video element and then hide the one that meets the ng-hide condition and shows the one that meets ng-show condition. So on each page you'll actually be rendering two different elements.
4. Another option to consider is ng-class directive.
This can be used as follows.
<div ng-class="{large-video: video.large}">
<!-- video block goes here -->
</div>
The above basically will add a large-video css class to the div element if video.large is truthy.
UPDATE: Angular 1.1.5 introduced the ngIf directive
5. ng-if directive:
In the versions above 1.1.5 you can use the ng-if directive. This would remove the element if the expression provided returns false and re-inserts the element in the DOM if the expression returns true. Can be used as follows.
<div ng-if="video == video.large">
<!-- code to render a large video block-->
</div>
<div ng-if="video != video.large">
<!-- code to render the regular video block -->
</div>
In the latest version of Angular (as of 1.1.5), they have included a conditional directive called ngIf. It is different from ngShow and ngHide in that the elements aren't hidden, but not included in the DOM at all. They are very useful for components which are costly to create but aren't used:
<div ng-if="video == video.large">
<!-- code to render a large video block-->
</div>
<div ng-if="video != video.large">
<!-- code to render the regular video block -->
</div>
Ternary is the most clear way of doing this.
<div>{{ConditionVar ? 'varIsTrue' : 'varIsFalse'}}</div>
Angular itself doesn't provide if/else functionality, but you can get it by including this module:
https://github.com/zachsnow/ng-elif
In its own words, it's just "a simple collection of control flow directives: ng-if, ng-else-if, and ng-else." It's easy and intuitive to use.
Example:
<div ng-if="someCondition">
...
</div>
<div ng-else-if="someOtherCondition">
...
</div>
<div ng-else>
...
</div>
You could use your video.yt$aspectRatio property directly by passing it through a filter, and binding the result to the height attribute in your template.
Your filter would look something like:
app.filter('videoHeight', function () {
return function (input) {
if (input === 'widescreen') {
return '270px';
} else {
return '360px';
}
};
});
And the template would be:
<video height={{video.yt$aspectRatio | videoHeight}}></video>
In this case you want to "calculate" a pixel value depending of an object property.
I would define a function in the controller that calculates the pixel values.
In the controller:
$scope.GetHeight = function(aspect) {
if(bla bla bla) return 270;
return 360;
}
Then in your template you just write:
element height="{{ GetHeight(aspect) }}px "
I agree that a ternary is extremely clean. Seems that it is very situational though as somethings I need to display div or p or table , so with a table I don't prefer a ternary for obvious reasons. Making a call to a function is typically ideal or in my case I did this:
<div ng-controller="TopNavCtrl">
<div ng-if="info.host ==='servername'">
<table class="table">
<tr ng-repeat="(group, status) in user.groups">
<th style="width: 250px">{{ group }}</th>
<td><input type="checkbox" ng-model="user.groups[group]" /></td>
</tr>
</table>
</div>
<div ng-if="info.host ==='otherservername'">
<table class="table">
<tr ng-repeat="(group, status) in user.groups">
<th style="width: 250px">{{ group }}</th>
<td><input type="checkbox" ng-model="user.groups[group]" /></td>
</tr>
</table>
</div>
</div>
<div ng-if="modeldate==''"><span ng-message="required" class="change">Date is required</span> </div>
you can use the ng-if directive as above.
A possibility for Angular:
I had to include an if - statement in the html part, I had to check if all variables of an URL that I produce are defined. I did it the following way and it seems to be a flexible approach. I hope it will be helpful for somebody.
The html part in the template:
<div *ngFor="let p of poemsInGrid; let i = index" >
<a [routerLink]="produceFassungsLink(p[0],p[3])" routerLinkActive="active">
</div>
And the typescript part:
produceFassungsLink(titel: string, iri: string) {
if(titel !== undefined && iri !== undefined) {
return titel.split('/')[0] + '---' + iri.split('raeber/')[1];
} else {
return 'Linkinformation has not arrived yet';
}
}
Thanks and best regards,
Jan
ng If else statement
ng-if="receiptData.cart == undefined ? close(): '' ;"