Block layout is different when called outside Magento - layout

I used snippet suggested here
Load block outside Magento, and apply current template
to display a block outside Magento.
Here is my code:
Mage::getSingleton('core/session', array('name'=>'frontend'));
$layout = Mage::app()->getLayout();
$layout->getUpdate()
->addHandle('default')
->load();
$layout->generateXml()
->generateBlocks();
$header = $layout->getBlock('header')->toHtml();
echo $header;
The header is printed, but the topLinks are different from those displayed in Magento.
Even more, the html is slightly different (some divs are missing).
This is my XML layout:
<block type="page/html_header" name="header" as="header">
<block type="core/text_list" name="top.menu" as="topMenu" translate="label">
<label>Navigation Bar</label>
</block>
<block type="page/switch" name="store_language" as="store_language" template="page/switch/languages.phtml"/>
<block type="page/template_links" name="top.links" as="topLinks"/>
<block type="page/html_wrapper" name="top.bar" as="topBar" translate="label">
<label>Breadcrumbs</label>
<action method="setElementClass"><value>top-bar</value></action>
<block type="page/html_breadcrumbs" name="breadcrumbs" as="breadcrumbs"/>
</block>
<block type="page/html_wrapper" name="top.container" as="topContainer" translate="label">
<label>Page Header</label>
<action method="setElementClass"><value>top-container</value></action>
</block>
</block>
What's wrong?
UPDATE
Thanks to Alan Storm's suggestion, now I know the error is not setting correct handles.
I need to add customer_logged_in or customer_logged_out to $layout.
I've tried with
Mage::getSingleton('core/session', array('name'=>'frontend'));
$session = Mage::getSingleton('customer/session', array('name'=>'frontend'));
$login_status = '';
if($session->isLoggedIn()){
$login_status = 'customer_logged_in';
} else {
$login_status = 'customer_logged_out';
}
But my user results always logged out even when it's logged in.
What am I missing?

For starters, you've only loaded a single layout handle. Besides the default handle, every Magento page request loads a handle for the store (such as STORE_english ) and a handle indicating if the customer is logged in or out (such as customer_logged_out). Without these handles, certain things don't happen, and the final rendered page is going to look different.

Related

Using global actions with multiple graphs in jetpack navigation

