I'm trying to create dynamic XMLs based on data stored in a javascript object. The problem is that the source data for certain attributes may contain null, which will result in Handlebars rendering the attribute with an empty string, while I would like Handlebars to just ignore it, and not render it at all.
I suppose I could make a template with a helper like this:
<item {{if id}}id={{id}}{{/if}}></item>
But it just seems like a lot of work, and makes the whole thing unreadable.
Is there a nicer way of getting the same results? Or would it perhaps be a better idea to traverse the whole XML after rendering and deleting attributes having empty strings?
I would recommend creating a very simple Handlebars helper. It could take a key and a value as parameters, but only return formatted attribute if value is 0 or truthy. For example:
Handlebars.registerHelper('attr', function (key, value) {
if (!Handlebars.Utils.isEmpty(value)) {
return new Handlebars.SafeString(key + '="' + Handlebars.escapeExpression(value) + '"');
}
});
Your template would invoke the helper in the following manner:
<item {{attr 'id' id}}></item>
Note: I have added the HTML escaping of value just in case it contains a quote character.
Also, if your are able to use ES6, a Template Literal would be prettier:
return new Handlebars.SafeString(`${key}="${Handlebars.escapeExpression(value)}"`);
I have created a fiddle for your reference.
Related
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 attempting to use the Roslyn SDK and StackExchange.Precompilation (thank you!) to implement aspect-oriented programming in C#6. My specific problem right now is, starting with an IdentifierNameSyntax instance, I want to find the "member type" (method, property, field, var, etc.) that the identifier refers to. (How) can this be done?
Background:
The first proof-of-concept I am working on is some good old design-by-contract. I have a NonNullAttribute which can be applied to parameters, properties, or method return values. Along with the attribute there is a class implementing the StackExchange.Precompilation.ICompileModule interface, which on compilation will insert null checks on the marked parameters or return values.
This is the same idea as PostSharp's NonNullAttribute, but the transformation is being done on one of Roslyn's syntax trees, not on an already compiled assembly. It is also similar to Code Contracts, but with a declarative attribute approach, and again operating on syntax trees not IL.
For example, this source code:
[return: NonNull]
public string Capitalize([NonNull] string text) {
return text.ToUpper();
}
will be transformed into this during precompilation:
[return: NonNull]
public string Capitalize([NonNull] string text) {
if (Object.Equals(text, null))
throw new ArgumentNullException(nameof(text));
var result = text.ToUpper();
if (Object.Equals(result, null))
throw new PostconditionFailedException("Result cannot be null.");
return result;
}
(PostconditionFailedException is a custom exception I made to compliment ArgumentException for return values. If there is already something like this in the framework please let me know.)
For properties with this attribute, there would be a similar transformation, but with preconditions and postconditions implemented separately in the set and get accessors, respectively.
The specific reason I need to find the "member type" of an identifier here is for an optimization on implementing postconditions. Note in the post-compilation sample above, the value that would have been returned is stored in a local variable, checked, and then the local is returned. This storage is necessary for transforming return statements that evaluate a method or complex expression, but if the returned expression is just a field or local variable reference, creating that temporary storage local is wasteful.
So, when the return statement is being scanned, I first check if the statement is of the form ReturnKeyword-IdentifierSyntaxToken-SemicolonToken. If so, I then need to check what that identifier refers to, so I avoid that local variable allocation if the referent is a field or var.
Update
For more context, check out the project this is in reference to on GitHub.
You'll need to use SemanticModel.GetSymbolInfo to determine the symbol an identifier binds to.
Use SemanticModel.GetTypeInfo.Type to obtain the TypeInfo and use it to explore the Type
I am trying to construct an anchor tag with a dynamic href attribute in Jade.
I did go through the docs and some SO questions but they didn't help me. This is what I tried.
a(href= "http://www.imdb.com/title/#{movie.imdb_id}") Know more
But it renders
http://www.imdb.com/title/#{movie.imdb_id}
rather than
http://www.imdb.com/title/tt1234567
However this works
a(href= "http://www.imdb.com/title/" + movie.imdb_id) Know more
and this too.
- var url = "http://www.imdb.com/title/" + movie.imdb_id;
a(href= url) Know more
What's wrong with the first version?
Interpolation is only available in text.
You need to use JS string concatenation for attributes:
a(href="http://www.imdb.com/title/" + movie.imdb_id) Know more
If you JavaScript runtime supports ES2015 template string, you can also use them (notice the backticks):
a(href=`http://www.imdb.com/title/${movie.imdb_id}`) Know more
Reference
the pug variable declaration doesnt work in this case using #{...}
the right syntax goes this way,
a(attributes) Know more
a(href="http://www.imdb.com/title/"+ movie.imdb_id) Know more
the attributes is an expression so it renders correcly, or you could use ES5 template literals with back quotes to render the variable along side the text which becomes
a(href=`http://www.imdb.com/title/${movie.imdb_id}`) Know more
note that when using back quotes with template literals your variable expression are enclosed in parenthesis and a leading $ sign, that is ${..expression..}
When you quote it simply tells pug "this is a string". That's basic JS. Interpolation works with #{'#{interpolation}'} too! is an example which renders "Interpolation works with #{interpolation} too!"
I don't have any knowledge about pug(jade)
But my guess is "a(your code)" is already a signal to pug(jade) that it is in the controller's scope already.. and "{variable}" is also an indicator that you are accessing controller's scope. so
a(href= "http://www.imdb.com/title/#{movie.imdb_id}") Know more
for "{}" inside a() is no longer an indicator that your are trying to access controller's scope because you're already in the controller's scope.. so "{}" inside a() is just a string, {movie.imdb_id} is part of the link string.
So in order for the framework to identity that movie.imdb_id is a variable, you should separate it from the actual string.
NOTE: This is just a guess..I'm using angular
I am trying to write a mixin in Jade. I would like to pass a parameter through mixin to build same type of block with different value at different states. In the mixin I want to write a conditional that checks if passed parameter is equal to some value. But unfortunately it is not working and I can't find proper documentation anywhere. My code section is following:
mixin test(id)
if territoryList
each val in territoryList
- if (val.parentArea==id){
button.btn.btn-primary.btn-block=val.name
- }
And I am calling it like test('1')
What is the problem?
I don't think your if is supposed to be a javascript if.
Don't you want this ?
mixin test(id)
if territoryList
each val in territoryList
if val.parentArea==id
button.btn.btn-primary.btn-block= val.name
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()