In lit-element # Templates # Bind properties to child elements (documentation) for
Attribute: <p id="${...}"></p>
Boolean attribute: ?checked="${...}"
Property: .value="${...}"
Event handler: #event="${...}"
a pair of " is always used.
On the contrary, in lit-html # Writing templates (documentation) a pair of " is never used, eg:
Attribute: <p id=${...}></p>
Boolean attribute: ?checked=${...}
Property: .value=${...}
Event handler: #event=${...}
Is there any reason for that? Is any style more preferable than the other?
There's no real difference, both are valid syntax and you should just use the style that suits you the best
Related
I have to form the following xpath via watir methods,
//span[#class='fieldTitle-small required z-label'][contains(.,'Address Type')]/../following-sibling::div//input
Is there a way to call the watir methods one after another to form this xpath?
That XPath could be done using Watir methods as:
browser.span(class: %w(fieldTitle-small required z-label), text: /Address Type/) # //span[#class='fieldTitle-small required z-label'][contains(.,'Address Type')]
.parent # /..
.following_sibling(tag_name: 'div') # /following-sibling::div
.input # //input
Note that when using a :class locator, an Array can be used to specify multiple classes that can appear in any order. The element only has to include those classes, but it can also include any others. For example, browser.span(class: %w(fieldTitle-small z-label), text: /Address Type/), which has the "required" class removed, would match <span class="fieldTitle-small required z-label"> and <span class="fieldTitle-small z-label">.
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.
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.
I've been using the--quite excellent--playframework and have had trouble finding documentation/examples on how to access a Map data structure from a view, using play's template engine.
To be even more specific, I wish to access the Map as I iterate over a List of objects, e.g.,
#{list items:itemList, as:'item'}
// access the map value using the ${item?.id} as the key
#{/list}
Thank's for looking.
This is a generic solution to iterate on Map in using Play! Framework:
in the controller :
render(map);
in the template:
#{list items:map.keySet(), as:'key'}
${map.get(key)}
#{/list}
The fact that you want to rely on a side List to iterate on your Map suggest me that you are searching a predictible way for your iteration process.
In that case, just remember that iteration will be unpredictable if you don't use an ordered/sorted Map implementation.
HashMap gives you an unsorted, unordered Map
LinkedHashMap maintains insertion order
TreeMap is the only JDK implementation of a sorted Map. By default it allows you to iterate following the natural order of the elements. You can also specify a custom sort order and implements the Comparable interface. This will lead you to override the compareTo() method.
Assuming you do in the Controller:
render(map, itemList); //map is a Map
This should work:
#{list items:itemList, as:'item'}
// access the map value using the ${item?.id} as the key
${map.get(item.?id)}
#{/list}
I don't know anything about the play framework, but this would work in a GSP
#{list items:itemList, as:'item'}
${map[item?.id]}
#{/list}
I'm doing things like that on a map:
*{ Map<User, Integer> shareHolders = ... }*
#{list shareHolders.sort{it.value}.collect {k,v -> k}.reverse(), as:'holder'}
<tr>
<td>${holder.name}</td>
<td>${shareHolders[holder]}</td>
</tr>
#{/list}
I am working with the classes in the System.Windows.Documents namespace, trying to write some generic code that will conditionally set the value of certain dependency properties, depending on whether these properties exist on a given class.
For example, the following method assigns an arbitrary value to the Padding property of the passed FrameworkContentElement:
void SetElementPadding(FrameworkContentElement element)
{
element.SetValue(Block.PaddingProperty, new Thickness(155d));
}
However, not all concrete implementations of FrameworkContentElement have a Padding property (Paragraph does but Span does not) so I would expect the property assignment to succeed for types that implement this property and to be silently ignored for types that do not.
But it seems that the above property assignment succeeds for instances of all derivatives of FrameworkContentElement, regardless of whether they implement the Padding property. I make this assumption because I have always been able to read back the assigned value.
I assume there is some flaw in the way I am assigning property values. What should I do to ensure that a given dependency property assignment is ignored by classes that do not implement that property?
Many thanks for your advice.
Tim
All classes that derive from Block have the Padding property. You may use the following modification:
void SetElementPadding(FrameworkContentElement element)
{
var block = element as Block;
if (block == null) return;
block.Padding = new Thickness(155d);
}
Even without this modification everything would still work for you because all you want is for Padding to be ignored by classes that do not support it. This is exactly what would happen. The fact that you can read out the value of a Padding dependency property on an instance that does not support it is probably by design but you shouldn't care. Block and derivatives would honor the value and all others would ignore it.