I created an application with a bottom navigation bar and added four different nav graphs using google's advanced navigation example
After that I added a fifth graph called settings that had the settings fragment along with a global action
I added an include to that graph on each of the first four graphs
when I do something like findNavController(R.id.container).navigate(SettingsDirections.showSettings()) the app crashes because it cannot find the destination or the action
but when I copy the fragment and the global action inside each of those graphs and call the above (with that graph's directions) it works
am I missing something? doesn't include actually copy everything from the other graph the original?
It seems that the include tag does not actually include all of the nav graph including the global actions
so in case someone wants to do something similar here is how I did it
first I updated the settings navigation to include a dummy action to show settings like so:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/settings"
app:startDestination="#+id/settings_dest">
<include app:graph="#navigation/questions" />
<!--this is just so safeargs will create a SettingsNavigation.showSettings it is never actually used-->
<action
android:id="#+id/show_settings"
app:destination="#id/settings_dest" />
<fragment
android:id="#+id/settings_dest"
android:name="com.example.app.ui.fragments.SettingsFragment"
android:label="#string/settings"
tools:layout="#layout/fragment_settings" >
<argument
app:argType="com.example.app.ui.model.SettingsProfile"
android:name="profile"
app:nullable="false"/>
</fragment>
</navigation>
and then on every nav graph that I wanted to use the show_settings action I would do by including this:
<include app:graph="#navigation/settings" />
<action
android:id="#+id/show_settings"
app:destination="#id/settings"
app:enterAnim="#anim/fade_in"
app:exitAnim="#anim/fade_out"
app:popEnterAnim="#anim/fade_in"
app:popExitAnim="#anim/fade_out" />
so now when I want to use directions to go to settings I do it like this
findNavController().navigate(SettingsDirections.showSettings(profile))
this will use the directions created in my settings.xml to execute the action in my current nav controller
it is important by the way the id of the action in the included file and the id of the action of the file including it to be the same
Let me show you with an example
<navigation ...
app:startDestination:"#id/nav_graph_one">
<include app:graph:"#navigation/nav_graph_one"/>
<include app:graph:"#navigation/nav_graph_two"/>
<fragment
android:id="#+id/aCommonFragment"
.../>
<action
android:id="#+id/action_grobal_aCommonFragment
app:destination="#+id/aCommonFragment"/>
</navigation>
Any where from your app you can now use
findNavController().navigate(NavGraphDirections.actionGlobalACommonFragment)
By copying nodes and actions from global actions graph to main graph in runtime it is possible to use global actions in all subgraphs and stay them in separate file.
main graph:
<navigation android:id="#+id/main_graph">
<include app:graph="#navigation/subgraph1"/>
<include app:graph="#navigation/subgraph2"/>
<navigation/>
global actions graph:
<navigation android:id="#+id/customer_dialogs">
<action android:id="#+id/actionToShareCustomer"
android:destionation="#id/shareCustomerDialog"/>
<dialog android:id="#+id/shareCustomerDialog">
<argument android:id="#+id/customerId" app:argType="long"/>
</dialog>
<navigation/>
programmatically set main_graph to navController:
override fun onCreate(savedInstanceState: Bundle?) {
with(navHostFragment.findNavController()) {
val mainGraph = navInflater.inflate(R.navigation.main_graph)
val actionsGraph = navInflater.inflate(R.navigation.customer_dialogs)
copyGraph(actionsGraph, mainGraph)
setGraph(mainGraph, null)
}
}
fun copyGraph(from: NavGraph, to: NavGraph) {
to.addAll(from)
val actions = NavDestination::class.java.getDeclaredField("actions").let {
it.isAccessible = true
it.get(from)
} as? SparseArrayCompat<NavAction>
actions?.forEach { actionId, action -> to.putAction(actionId, action) }
}
usage of action from fragment of subgraph1:
private fun shareCustomer(customerId: Long) {
findNavController().navigate(CustomerDialogsDirections.actionToShareCustomer(customerId))
}
I created an extension function when setting the default animation
fun View.navigate(id: Int, navOptions: NavOptions? = null){
var updatedNavOptions = navOptions
if(updatedNavOptions == null){
updatedNavOptions = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
}
this.findNavController().navigate(id, null, updatedNavOptions)
}
In my fragment just do so:
my_view.click { view?.navigate(R.id.how_referral_program_works) }

How to get the value of a cms:FormField in a form layout script block?

I have a form that has a layout like so:
<cms:FormField runat="server" ID="fMemberType" Field="MemberType" />
<cms:FormField runat="server" ID="fEmployeeCount" Field="EmployeeCount" />
<asp:Literal runat="server" ID="test" Text="test" />
<script runat="server">
protected void Page_PreRender(object sender, EventArgs e)
{
test.Text = fMemberType.Value.ToString();
}
</script>
However this produces Object reference not set to an instance of an object. because it can't find fMemberType for some reason. Looking for the correct way of doing this.
It's worth noting that the form fields are dropdowns with depending flags set so changing them triggers a postback, or at least it would, but I set the webpart container to be an update panel so it's AJAXing which means the data isn't available in the page POST params. I could turn this off and grab the data from the POST data but wanted to know if there was a better way first.
So you're fully defining the fields and everything for your form? Why not use the DataForm control and dynamically create the form for you? You can then get the data like so: (formUserSettings is a cms:DataForm)
EditingFormControl ctrState = formUserSettings.BasicForm.FieldEditingControls["UserState"] as EditingFormControl;
Then do some checking and assign the value:
if (ctrState != null)
{
fState = ctrlState.Value;
}
Most likely the form value is not set until after the pre-render. Alen Genzic's recommendation will show that. May want to try OnInit.

How to create a declarative HTML helper in MVC 5

my scenario:
I am finally getting around to creating my own blog, and I am trying to learn as much as possible with regards to MVC while doing so. I am trying to display my tags as a custom declarative helper in my "PostView.cshtml" file but my problem is that it isn't in the current context and I don't know how to make it so.
I have had a look at the following 2 questions on SO:
this one is for previous version of MVC (<= 4) and
this one was answered by the guy who asked the question and isn't very explanatory.
I tried the above advice but with no success, hopefully someone can help me out. Here is my code:
Tags.cshtml (in ~/Views/Helpers/):
#helper Tags(System.Web.Mvc.HtmlHelper htmlHelper,
ICollection<MyNamespace.Objects.Tag> tags)
{
foreach (var tag in tags)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag):
</div>
}
}
ActionLinkExtensions.cs (in ~/Extensions/ActionLinkExtensions/)
namespace MyNamespace.Extensions
{
public static class ActionLinkExtensions
{
public static MvcHtmlString TagLink(this HtmlHelper helper, Tag tag)
{
return helper.ActionLink("", ""); //logic removed for simplicity
}
}
}
PostView.cshtml (in ~/Views/Shared/) //where i want to use my custom helper:
#model MyNamespace.Objects.Post
<!--extra html removed for simplicity-->
<div>
<span>Tags:</span>#Tags(Html, Model.Tags) // '#Tags' doesn't exist in current context
</div>
I also tried adding namespaces to '~/Views/web.config':
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="MyNamespace" />
<add namespace="MyNamespace.Extensions" />
</namespaces>
</pages>
My "full name" for my "Tag.cs" class is MyNamespace.Objects.Tag and "Post".cs" is MyNamespace.Objects.Post.
Any explanations and advice with an answer would be greatly appreciated too, thank you very much in advance.
I decided to try use MVC3 way, I added the App_Code folder manually and followed steps from this great article.
And it worked, I needed to restart Visual Studio for my Intellisense to work (which prolonged finding my solution).
I deleted the folder '~/Views/Shared/'
I Added a file MyHelpers.cshtml into the App_Code folder, inside the file I added my helper method:
#helper Tags(System.Web.Mvc.HtmlHelper htmlHelper,
ICollection<MyNamespace.Objects.Tag> tags)
{
foreach (var tag in tags)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag)
</div>
}
}
And called it in my PostView.cshtml like so:
#MyHelpers.Tags(Html, Model.Tags)
And Viola works as expected... hopefully this helps someone else who ends up in this situation...
I believe the better and simpler way would be to define a display template for you Tags collection that would be placed in ~Views/Shared/DisplayTemplates:
#model ICollection<MyNamespace.Objects.Tag>
foreach (var tag in Model)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag)
</div>
}
In your PostView.cshtml you would then just write:
#Html.DisplayFor(model => model.Tags)

