I have a page template that outputs three component presentations in a div down the bottom of the page. All three of these component presentations use the same schema and Dreamweaver component template.
I'd like to style these component presentations slightly differently based on whether they're the first component in that div, or the last - basically I'd like to add "first" and "last" CSS classes to each component presentation.
I'm trying to set "arguments" for the component presentations dynamically, in a template building block. Below is what I've got so far (doesn't work, but just to give you an idea of what I'm trying to do):
public override void Transform(Engine engine, Package package)
{
var page = GetPage();
var wantComponents =
from c in page.ComponentPresentations
where c.ComponentTemplate.Title == "Content highlight"
select c;
if (wantComponents.Count() > 0)
{
// pseudocode - won't compile!
wantComponents.First().ComponentTemplate.Parameters["myCssClass"] = "first";
wantComponents.Last().ComponentTemplate.Parameters["myCssClass"] = "last";
}
...
In my Dreamweaver template (again, doesn't work, just to give you an idea of what I'm trying to do):
<div class="block ##Parameters.myCssClass##">
...
</div>
How do I dynamically add the "first" CSS class to the first component presentation on the page, and the "last" CSS class to the last component presentation on the page?
Not a bad question at all George.
If you take your divs out of the Component Template and put them into the Page Template then you don't need to pass the arguments from the Page template into the Component Template. Then setting the CSS class to the first component presentation is easy:
<div class="<!-- TemplateBeginIf cond="TemplateRepeatIndex==0" -->myFirstCssClass<!-- TemplateEndIf -->"></div>
Setting a class on the last Component Presentation is a bit more fun and there are a couple of ways this can be achieved:
A custom Dreamweaver function, for example TemplateRepeatCount(). Then you can do stuff like this inside your Page Template:
<!-- TemplateBeginRepeat name="Components" --><div class="<!-- TemplateBeginIf cond="TemplateRepeatIndex==TemplateRepeatCount()-1" -->lastCssClass<!-- TemplateEndIf -->">##RenderComponentPresentation()##</div><!-- TemplateEndRepeat -->.
The other approach is to write a basic TBB that counts up the component presentations and drops the total number onto the package, and then you can compare your TemplateRepeatIndex against this number.
Both #1 and #2 above are described in my article here: http://www.tridiondeveloper.com/more-fun-with-dreamweaver-templates-templaterepeatcount
Finally, here is an approach more inline with specifically what you were asking where a Component Template actually looks up into the Page's scope to determine if it's the last Component Presentation in the list. It's not my favourite because it's not so easy to debug with TemplateBuilder (since when you're running through a CT you don't have a PT, hence the component presentation count doesn't exist in this scope).
public class IsLastCP : TemplateBase
{
private string MY_SCHEMA = "My Component's Schema Title";
public override void Transform(Engine engine, Package package)
{
this.Initialize(engine, package);
//in the page template.
Page page = this.GetPage();
if (page == null)
{
//this TBB is being executed either in Template Builder or a dynamic component presentation.
// so we just don't do anything.
}
else
{
IList<ComponentPresentation> cpList = page.ComponentPresentations;
int cpCount = 0;
int thisCPIndex = -1;
Component thisComponent = this.GetComponent();
foreach (ComponentPresentation cp in cpList)
{
Component comp = cp.Component;
if (comp.Schema.Title == MY_SCHEMA)
{
if (comp.Id.Equals(thisComponent.Id))
thisCPIndex = cpCount;
cpCount++;
}
}
if (thisCPIndex == cpCount-1)
{
package.PushItem("IsLastCP", package.CreateStringItem(ContentType.Text, "true"));
}
}
}
For this you'll need Will's famous TemplateBase class which you can get from his "Useful Building Blocks" extension available from SDLTridionWorld. Obviously you'll need to tweak the code I provided to your schema name, etc.
Drop this TBB ahead of your Dreamweaver TBB in your Component Template then use its output like this: <!-- TemplateBeginRepeat name="IsLastCP" -->class="myLastCSSClass"<!-- TemplateEndRepeat -->
Note: you don't need to do a TemplateBeginIf here and check explicitly for true/false on IsLastCP. If the CP in question is last, then this variable will be present in the package, and the TemplateBeginRepeat clause will enter.
You can also do this kind of thing using Context Variables. You can't do this directly from a DWT, so this would mean perhaps writing a function source, or perhaps replacing the DWT with an assembly TBB that writes to output. If this kind of approach fits your design you can just write a variable into engine.PublishingContext.RenderContext.ContextVariables from the page template, indicating whether the Component render is the first or not, and then have the component template read it to determine what output to produce.
In general, the idea is to write variables in the page template and read them in component templates. This should be enough to let you avoid moving component template concerns into the page template, although, of course, the amount of plumbing might put you off. For more extreme cases, it's possible to get values from the component template to the page template, but then you've got even more plumbing, so wanting to do that at all might be a design smell.
Related
I am new to ASP.NET.
I needed to show a list of items. I made a partial view to represent each item. It works fine.
I needed to add a script in the partial view. So I created a new javascript file in wwwroot > js. Then referenced it from the partial view, like this:
<script src="~/js/testjs.js"></script>
And this is the javascript file:
var upbtn = document.getElementById("upbtn");
var dnbtn = document.getElementById("dnbtn");
upbtn.onclick = upbtn_click;
dnbtn.onclick = dnbtn_click;
function upbtn_click() {
upbtn.children[0].children[0].style.fill = "green";
dnbtn.children[0].children[0].style.fill = "gray"
}
function dnbtn_click() {
upbtn.children[0].children[0].style.fill = "gray";
dnbtn.children[0].children[0].style.fill = "green";
}
The problem is with the script. It only executes fot the first partial view, not for the others.
So, for the first partial view, when I click the buttons, I can see the changes. But for the rest of the partial views, they have no effect. What should I do to make the script work for all partial views?
The project is using ASP.NET Core.
The reason you are seeing your functions only get called for the first partial view is due to how that javascript code works and the fact that the same element id's and javascript is being repeated in each partial view.
For example, the first partial view gets rendered and your onclick events get hooked up for your two elements with id's "upbtn" and "dnbtn". When the second partial view gets rendered (and any others after that), that document.getElementById runs, and finds the first element with that id in the document, which will be that of the first partial. (also element id's should be unique throughout a page, so you should look at ways to get unique id's in repeated partial views after you get this working. I didn't add that to this answer so as to keep focused on this question/answer)
In order to get this working, you need to re-design how the elements in your partials are calling the js functions, and you can move the js out to the parent view or to a single shared js file for your site. Note I'm making some assumptions/estimates on html elements below since you did not share that code.
For your upbtn/dnbtn elements, you can change them to something like below. The "buttonsParentElement" div is so we can get to the other btn elements children upon any click.
<div id="buttonsParentElement">
<div id="upbtn" onclick="upbtn_click(this)">UP</div>
<div id="dnbtn" onclick="dnbtn_click(this)">DOWN</div>
</div>
Then update your js methods (again, not in the partial but either in the parent view, or a shared js file):
function upbtn_click(element) {
element.children[0].children[0].style.fill = "green";
var relatedDnBtn = element.closest("#buttonsParentElement").querySelector("#dnbtn");
relatedDnBtn.children[0].children[0].style.fill = "gray"
}
function dnbtn_click(element) {
var relatedUpBtn = element.closest("#buttonsParentElement").querySelector("#upbtn");
relatedUpBtn.children[0].children[0].style.fill = "gray";
element.children[0].children[0].style.fill = "green";
}
In my Spring Boot 2 project I have Thymeleaf fragments to generate form elements. For example:
<input th:replace="component/admin_form :: text (formElement = ${vm.getElementStatus()}, class='css-class-a'))" />
The code above generates a complex div block with label, input field and error block.
I would like to simplify this syntax. My idea was to create a custom dialect with a custom tag and write this:
<admin-form:text value="${vm.getElementLastName()}" class="css-class-a"/>
The second one is easier to read, it clearly indicates for the designers that this is a special element. Besides this, it would be easier to change themes as I only need to change the concrete fragment location in the tag processor and not hundreds of th:replace value.
It is also important that I don't want to build the complex html layout in the tag processor just want to somehow import a fragment. So designers can modify the html fragment without java code changes.
I was able to create the custom dialect and create the custom tag that generates a html block:
#Override
protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structureHandler) {
final IModelFactory modelFactory = context.getModelFactory();
final IModel model = modelFactory.createModel();
model.add(modelFactory.createOpenElementTag("div", "class", "test"));
model.add(modelFactory.createText("This is my custom element"));
model.add(modelFactory.createCloseElementTag("div"));
structureHandler.replaceWith(model, false);
}
But I don't know how to import a fragment in my custom tag.
Is it possible anyhow?
The doProcess of your <admin-form:text> tag could create a dummy tag with a th:replace attribute that includes the "complex input" fragment:
Map<String, String> attributes = new HashMap<>();
attributes.put("th:replace", "/pathToMyFragment/complexInput::myfragname");
IOpenElementTag newTag = modelFactory.createOpenElementTag("div", attributes, AttributeValueQuotes.DOUBLE, false);
model.replace(0, newTag);
or something similar (take care of closing the tag and so on).
The result would be to replace
<admin-form:text/>
with
<div th:replace="/pathToMyFragment/complexInput::myfragname"></div>
which in turn would be processed into the final HTML.
If you need to keep the original tag attributes (like class="css-class-a") you can get them from the original tag with model.get(0).getAttributeMap() and add them as a local variable with structureHandler.setLocalVariable("originalAttributes", theAttributeMap); to use in the final fragment, for example.
I am using Google Sites for my webpage and I want parts of it to be kind of dynamic. Hence, what I want to achieve is to change the CSS class associated with certain HTML element in the presence of an on click event.
E.g.:
Initial situation:
<div class="foo">Hello</div>
World
After clicking on "World":
<div class="bar">Hello</div>
World
Add an ID to the div, then:
function method() {
var myDiv = document.getElementById("myDIV");
myDiv.className = "bar";
}
Alternatively you can get all elements with that particular class name, then loop through them to change their class name.
function method() {
var myDivs = document.getElementsByClassName("foo");
for (var i = 0; i < myDivs.length; i++)
myDivs[i].className = "bar";
}
Now of course you can add if/else statements to check for certain conditions before making changes.
Now that I understand what you are looking for, Check this link.
There is a get and set method for the HTML content of the page requested. By manipulating the generated string, it should be possible to then change the div to the required class and other such things. This should be a good start, we'll have to play around with the rest.
This might also be helpful, but I believe it has several limitations.
I'm trying to render a partial view as a string so it can be returned as HTML to a jquery ajax call. After a lot of searching I found this code.
public string RenderAsString(string viewName, string modelName, object model)
{
// Set up your spark engine goodness.
var settings = new SparkSettings().SetPageBaseType(typeof(SparkView));
var templates = new FileSystemViewFolder(Server.MapPath("~/Views"));
var engine = new SparkViewEngine(settings) { ViewFolder = templates };
// "Describe" the view (the template, it is a template after all), and its details.
var descriptor = new SparkViewDescriptor().AddTemplate(#"Shared\" + viewName + ".spark");
// Create a spark view engine instance
var view = (SparkView)engine.CreateInstance(descriptor);
// Add the model to the view data for the view to use.
view.ViewData[modelName] = model;
// Render the view to a text writer.
var writer = new StringWriter(); view.RenderView(writer);
// Convert to string
return writer.ToString();
}
But when the following line executes:
var view = (SparkView)engine.CreateInstance(descriptor);
I get the following error:
Dynamic view compilation failed. An
object reference is required for the
non-static field, method, or property
'DomainModel.Entities.Region.Id.get.
This is my partial view:
<ViewData Model="Region" />
<div id="${ Region.Id }" class="active-translation-region-widget" >
<label>${Region.RegionName}</label>
${ Html.CheckBox("Active") }
</div>
It doesn't seem to recognise the model.
P.S. When I call the view from a parent view like so
<for each="var region in Model">
<ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>
It renders perfectly. What am I doing wrong?
Just from looking at it, I think the following line is the problem:
<ViewData Model="Region" />
Instead it should read:
<viewata model="Region" />
Note the lower case "model". This is because model is a special case since behind the scenes it performs a cast to a strongly typed viewmodel. The top one will define a variable called Model in the generated view class and assign the value Region to it. Using the lowercase option below will actually create a Model variable, but also cast it to strongly typed instance of Region that comes from the ViewData dictionary.
Note When using Model in the code though, like you did in the for each loop, it needs to be upper case which is correct in your case. Once again, this is the only special case because it's pulling a strongly typed model from the ViewData dictionary.
One other thing - <viewata model="Region" /> must be declared in the parent view, and it can only be defined once per page, so you cannot redefine it in a partial view. If it's a partial view, you should rather use it by passing in a part of the model like you have done in your second example above.
The reason for your exception above is because it's trying to get the Id property as a static item off the Region Type, rather than querying the Id property on your instance of Region as part of your viewmodel.
As a side note, the code to get where you want is a little mangled. You can find neater ways of doing what you want by checking out some of the Direct Usage Samples, but I understand this was probably just a spike to see it working... :)
Update in response to your follow up question/answer
I'm fairly sure that the problem is with passing the Region into the following call:
<ActiveTranslationRegion Region="region" if="region.Active==true">
... is again down to naming. Yes, you can only have one model per view as I said before, so what you need to do is remove the following from the top of your partial:
<viewdata model="Region" />
That's what's causing an issue. I would then rename the item going into your partial like so:
<ActiveTranslationRegion ActiveRegion="region" if="region.Active==true">
and then your partial would look like this:
<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
<input type="hidden" name="Id" value="${ActiveRegion.Id}" />
<label class="region-name">${ ActiveRegion.RegionName }</label>
<input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>
Note I'm using ActiveRegion because in the Spark parser, ActiveRegion gets declared as a variable and assigned the value of region in the current scope as you loop through the for loop. No need to stick religiously to the model - because you've gone and passed in a piece of the model now that you've declared as ActiveRegion. Oh, and you could stick with the name Region if you really want, I just changed it to make a point, and because you've got a Type called Region in your code and I'm not a big fan of the quirky issues using the same name for a variable as a type can bring about. Plus it makes it a little clearer.
The disadvantage of calling the Html.RenderPartial method is not immediately obvious. One thing you lose is the 3-pass rendering that Spark provides. If you use the tag syntax (which is preferable) you'll be able to stack partials within partials to multiple levels down passing variables that feed each partial what they need down the stack. It gets really powerful - start thinking data grid type structures where rows and cells are individual partials that are fed the variables they need from the model, all kept nice and clean in separate manageable view files. Don't stop there though, start thinking about targeting header and footer content base on variables or three column layouts that create a dashboard that renders all sorts on individually stacked partials many levels deep.
You lose all of that flexibility when you use the bog standard ASP.NET MVC Helper method Html.RenderPartial() - careful of doing that, there's more than likely a solution like the one above.
Let me know if that works...
All the best
Rob G
I refactored the code and views quite a bit. In the end all I'm really trying to acheive is have a parent view (not shown) iterate over an IEnumerable and for each iteration render a partial view (ActiveTranslationRegion) which renders some Html to represent a region model.
I also want to be a able to call an action method via an ajax call to render an indivual ActiveTranslationRegion partial view by passing it an individual region model. I've refactored the code accordingly.
Partial view (_ActiveTranslationRegion.spark)
<viewdata model="Region" />
<form action="/Translation/DeactivateRegion" class="ui-widget-content active-translation-region-widget">
<input type="hidden" name="Id" value="${Model.Id}" />
<label class="region-name">${ Model.RegionName }</label>
<input class="deactivate-region-button" type="image" src=${Url.Content("~/Content/Images/Deactivate.png")} alt="Deactivate" />
</form>
Notice by using I can refer to Model within the view as RobertTheGrey suggested (see above) .
I removed all the code to return the view as a string and simply created an action method method that returned a partialViewResult:
[UnitOfWork]
public PartialViewResult ActivateRegion(int id)
{
var region = _repos.Get(id);
if (region != null)
{
region.Active = true;
_repos.SaveOrUpdate(region);
}
return PartialView("_ActiveTranslationRegion", region);
}
One thing I had to do was amend my parent view to look like so:
<div id="inactive-translation-regions-panel">
<h3 class="ui-widget-header">Inactive Regions</h3>
<for each="var region in Model">
<span if="region.Active==false">
# Html.RenderPartial("_InActiveTranslationRegion", region);
</span>
</for>
</div>
Where previously I had the following:
<div id="inactive-translation-regions-panel">
<for each="var region in Model">
<ActiveTranslationRegion Region="region" if="region.Active==true"></ActiveTranslationRegion>
</for>
</div>
Notice I have to call the Html.RenderPartial rather than use the element. If I try and use the element (which I would prefer to do) I get the following error:
Only one viewdata model can be declared. IEnumerable<Region> != Region
Is there a way round that problem?
Update:
I tried your recommendation but with no luck. To recap the problem, I want to use the partial in 2 different situations. In the first instance I have a parent view that uses a model of IEnumerable<Region>, the partial simply uses Region as its model. So in the parent I iterate over the IEnumerable and pass Region to the partial. In the second instance I want to call PartialView("_ActiveTranslationRegion", region) from an action method. If I remove the <viewdata model="Region" /> from the partial I get an error complaining about the model. The best way round the problem I have found is to add a binding to the bindings.xml file:
<element name="partial"># Html.RenderPartial("#name", #model);</element>
(Note: It seems very important to keep this entry in the bindings file all on te same line)
This way I can still call the partial from the action method as described above and pass it a Region as the model, but I can also replace my call to Html.RenderPartial in the parent view with a more 'html' like tag:
<partial name="_ActiveTranslationRegion" model="region" />
So my parent view now looks more like this:
<div id="inactive-translation-regions-panel">
<h3 class="ui-widget-header">Inactive Regions</h3>
<for each="var region in Model">
<span if="region.Active==false">
<partial name="_ActiveTranslationRegion" model="region" />
</span>
</for>
</div>
Of course under the hood its still making a call to
# Html.RenderPartial("_ActiveTranslationRegion", region);
But its the best solution we could come up with.
Regards,
Simon
I'm using a tree control that I want to customize. The data items in the tree's dataProvider have a property name that should be used for labeling the node, and a property type that should be used to select one of several embedded images for use as an icon. The simplest way to do this is by using the labelField and iconFunction properties.
However, I wanted to get started with item renderers and open the door for adding more complex customization later, so I tried making my own item renderer. I extended the TreeItemRenderer class as follows and used it in my tree control:
class DirectoryItemRenderer extends TreeItemRenderer
{
[Embed("assets/directory/DefaultIcon.png")]
private static var _DEFAULT_ICON:Class;
// ... some more icons ...
override public function set data(value:Object):void
{
super.data = value; // let the base class take care of everything I didn't think of
if (value is Node) { // only handle the data if it's our own node class
switch ((value as Node).type) {
// ... some case clauses ...
default:
this._vSetIcon(_DEFAULT_ICON);
}
this.label.text = (value as Node).name;
}
}
private function _vSetIcon(icon:Class):void
{
if (null != this.icon && this.contains(this.icon)) {
this.removeChild(this.icon);
}
this.icon = new icon();
this.addChild(this.icon);
this.invalidateDisplayList();
}
}
This code has no effect whatsoever, icon and label in the tree control remain at their defaults. Using trace(), I verified that my code is actually executed. What did I do wrong?
Looking at the base mx.controls.treeClasses.TreeItemRenderer class, I see that in the updateDisplayList function the renderer gets it's icon and disclosureIcon classes from _listData:TeeListData. Instead of overriding the updateDisplayList function, try modifying the icon and disclosureIcon classes of the renderer's private _listData instance in your _vSetIcon method using the public accessors, like so:
private function _vSetIcon(icon:Class, disclosureIcon:Class = null):void
{
var tmpListData:TreeListData;
if (disclosureIcon == null) disclosureIcon = icon;
tmpListData = this.listData;
tmpListData.icon = icon;
tmpListData.disclosureIcon = disclosureIcon;
this.listData = tmpListData;
}
EDIT
Here is some clarification on the difference between data and listData. You'll have to excuse my omission of package names but I'm editing from my phone so its tough to look them up and I don't know the package names off the top of my head. data is defined in the context of a TreeItemRenderer in the IDataRenderer interface. You create a data renderer by implementing this interface and defining a public property data, which in this case is set by the parent control and contains some data and meta-data from the dataProvider to be rendered by the data renderer class.
listData is defined in the IDropInListItemRenderer interface as a property of type BaseListData and is realized in the TreeItemRenderer class as a property TreeListData. It differs from the data property in that it contains meta-data that describes the TreeListRenderer itself (icon, indent, open) as well as (I believe, I'll have to double check this later) a reference to the data item being rendered. I gather that It's used by the the TreeItemRenderer and I would imagine the parent list control for display update and sizing purposes. Someone is free to correct or add onto that if I'm incorrect or missed something, I'm going of what I remember drom the code.
In this case, you wanted to use meta-data from the data set from the data provider to modify data that determines the display of the renderer, so you would need to modify both.
I think the real confusion here however came from the fact that you extended the TreeItemRenderer class then tried to override functionality on the component in a manner the original developer didn't intend for someone to do, hence the unexpected results. If your goal is education and not ease of implementation you would probably be better served by extending the UIComponent class and using the TreeItemRenderer code as a reference to create a class that implements the same interfaces. That would be a real dive into the pool of custom component development.
I'd probably try something simple, as in this example from the Adobe Cookbooks. I notice that they override updateDisplayList, which may have something to do with your problems.
There's another example (for Flex 2, but looks applicable to Flex 3) that shows how to manage the default icons. It looks like you'll want to manage the icon yourself, setting the default icon styles to null, instead of trying to manipulate the superclass's icon property.
Update -- Looking at the source for TreeItemRenderer, commitProperties has the following before checking the data and setting up the icon and label:
if (icon)
{
removeChild(DisplayObject(icon));
icon = null;
}
Also, it looks like the setter for data calls invalidateProperties. Hence, your icon is wiped out when the framework gets around to calling commitProperties.