Render variable partial - handlebars.js

I'm using Handlebars together with BackboneJS. I have Backbone Views that extend each other, e.g. a ModalView and a SpecificModalView. ModalView has a Handlebar template, something like this (simplified):
<div class="modal-header">
{{modalTitle}}
</div>
<div class="modal-body">
{{modalBody}}
</div>
<div class="modal-footer">
{{modalFooter}}
</div>
Now, the modalBody is usually a bit more complex than just a regular placeholder like the title and is defined by the SpecificModalView. What I want is that the SpecificModalView can override the modalBody with a partial or an HTMLElement object.
Is this beyond the scope of Handlebarjs and should I just use jQuery to find the modal-body and replace it's content with whatever is passed as modalBody? Or can Handlebarjs deal with variable partials and HTMLElements?
One idea I tried was that SpecificModalView registered a helper called modalBody and returned whatever body required for that modal. The return value of helpers seems to be type casted to a string though.
Thanks for any help.

Ok I just found one solution, you can return HTML from a helper with the Handlebars.SafeString object. So the SpecificModalView can do something like this:
hbs.registerHelper('modalBody', function() {
var tpl = require('hbs!templates/SpecificModalBody'),
return new hbs.SafeString(tpl());
});
(I'm using requirejs)
This offers the flexibility of creating an HTML object with jQuery or using another Handlebar template for the content.
I'm open to any other solutions though.

Related

What is the 'angular' way of displaying a tooltip / lightbox?

I've been looking around and have not been quite able to get a clear path to the 'angular' way of accomplishing the following. What I'm trying to achieve is displaying a tooltip with information when hovering over a link within an ng-repeat loop. Based on my research, I understood that this is part of the view, and so I should probably handle this in a directive. So, I created an attribute directive called providertooltip. The html declaration is below:
<table>
<tr id="r1" ng-repeat="doc in providers">
<td>
<a providertooltip href="#{{doc.Id}}" ng-mouseover="mouseOverDoc(doc)" ng-mouseleave="mouseLeave()">{{doc.FirstName}} {{doc.LastName}}</a>
</td>
</tr>
</table
<div id="docViewer" style="display:hidden">
<span>{{currentDoc.FirstName}} {{currentDoc.LastName}}</span>
</div>
In the module, I declare my directive, and declare my mouseOver and mouseLeave functions in the directive scope. I also 'emit' an event since this anchor is a child scope of the controller scope for the page. On the controller function (docTable ) which is passed as a controller to a router, I listen for the event. Partial implementation is seen below:
app.directive("providertooltip", function() {
return {
restrict : 'A',
link: function link(scope, element, attrs) {
//hover handler
scope.mouseOverDoc = function(doc){
scope.currentDoc = doc;
scope.$emit('onCurrentDocChange');
element.attr('title',angular.element('#docViewer').html());
element.tooltipster('show');
//docViewer
};
scope.mouseLeave = function() {
element.tooltipster('hide');
}
}
}});
function docTable(docFactory, $scope, $filter, $routeParams) {
$scope.$on('onCurrentDocChange',function(event){
$scope.currentDoc = event.targetScope.currentDoc;
event.stopPropagation();
});
}
Ok, so here is my question. All of the works as expected; Actually, the tooltip doesn't really work so if someone knows a good tooltip library that easily displays div data, please let me know. But, what I'm really confused about is the binding. I have been able to get the tooltip above to work by setting the title ( default tooltip behavior ), but I can see that the binding has not yet occured the first time I hover of a link. I assume that the onCurrentDocChange is not synchronous, so the binding occurs after the tooltip is displayed. If I hover over another link, I see the previous info because as I mentioned the binding occurs in an asynchronous fashion, i.e., calling scope.$emit('onCurrentDocChange') doesn't mean the the parent scope binds by the time the next line is called which shows the tooltip. I have to imagine that this pattern has to occur often out there. One scope does something which should trigger binding on some other part of the page, not necessarily in the same scope. Can someone validate first that the way I'm sending the data from one scope to the other is a valid? Moreover, how do we wait until something is 'bound' before affecting the view. This would be easier if I let the controller mingle with the view, but that is not correct. So, I need the controller to bind data to the scope, then I need the view to 'display a tooltip' for an element with the data. Comments?
To go the angular way correctly start your directive like:
...
directive('showonhover',function() {
return {
link : function(scope, element, attrs) {
element.parent().bind('mouseenter', function() {
element.show();
});
element.parent().bind('mouseleave', function() {
element.hide();
});
}
...
Or start with http://angular-ui.github.io/ link to go the angular-way UI. Look into the bootstrap-ui module - pure angular bootstrap widgets implemented as directives. You can get a clue how the tooltip binding implemented directly from the source of the module - https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js
Also here is another example - (having jQuery and bootstrap scripts included) - use the ui-utils module Jquery passthrough directive ui-jq'. It allows to bind Jquery plugins ( style of $.fn ) directly as angular directive.
Here is their example for binding twitter bootstrap tooltip.
<a title="Easiest. Binding. Ever!" ui-jq="tooltip">
Hover over me for static Tooltip</a>
<a data-original-title="{{tooltip}}" ui-jq="tooltip">Fill the input for a dynamic Tooltip:</a>
<input type="text" ng-model="tooltip" placeholder="Tooltip Content">
<script>
myModule.value('uiJqConfig', {
// The Tooltip namespace
tooltip: {
// Tooltip options. This object will be used as the defaults
placement: 'right'
}
});
</script>
Also look into the official angular documentation for writing directives examples,
and have a happy coding time with Angular!

How do I pass arguments down to component (Dreamweaver) templates?

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.

Binding to nested properties where observables in the chain can be null

I just read about KnockoutJS and when I try to bind to sub-properties on objects that can be null I get binding errors, e.g.:
<div data-bind="text: selectedAccount().DocumentList().length"></div>
So once you call ko.applyBindings it tries to evaluate the above expression and if selectedAccount is null (which it is by default) it throws an error. I know that I can create a dependentObservable like so:
viewModel.docLength = ko.dependentObservable(function () {
return selectedAccount() ? selectedAccount().DocumentList().length : null;
})
But I was wondering if there's a solution other than putting properties in the ViewModel simply because I get a binding error.
A couple thoughts:
If you don't want to bother with the dependentObservable, then you can place your statement directly in the text binding like:
<div data-bind="text: selectedAccount() ? selectedAccount().DocumentList().length : null"></div>
or even shorter:
<div data-bind="text: selectedAccount() && selectedAccount().DocumentList().length"></div>
Depending on your scenario, you can also use the template binding to your advantage when dealing with potentially null values. It would be like this:
<div data-bind="template: { name: 'accountTmpl', data: selectedAccount }"></div>
<script id="accountTmpl" type="text/html">
${DocumentList().length}
</script>
Additionally, in the 1.3 release of Knockout there will be some control flow bindings that could be helpful to you. Particularly the "if" or "with" bindings would work for this situation. They are described here: https://groups.google.com/d/topic/knockoutjs/pa0cPkckvE8/discussion

Rendering a spark view to a string

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

How to solve duplicate objects in dynamic loading page by using jQuery?

I want to solve duplicate objects in dynamic loading content. Please look at the following source code for easier understand.
Base Page HTML With 1 Dynamic Loading Content
<body>
<div id="general-div"></div>>
<div id="div1"></div>
<div id="placeholder1">
Dynamic Content will be placed inside this.
<div class="inner-div"></div>
<div class="div1"></div>
</div>
</body>
For script in header of this page, it's very easy to select "general-div" object like the following code.
$('#general-div')
It's quite easy for select "inner-div" object inside placeholder1. So I can select by using the below code.
$('.inner-div')
The above code could works perfectly. However, I can't use the above code when there is more than 1 duplicated object in the same document like the following HTML. The above code will return 2 objects that don’t what I want.
Base Page HTML - After load another dynamic loading content
<body>
<div id="general-div"></div>>
<div id="div1"></div>
<div id="placeholder1">
Dynamic Content will be placed inside this.
<div class="inner-div"></div>
<div class="div1"></div>
</div>
<div id="placeholder2">
Dynamic Content will be placed inside this.
<div class="inner-div"></div>
<div class="div1"></div>
</div>
</body>
Possible Solution 1
I must create specified jQuery object foreach script in dynamic loading content like the following code.
// Deep copy for jQuery object.
var specfiedjQueryObj = $.extend(true, {}, jQuery);
// modify find function in jQuery object.
specfiedjQueryObj.fn.find = function(selector)
{
// by adding placeholder selector before eval result.
return new specfiedjQueryObj.fn.old_find('#placeholder1 ' + selector);
};
// So, I can select any object in dynamic loading content.
(function($)
{
// This result must be 1 object.
$('.div1');
})(temp);
Even though, this solution should be work great. But I found that jQuery is a very complex object. I found a lot of errors when I try to use it.
Do you have any idea for solving this problem?
PS.PlaceHolder Id is not a fixed Id. So, It's impossible to fixed it in selector rule. Moreover, I do not know exactly amount of element and position (first, last or middle) of it in document. Because of dynamic loading content will be displayed on a lot of page.
How about $('div[id^=placeholder]:last') ?
Selectors / attrubuteStartsWith
You could simply use $('.innerdiv:first') to get the first one or $('.inner-div:last') to get the last one. Or if you have multiples and want to select a particular one $('.inner-div:nth(x)') where x is the index of the item.
The following function will process data from partial loading view page and add specified context for each jQuery selector in script. This answer works well. However, it does not support external script file.
function renderPartialView(control, data)
{
// For detecting all script tag in loaded data.
var reExtractScript = /(<script type="text\/javascript">)([\s\S]+?)(<\/script>)/gi;
// For detecting all "$" sign (must be jQuery object) in loaded data.
var reFindDollarSign = /\$\(([\S]+?)\)/gi;
// Find all matched string in loaded data.
var result = reExtractScript.exec(data);
var allScript = '';
if (result)
{
for (i = 0; i < result.length; i += 4)
{
// Remove current script from loaded script.
data = data.replace(result[i], '');
// Replace all "$" function by adding context parameter that is control.
allScript += result[i+2].replace(reFindDollarSign, '$($1, "' + control + '")');
}
}
// Load non-script html to control.
$(control).html(data);
// Evaluate all script that is found in loaded data.
eval(allScript);
}
// This script will partially download view page from server in the same domain
$(function()
{
$.get(getUrl('~/Profile/Section/ViewEducation'), null, function(data)
{
// When partial loading is complete, all loaded data will be sent to “renderPartialView” function
renderPartialView('#education-view', data);
});
});
Okay, so let's talk about your example HTML. I added a class of placeholder, and added a dash in the id for convenience later.
<div id="placeholder-1" class="placeholder">
Dynamic Content will be placed inside this.
<div class="inner-div">baz</div>
<div class="div1">zip</div>
action
</div>
<div id="placeholder-2" class="placeholder">
Dynamic Content will be placed inside this.
<div class="inner-div">foo</div>
<div class="div1">bar</div>
action
</div>
Now I can bind an event to each of these links with $('.placeholder a.action').bind('click', ... ); If I want this event to all future chunks of html like this on the page, I do $('.placeholder a.action').live('click', ... );
This code will attach an event to those links and the var statements can capture the id, or the inner text of the <div>s. In this way you don't have colliding id attribute values, but you can traverse actions inside divs with class placeholder.
$('.placeholder a.action').live('click', function(){
// get the id
var id = $(this).parents('div.placeholder').attr('id').split('-')[1];
// get the text inside the div with class inner-div
var inner_div = $(this).parents('div.placeholder').children('.inner-div').text();
// get the text inside the div with class div1
var div1 = $(this).parents('div.placeholder').children('.div1').text();
return false;
});

Resources