How to 'force' Magento to load a different layout

Background: I need to be able to load upsell / crosssell products in a lightbox complete with add-to-cart functionality.
My idea for achieving this was to 'force' Magento to load products in a different layout. I thought of using an observer on the controller_action_layout_generate_xml_before event (code below).
Unfortunately what I have is not working. Any pointers (or completely different / better ideas) are much appreciated.
<?php
class My_ForceLayout_Model_Observer
{
public function changeLayoutEvent($observer)
{
$action = $observer->getEvent()->getAction();
$layout = $observer->getEvent()->getLayout();
if($action->getRequest()->getControllerName() == 'product'
&& $action->getRequest()->getActionName() == 'view')
{
$update = $layout->getUpdate();
$update->load('popup'); // for testing only
$layout->generateXml();
}
}
}
I managed to get this to work exactly as I first intended. Thanks to #Jonathan Day for making me realize the reason it was not working was trivial.
Config.xml:
<config>
....
<frontend>
<events>
<controller_action_layout_generate_blocks_before>
<observers>
<forcelayout>
<type>singleton</type>
<class>forcelayout/observer</class>
<method>changeLayoutEvent</method>
</forcelayout>
</observers>
</controller_action_layout_generate_blocks_before>
</events>
</frontend>
....
</config>
Observer.php:
class Unleaded_ForceLayout_Model_Observer
{
public function changeLayoutEvent($observer)
{
$action = $observer->getEvent()->getAction();
$layout = $observer->getEvent()->getLayout();
if($action->getRequest()->getControllerName() == 'product'
&& $action->getRequest()->getActionName() == 'view')
{
$template = $action->getRequest()->template;
if (isset($template) && $template != '')
{
$update = $layout->getUpdate();
$update->load($template);
$layout->generateXml();
}
}
}
}
Local.xml:
<popup translate="label">
<label>Catalog Product View Lightbox</label>
<remove name="right"/>
<remove name="left"/>
<reference name="root">
<action method="setTemplate">
<template>page/popup.phtml</template>
</action>
</reference>
<reference name="content">
<remove name="product.info.upsell"/>
</reference>
</popup>
Product url in .phtml file:
echo $this->getProductUrl($_item) . '?template=popup';
Why don't you want to use just regular layout udates?
<catalog_product_view translate="label">
<label>Catalog Product View (Any)</label>
<!-- Mage_Catalog -->
<remove name="right"/>
<remove name="left"/>
<reference name="content">
<block type="new_catalog/product_view"
name="new.product.info"
template="new/catalog/product/view_popup.phtml">
...
</block>
</reference>
</catalog_product_view>
If you want to change the design of your product page depends on some conditions, you could use layout handler functionality. It means that you have to check your parameters in controller and add handler for layout updates, then you could use it in layout file as any other handler. For example:
if ($this->check_parameters()) {
$update->addHandle('new_magic_handler');
$this->loadLayoutUpdates();
}
And in layout:
<new_magic_handler translate="label">
<label>New Magic</label>
...
</new_magic_handler>
Check for details Mage_Catalog_ProductController::_initProductLayout()

