Piranha CMS block inside and outside container div - piranha-cms

From code template we have:
<div class="block #block.CssName()">
<div class="container">
#Html.DisplayFor(m => block, block.GetType().Name)
</div>
</div>
it make my content block always inside container class. How to make a flexible page where we can put a block inside and outside container

You can check the block type and put a different class on the div besides "container" like this:
<!-- language: lang-razor -->
<div class="block #block.CssName()">
<div class="#(block.GetType().Name == "MySpecialBlock" ? "myspecialclass" : "container") ">
#Html.DisplayFor(m => block, block.GetType().Name)
</div>
</div>
or you can remove the div altogether:
<!-- language: lang-razor -->
<div class="block #block.CssName()">
#Html.DisplayFor(m => block, block.GetType().Name)
</div>
with the intention of editing each DisplayTemplate .cshtml file and add a wrapper div there. i.e. /Views/Cms/DisplayTemplates/HtmlBlock.cshtml :
<div class="myWrapperClass">
#Html.Raw(Model)
</div>
Note: If you did this, you'd probably want to edit each of the various block type templates to add a wrapper of some kind.
Another possibility would be to write a helper class that checks the block type and automatically returns a specific class depending on each block type.
using Piranha.Extend;
namespace MyProject.Classes
{
public static class Helper
{
public static string getWrapperCssClassForBlockType (Block block)
{
string blockName = block.GetType().Name;
string className = "";
switch(blockName)
{
case "HtmlBlock":
className = "row";
break;
case "QuoteBlock":
className = "myQuoteClass";
break;
default:
className = "container";
break;
}
return className;
}
}
}
Then just call the helper method from in your template(s):
#using MyProject.Classes;
#foreach (var block in Model.Blocks)
{
var wrapperClass = Helper.getWrapperCssClassForBlockType(block);
<div class="block #block.CssName()">
<div class="#wrapperClass">
#Html.DisplayFor(m => block, block.GetType().Name)
</div>
</div>
}

So finally i've made it by removing containerfrom template:
<div class="block #block.CssName()">
<!--<div class="container">-->
#Html.DisplayFor(m => block, block.GetType().Name)
<!--</div>-->
</div>
It make all standard block sit outside container. Next i made a group block of container. Any block can be inside this container group block except standard group block such as gallery and columns. Luckily it is open source, so we can make our own columns and gallery group block to be inside a container by modify cshtml template. I dont know how piranha.org do this
But i think my solve fit my purpose

Related

How to pass a dynamicy changed model to Partial view?

