global variables not parsing as parameters - expressionengine

I am having troubles with global variables not parsing when passed as parameters.
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{structure:child_ids_for:21}"
}
(0.012500 / 3.36MB) Tag: {exp:channel:entries disable="categories|category_fields|member_data|pagination|trackbacks" dynamic="no" entry_id="{structure:child_ids_for:21}" }
The same result is produced with and without parse="inward"
However this works fine and grabs the data I need
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{exp:query sql='SELECT exp_structure.entry_id,
exp_structure.parent_id,
exp_structure.lft
FROM exp_structure
WHERE parent_id = 21
ORDER BY exp_structure.lft ASC'}{entry_id}|{/exp:query}"
parse="inward"
}
But, then if I add in a global variable author_id="{logged_in_member_id}" it fails to work, if I hard code that value as 1 then it functions.
Any thoughts as to what could be happening here?

You can avoid the overhead of embeds by using Stash for this sort of thing. It has the insanely useful ability to let you explicitly state your preferred parse order for different chunks of code. In this case the first thing you'd do is store the two variables via {exp:stash:set}, then you can retrieve them in the second chunk of code via {exp:stash:get}. The magic bit is the parse priority tag; because the first item has a priority of 10 it will be executed first, which ensures the vars are available for use as channel entries parameters in the second {exp:stash:parse} tag.
{exp:stash:parse priority="10" process="end"}
{exp:stash:set}
{stash:structure_ids}{structure:sibling_ids}{/stash:structure_ids}
{stash:logged_in_member}{logged_in_member_id}{/stash:logged_in_member}
{/exp:stash:set}
{/exp:stash:parse}
{exp:stash:parse priority="20" process="end"}
{exp:channel:entries
disable="categories|category_fields|member_data|pagination|trackbacks"
dynamic="no"
entry_id="{exp:stash:get name='structure_ids'}"
author_id="{exp:stash:get name='logged_in_member'}"
parse="inward"
}
...
{/exp:channel:entries}
{/exp:stash:parse}

I can't speak for Structure's global variables, but {logged_in_member_id} is a late-parsed global variable, meaning you can't use it in a module tag parameter. I can only only assume that the same goes for the Structure variables.
You can use the CURRENT_USER constant in the author_id parameter though (docs).
Unfortunately, the solution for your {structure:child_ids_for:21} issue is to pass that as an embed variable, and put your Channel Entries loop in an embed. (I say unfortunately because embeds do incur some overhead.)
One note: parse="inward" has no effect on module tag pairs - they always parse inward. It only affects plugin tag pairs.

Related

How to refer to alternate resources in Terraform?

Terraform (as of today) has the somewhat disturbing limitation, that you cannot create a resource with an interpolated (calcuted) lifecycle attribute prevent_destroy.
Terraform: How to get a boolean from interpolation?
https://github.com/hashicorp/terraform/issues/3116
The work-around is pretty simple to code, just create 2 resources with "alternating" counts. When you have 1 "production" resource which does not allow destroying you have 0 "testing" resources which can be destroyed. Or the other way round. (See the answer to the stackoverflow question linked
above for details.)
However, this brings up a new question. When I want to refer to "the one of the alternate resources that exists for this execution", how do I do that?
In pseudo code something like
"${local.production ? "${aws_eip.commander_production.public_ip}" : "${aws_eip.commander_testing.public_ip}" }"
This pseudo code cannot work for a couple of reasons:
aws_eip.commander_production is no longer a single resource, it is a list, so you need the * syntax
one of the lists is always empty and Terraform easily complains that it cannot determine the type of an empty list. (I guess because the ternary operator requires that the alternates have the same type)
when you access into an empty list you will get an error (With C semantics the unused alternate would not be evaluated, but Terraform seems to work differently and I got errors when trying to code this)
To work around those I came up with the following hacky solution:
Extend the lists with a dummy element in the end and then refer to the
first element of the extended list. The code for this is pretty
horrible to type, but it seems to work
locals {
dummy = [ "foo" ]
}
output "0101: address" {
value = "${format("Public IP is %s", "${local.production ? "${element("${concat("${aws_eip.commander_production.*.public_ip}", "${local.dummy}")}", 0)}" : "${element("${concat("${aws_eip.commander_testing.*.public_ip}", "${local.dummy}")}", 0)}" }")}"
}
Question: What is a shorter / more elegant way to code this?
Note: I have found one answer myself, but feel welcome to contribute even better ones.
A bit shorter code is
output "0101: address" {
value = "${format("Public IP is %s", "${element("${concat("${aws_eip.commander_production.*.public_ip}", "${aws_eip.commander_testing.*.public_ip}")}", 0)}")}"
}
In plain text: Concatenate the lists and take the first element of the the result. One list has one element and the other one zero, so the result will be what we want, regardless whether the element is in the first or second list.