Aggregating local events in SCXML

My state machine has a self loop every time some request event is created. I want to store these events in a local context list against a key and everytime this self loop is executed an element is appended to the list. Then this list after a certain expiry period ,of say 1 Hour , is added to global context of SCXML. How can I achieve this?
Basically I want to aggregate the requests before I trigger a particular action.
<state id="S02" label="REQUEST CREATED">
<onentry>
<action:trigger id="ACC1" name="EXPIRY_EVENT_expiry.sm00007" />
</onentry>
<transition event="expiry.sm00007" target="S03">
<action:trigger id="ACC2" name="TO_DO_SOMETHING" />
// add the local event list to global context
</transition>
<transition event=reqCreated" target="S02" >
// keep adding the event to local context like appending to list
</transition>
</state>
In the SCXML spec, all datamodel variables are global so there's not really a "local" context. But you could use a key to index into a JavaScript object. Something like:
<datamodel>
<data id="globalEventList"/>
<data id="localEventListMap" expr="{}"/>
<data id="localKey" expr="'foo'"/>
</datemodel>
<state id="init">
<onentry>
<script>
localEventListMap[localKey] = [];
</script>
</onentry>
<transition target="S02"/>
</state>
<state id="S02" label="REQUEST CREATED">
<onentry>
<action:trigger id="ACC1" name="EXPIRY_EVENT_expiry.sm00007" />
</onentry>
<transition event="expiry.sm00007" target="S03">
<action:trigger id="ACC2" name="TO_DO_SOMETHING" />
<script>
// add the local event list to global context
globalEventList = localEventListMap[key];
</script>
</transition>
<transition event="reqCreated" target="S02" >
<script>
// keep adding the event to local context like appending to list
localEventListMap[key].push(_event);
</script>
</transition>
</state>

Resources