I have a list of "workbooks" displayed in a table. Each workbook has a "Share" button next to the workbook's title. When the user clicks on the share button a modal dialog is shown containing a form.
The form allows the user to enter a list of the recipient's emails separated by a comma which is validated on the client-side.
As the dialog is located in a partial view _ShareView.cshtml that allows me to pass a modal WorkbookShareModel that has some fields like WorkbookId and Title. The goal here is to pass the details of each workbook when the user presses the share button (i.e. construct a modal and pass it to the already rendered model).
I am not sure how to pass a model to an already rendered view?
The solution have to be done on the client (i.e. dont involve actions on the server that return the partial view provided the parameters are passed). I want to avoid unnesessary calls to the server - we have all the data on the client regarding a workbook and I need to do a POST when the user types in list of emails.
This is my index.cshtml:
#section BodyFill
{
<div id="shareFormContainer">
#{ await Html.RenderPartialAsync("_ShareView", new WorkbookShareModel());}
</div>
<div class="landing-container">
<div class="workbook-container">
<table class="table">
<tbody>
#foreach (var workbook in Model.Workbooks)
{
string trClassName, linkText;
if (workbook.Metadata.SharedBy == null)
{
trClassName = "saved-workbooks";
linkText = workbook.Name;
} else {
trClassName = "shared-with-me";
linkText = string.Format(
BaseLanguage.SharedWithMeWorkbook,
workbook.Name,
workbook.Metadata.SharedBy,
workbook.Metadata.SharedDate.ToShortDateString()
);
}
<tr class="#trClassName">
<td>#Html.ActionLink(linkText, "Open", "OpenAnalytics", new { id = Model.Id, workbook = workbook.Name })</td>
<td class="last-modified-date" title="Last Modified Date">#workbook.ModifiedDate.ToShortDateString()</td>
<td class="share">
<button title="Share" class="share-button" onclick='showSharingView("#workbook.Name", "#workbook.Id", "#Model.Id")'> </button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
#section Scripts
{
<!--Load JQuery 'unobtrusive' validation -->
#await Html.PartialAsync("_ValidationScriptsPartial")
<script type="text/javascript">
// hide the modal as soon as the page loads
$('#shareFormModal').modal("hide");
function showSharingView(title, workbookId, id) {
$('#shareFormModal').modal("show");
// how to pass a WorkbookShareModel to my partial view from here?
}
function hideDialog() {
var form = $("#partialform");
// only hide the dialog if the form is valid
if (form.valid()) {
activateShareButtons();
$('#shareFormModal').modal("hide");
}
}
// Helper method that validates list of emails
function IsEmailValid(emailList, element, parameters) {
var SPLIT_REGEXP = /[,;\s]\s*/;
var EMAIL_REGEXP =
/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+##[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i;
var emails = emailList.split(SPLIT_REGEXP);
for (var i = emails.length; i--;) {
if (!EMAIL_REGEXP.test(emails[i].trim())) {
return false;
}
}
return true;
}
</script>
}
That is my dialog:
#using DNAAnalysisCore.Resources
#model DNAAnalysisCore.Models.WorkbookShareModel
#* Partial view that contains the 'Share Workbook dialog' modal *#
<!-- Modal -->
<div onclick="activateShareButtons()" class="modal fade" id="shareFormModal" role="dialog">
<div class="modal-dialog modal-md">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Share Workbook - #Model.Title</h4>
</div>
#using (Html.BeginForm("ShareWorkbook", "Home", FormMethod.Post, new { #id = "partialform" }))
{
<div class="modal-body">
<label>#BaseLanguage.Share_workbook_Instruction_text</label>
<div class="form-group">
<textarea class="form-control" asp-for="Emails" rows="4" cols="50" placeholder="#BaseLanguage.ShareDialogPlaceholder"></textarea>
<span asp-validation-for="Emails" class="text-danger"></span>
</div>
<input asp-for="Title" />
<input asp-for="Id" />
<input asp-for="WorkbookId"/>
</div>
<div class="modal-footer">
<button onclick="hideDialog()" type="submit" class="btn btn-primary">Share</button>
<button onclick="activateShareButtons()" id="btnCancelDialog" type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
}
</div>
</div>
</div>
There are two solutions to solve your problem :
Option 1 :
Since you have got the parameters(title, workbookId, id) , you can call server side function using AJAX to render the partial view , then replace the DIV contained in the partial view with the updated contents in the callback function of AJAX .
You can click here for code sample .
Option 2 :
Directly update related input/area using Jquery . For example , the input tag helper :
<input asp-for="<Expression Name>">
generates the id and name HTML attributes for the expression name specified in the asp-for attribute. So you can set the value using Jquery like :
$("#Title").val("Title")
Please click here for Tag Helpers in forms in ASP.NET Core
With Option 2 , you need to clear the Emails area firstly after user click the share button ; With Option 1 , you don't need to care that since the HTML will replace entirely .

Dragula disable drag to reorder

I am using this awesome library for drag and drop functionality.
Dragula is very good at drag and drop.
One thing I am trying to do is to disable dragging to reorder in own container. But should allow dragging if going to drop in a connected/linked container.
for example following two div tags as containers
<div dragula="dropContainer" id="dropbag1" [(dragulaModel)]="bagOneModel">
<div *ngFor="let model of bagOneModel" class="col-sm-2 col-md-2 col-lg-2">
{{model}}
</div>
</div>
<div dragula="dropContainer" id="dropbag2" [(dragulaModel)]="bag2Model">
<div *ngFor="let model of bag2Model" class="col-sm-2 col-md-2 col-lg-2">
{{model}}
<!-- don't allow re ordering in this container -->
</div>
</div>
It's very easy to create something that permit to drag:
from A to B
from B to A
and disable
reorder inside A
reorder inside B
In your name.component.ts you should add:
constructor(public dragulaService: DragulaService) {
dragulaService.createGroup('dropContainer', {
accepts: (el, target, source, sibling): boolean => {
if (!target || !source || (target === source)) {
return false;
}
return true;
}
});
}

Validate input that derives from custom element in to DynamicForm

I am trying to add validation to custom element that gets generated in dynamic form component to support page for view. I injected Aurelia-Validation in main.ts, and DynamicForm.ts and instantiated. Below is my code.
CUSTOM ELEMENT:
TS File
import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';
#customElement('custom-input')
#useView('./custominput.html')
export class CustomInput {
#bindable({ defaultBindingMode: bindingMode.twoWay }) fieldValue: string;
#bindable public customClass: string;
#bindable public placeHolder: string;
#bindable public fieldName: string;
#bindable public formItem: any;
}
HTML View:
<template>
<input class="${customClass}" custom-class.bind="customClass" type="text" field-value.bind="fieldValue"
value.two-way="fieldValue & validateOnChange" placeholder="${placeHolder}" place-holder.bind="placeHolder"
id="${fieldName}" field-name.bind="fieldName" form-item.bind="formItem" />
</template>
DynamicForm
TS File:
import { bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory } from 'aurelia-validation';
#inject(ValidationControllerFactory)
export class DynamicForm {
#bindable public formName: string;
#bindable public formTemplate: Object;
#bindable public callback;
inputItem: HTMLInputElement;
controller = null;
constructor(ValidationControllerFactory) {
this.controller = ValidationControllerFactory.createForCurrentScope();
}
public formValidator(element, field) {
//console.log(element);
}
public bind() {
if (this.formTemplate) {
this.formTemplate[this.formName].fields.forEach((item, i) => {
if (item.validation.isValidate === true) {
ValidationRules.ensure(item.name)
.displayName(item.name)
.required()
.on(this.formTemplate);
}
});
this.controller.validate();
}
console.log(this.controller);
}
}
HTML View:
<template>
<require from="../../elements/custominput/custominput"></require>
<form class="form-horizontal">
<div form-name.bind="formName" class="form-group" repeat.for="item of formTemplate[formName].fields">
<label for="${item.name}" class="col-sm-2 control-label">${item.label}</label>
<div class="col-sm-10" if.bind="item.type === 'text' && item.element === 'input'">
<custom-input router.bind="router" custom-class="${item.classes}" field-value.two-way="item.value"
place-holder="${item.placeHolder}" ref="inputItem" item.bind="formValidator(inputItem, item)"
field-name.bind="item.name" form-item.bind="item">
</custom-input>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-default pull-right" click.delegate="callback()">Submit</button>
</div>
</div>
</form>
<ul if.bind="controller.errors.length > 0">
<li repeat.for="error of controller.errors">${error}</li>
</ul>
</template>
Support page:
This page will load DynamicForm
<template>
<require from="./support.scss"></require>
<require from="../DynamicForm/dynamicform"></require>
<div class="panel panel-primary">
<div class="panel-heading">${pageTitle}</div>
<div class="panel-body">
<dynamic-form form-template.two-way="formTemplate" form-name.two-way="formName" callback.call="processForm()"></dynamic-form>
</div>
</div>
</template>
When I view the support page in browser, I do not get validation in UI. Not sure if validation is position in in nested components/elements. The view is generated like this custominput element -> DynamicForm -> support page
Plunker link for more information:
Any help is really appreciated. :)
Two major issues:
1. Rules shouldn't be stored on fields
Rules are stored on the prototype of an object and pertain to the properties of that object.
You are defining the rules on each individual property, so ultimately it's trying to validate property.property rather than object.property, which doesn't do much as you can see.
You're also declaring the rules every time the form template changes. I basically wouldn't put that logic there; put it closer to where those object come from.
If the objects are declared somewhere in your client code, declare the rules in the same module files
If the objects come from the server, declare the rules on those objects on the same place where you fetch them, right after you fetched them
Either way, those rule declarations don't belong in a change handler.
2. Bindings are missing
The ValidationController needs to know which object or properties you want to validate. It only knows in either of these cases:
Your rules are declared via controller.addObject(obj, rules).
Your rules are declared via ValidationRules.[...].on(obj) and the fields in your html template have & validate following them.
There's several pros and cons with either approach, ultimately you should go with one that gives you least resistance. I would probably go for the second approach because things get more entangled if you declare all rules on your controllers directly.

Kentico's webpart control ID in value in a repeater and transformation

I'm building out a bootstrap based accordion. It's almost there, except i need to wrap each accordion with a tab with a unique ID. My thought was to use the repeaters control ID. So how i can access this from a transformation, and also the HTML envelope?
Here is the HTML envelope from the repeater
<div class="accordion" id="askUsAccordion">
</div>
Here is my transformation code
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<%# DataItemIndex + 1 %> <%# Eval("Heading") %>
</h4>
</div>
<div id="accordionPanel<%# DataItemIndex + 1 %>" class="panel-collaspe collapse" role="tabpanel" aria-labeledby="panel<%# DataItemIndex + 1 %>">
<div class="panel-body">
<%# Eval("Panel") %>
</div>
</div>
</div>
Why not just use the Repeater's ClientID?
Try <%# Container.ClientID %> in this case Container should reference the Repeater the transformation is running on.
Mark, not sure this is the best solution, but it should work for you. Add server side function into your transformation like this:
<script runat="server">
protected string GetID()
{
Control parent = this;
while ( (!(parent is CMSWebParts_Viewers_Documents_cmsrepeater)) &&
(parent != null))
{
parent = parent.Parent;
}
return (parent as CMSWebParts_Viewers_Documents_cmsrepeater).WebPartID;
}
</script>
And call this method in your transformation like this:
<%# GetID() %>
While this isn't my preferred method, i wrote a quick js snippet. I try to avoid having too much JS.
/* Accordions */
// we first detect if there is an accordion in the DOM, and if see we ensure that each is with it's own names space
if ($accordion.length){
// we need the ID of each accordion on the page which then becomes the data-parent value, which is needed to ensure we can isolate accordions
$accordion.each(function(i,v){
var $this = $(this),
$id = $this.attr('id');
// loop through each accordion panel
$this.children('.panel').each(function(){
var $that = $(this);
$('.panel-title-link', $that).attr('data-parent', $id);
});
});
}

Dojo update only part of template

I have created a widget based template like,
<div class="content">
<div>
<!-- rest of content-->
</div>
<div class="col-md-6">
<div class="panel" data-dojo-attach-point="sysinfo">
<ul class="col-md-12 stats">
<li class="stat col-md-3 col-sm-3 col-xs-6">Host:</br> <span><b class="value">{{hname}}</b></span>
</li>
<li class="stat col-md-3 col-sm-3 col-xs-6"># CPU:</br> <span><b class="value">{{cpu}}</b></span>
</li>
</ul>
</div>
</div>
</div>
How do I update only content of sysinfo ?
Till now I was doing,
var widget = this;
widget.template = new dtl.Template(widget.templateString);
var template = widget.template;
template.update(node, stats); // but it update complete content as node == content. I just want to refresh sysinfo.
I also tried,
template.update(this.sysinfo, stats); // but it throws exceptions
Any ideas?
As far as I can see is that when you're using dojox/dtl/_Templated as suggested in the documentation, there is no update() function available.
If you really wish for certain things, you will have to manually define a template and render that one (and replace the attach point), for example:
var subtemplate = "<ul data-dojo-attach-point='sysinfo'>{% for item in list %}<li>{{item}}</li>{% endfor %}</ul>";
var template = "<div><h1>{{title}}</h1>" + subtemplate + "</div>";
var CustomList = declare("custom/List", [ _WidgetBase, _Templated, _DomTemplated ], {
templateString: template,
subTemplate: new dtl.Template(subtemplate),
title: "Fruits",
list: [ 'Apple', 'Banana', 'Lemon' ],
_setListAttr: function(list) {
this.list = list;
this.sysinfo = domConstruct.place(this.subTemplate.render(new dtl.Context(this)), this.sysinfo, "replace");
},
_getListAttr: function(list) {
return this.list;
}
});
Normally, if you would update the template when the list is set, you could use this.render() inside the _setListAttr() function to update the entire template.
However, as you can see in the _setListAttr() function I'm replacing an attach point by a newly rendered Django template.
This results in only that part of the template being updated, in stead of the entire template. So {{title}} would remain the original value, even when changed.
A full example can be found on JSFiddle: http://jsfiddle.net/pb3k3/