Set temporary variable and reuse it further down in pug template

I am trying to pass along some values from my nodejs/express backend into the template (using res.render({...})), store it in some variables, then pass it further up until the front end (The usecase is that I need to do some mild calculation on the passed values and stored them in some intermediate variables)
I know I can do this in the pug file
block append script
script.
const foo = parseInt(#{stat.get('fooStat')}, 10);
But then later it seems that I can not refer to this later in my pug template, say here
span #{foo}
The value is empty, I guess because it is undefined... What's the usual way to accomplish that.
Adding the period after the script tag signals to pug that its contents should be treated as plain text. This is handy when you want to write javascript for client-side consumption, but any variables declared are inaccessible to pug.
script.
const foo = 10;
span #{foo} // foo is undefined here
If you want to define a variable that pug can use later in the template, do so using unbuffered code.
- const foo = 10
span #{foo} // renders <span>10</span>

TagHelper ; how to modify dynamically added children

The asp-items Razor "TagHelper" will add an <option> to a <select> for each value in the SelectList. I want to modify each of those children.
Specifically I want to disable some of them (i.e. add disabled="disabled").
Even more specifically I want to dynamically disable some of them; I'm using angular so I could ng-disabled="{dynamic_boolean_which_determines_disabled}". This means the option could be disabled at first, but after user makes a change, the option could be disabled (without page reload). Angular should take care of this; I think Angular and TagHelpers should work together in theory...
I expected:
I could somehow access an IEnumerable of the children <option> tags that would be created (i.e. one for each item in the SelectList), iterate the children tags, and SetAttribute("disabled") or SetAttribute("ng-disabled")...
I tried:
Creating my own TagHelper which targets the select[asp-items], and tries to GetChildContentAsync() and/or SetContent to reach an IEnumerable <option> tags and iterate them and process each, but I think this will only let me modify the entire InnerHtml as a string; feels hacky to do a String.replace, but I could do it if that's my only option? i.e. ChildrenContent.Replace("<option", "<option disabled=\"...\"")
Creating my own TagHelper which targets the option elements that are children of the select[asp-items], so I can individually process each. This works, but not on the dynamically-added <option> created by asp-items, it only works on "literal" <option> tags that I actually put into my cshtml markup.
I think this'll work but not ideal:
As I said above, I think I can get the result of TagHelper's dynamic asp-items <option></option> <option></option>, as a string, and do a string replace, but I prefer not to work with strings directly...
I suspect (I haven't tried it) that I could just do the work of asp-items myself; i.e. custom-items. But then I'm recreating the wheel by re-doing the work which asp-items could've done for me?
So I hadn't yet read the "AutoLinkHttpTagHelper" in the example which uses string replacement (specifically RegEx replace) to replace every occurrence of a URL, with an <a> pointed at that URL. The cases are slightly different*, but...
Anyway, here's my solution once I learned to stop worrying and love the string modification:
[HtmlTargetElement("select", Attributes = "asp-items")]
public class AspItemsNgDisabledTagHelper : SelectTagHelper
{
//Need it to process *after* the SelectTagHelper
public override int Order { get; } = int.MaxValue;
//https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring#ProcessAsync
public AspItemsNgDisabledTagHelper(IHtmlGenerator gen) : base(gen) {}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
//Notice I'm getting the PostContent;
//SelectTagHelper leaves its literal content (i.e. in your CSHTML, if there is any) alone ; that's Content
//it only **appends** new options specified; that's PostContent
//Makes sense, but I still wasn't expecting it
var generated_options = output.PostContent.GetContent();
//Note you do NOT need to extend SelectTagHelper as I've done here
//I only did it to take advantage of the asp-for property, to get its Name, so I could pass that to the angular function
var select_for = this.For.Name;
//The heart of the processing is a Regex.Replace, just like
//their example https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring#inspecting-and-retrieving-child-content
var ng_disabled_generated_options = Regex.Replace(
generated_options,
"<option value=\"(\\w+)\">",
$"<option value=\"$1\" ng-disabled=\"is_disabled('{select_for}', '$1')\">");
//Finally, you Set your modified Content
output.PostContent.SetHtmlContent(ng_disabled_generated_options);
}
}
Few learning opportunities:
Was thinking I'd find AspForTagHelper and AspItemsTagHelper, (angular background suggested that the corresponding attributes; asp-for and asp-items, would be separate "directives" aka TagHelper).
In fact, TagHelper "matching" focuses on the element name (unlike angular which can match element name... attribute... class... CSS selector)
Therefore I found what I was looking for in SelectTagHelper, which has For and Items as properties. Makes sense.
As I said above, I extend SelectTagHelper, but that's not necessary to answer my original question. It's only necessary if you want access to the this.For.Name as I've done, but there may even be a way around that (i.e. re-bind its own For property here?)
I got on a distraction thinking I would need to override the SelectTagHelper's behavior to achieve my goals; i.e. Object-Oriented Thinking. In fact, even if I did extend SelectTagHelper, that doesn't stop a separate instance of the base SelectTagHelper from matching and processing the element. In other words, element processing happens in a pipeline.
This explains why extending and calling base.Process(), will result in Select doing its job twice; once when your instance matches, and again when the base instance matched.
(I suppose could've prevented SelectTagHelper from matching by creating a new element name like <asp-items-select>? But, not necessary... I just avoid calling base.Process(). So unless that's a bad practice...)
*Different in this way:
They want to create a tag where none exists, whereas I want to add an attribute a tag which is already there; i.e. the <option>
Though the <option> "tag" is generated by the SelectTagHelper in its PostContent (was expecting to find it in Content), and I don't think tags-generated-in-strings-by-content-mods can be matched with their corresponding TagHelper -- so maybe we really are the same in that we're just dealing with plain old strings
Their "data" aka "model" is implied in the text itself; they find a URL and that URL string becomes a unit of meaning they use. In my case, there is an explicit class for Modeling; the SelectList (<select>) which consists of some SelectListItem (<option>) -- but that class doesn't help me either.
That class only gives me attributes like public bool Disabled (remember, this isn't sufficient for me because the value of disabled could change to true or false within browser; i.e. client-side only), and public SelectListGroup Group -- certainly nothing as nonstandard as ng-disabled, nor a "catch-all" property like Attributes which could let me put arbitrary attributes (ng-disabled or anything else) in there.

Optional Partials in DustJS

Is it possible to have optional partials in Dust? Let's say I define a layout like this:
<div>
{>"{module}"/>
</div>
I have been defining the module in Express's res.locals object. However, what if I forgot to define a module, or I actually want a default module for use when I do not define one? Worse, what if I did define one but it's the incorrect module, meaning there's no template file in the view folder with the name of that module? I don't want the user to see the ugly error message, which seems to be something like:
Error: ENOENT, open 'view_path\{module}.dust'
where {module} is the name of the module, or an empty string if I did not specify a res.locals.module. Should I resort to try-catch blocks (not even sure how to do them in dust), or is there a method for making templates optional, rather than required? NOTE: The template would be optional, but the module variable would (usually) still be a string. It seems that dust sections are optional, meaning if the exact key is not available, the section is simply not included. For example, say I have the context {friends: [{name: "Harry"}, {name: "Ron"}, {name: "Hermione"}]}. If I define the section:
{#friends}
{name} is {age} years old.
{/friends}
it will output
Harry is years old.
Ron is years old.
Hermione is years old.
Notice there are 2 spaces between is and years in each case, where the age would be if we defined any ages. If this functionality is included, how is it that neither the original creators of dust nor LinkedIn thought to not require partials? How do I specify optional partials in dust?
You could use a conditional like so,
<div>
{?module}
{>"{module}"/>
{:else}
{>"{YourDefaultPartial}"}
{/module}
</div>
This will work when {module} is either undefined or an empty string or a falsey value.
But, in case its a string pointing to a partial that does not exist, it'll throw an error. Hope this helps.

Passing variable from jade to ng-init not working

I'm trying to pass an object from jade to ng-init in angular
This: doesn't work:
ng-init='tables=!{JSON.stringify(tables)}'
This: expands but,
ng-init='tables=#{JSON.stringify(tables)}'
the output is unescaped and filled with "s
ng-init="tables={"12":{"id":....
and the view isn't updated in either of the cases. This article implies that first one should work, but like I said, it doesn't even expand,
ng-init='tables=!{JSON.stringify(tables)}'
in source code shows up exactly the same in the HTML source
ng-init='tables=!{JSON.stringify(tables)}'
Actually, the #{...} approach seems to work fine.
It is probably the way console.log prints attributes' values that confused you.
ng-init="tables=#{JSON.stringify(tables)}"
Take a look at this short demo.
In what use-case you want to pass data directly from Jade to angular? I think you could to this job in controller like this :
$scope.init = function () {
// init stuff
}
...and in your view :
ng-init = init()

Resources