I have an MVC/angularJS page with a button, the button needs to call code to process the current page and proceed to the next step in the application, but they want the button text to be a/b testable with different variations. I'm new to Sitecore so am struggling to know the best way of doing things.
I thought of having a simple text component/template which just has a single line text property, but if I add that to the page template then it doesn't seem a/b testable because when you click on the test option it asks you to select content. Whereas the content was text they entered as part of the page template.
The only way I know of making a/b testable content so that they can click on the page in page editor and choose to select content / add test variation. I wouldn't add the button to the placeholder as it needs to call specific angular code and always be there, but should I be adding a placeholder where the text is? It seems like overkill to have to define a placeholder there, define a rendering, create a partial view, define placeholder settings to limit it to the simple text component, and then hope they don't try adding multiple items to the placeholder.
I would make a separate template (ie with the text field for your button) to represent your form, then either create the two test variation items as children of your page, or maybe place them in a shared components folder outside of your 'home' node.
EDIT
In order to move your form component into a new A/B testable component you would need to create a new Sublayout in Sitecore, then create a new ascx control for the sublayout. In the Page_Load handler of this control, you would use the following code to retrieve the datasource of the sublayout:
//assume you have a button on your usercontrol called btnSubmit
//assume your template has a single-line text field called 'SubmitButtonText'
Guid dataSourceId;
Sitecore.Data.Items.Item dataSource;
if (Guid.TryParse(sublayout.DataSource, out dataSourceId))
{
dataSource = Sitecore.Context.Database.GetItem(new ID(dataSourceId));
btnSubmit.Text = dataSource["SubmitButtonText"];
}
So I created a new template which just had a single line of text as a field, and added a content item in a shared data node.
In my partial view:
#model Digital.Models.SimpleTextItem
<button ....>
<span class="hidden-xs">#Model.SimpleText_Value<br></span>
</button>
In my main page - I was trying to statically bind it so that they could only change content rather than add new controls to the placeholder, but that only worked if I specified the datasource in this page.
Using a rendering, and in the page layout adding the rendering to the placeholder with a specified data source:
#Html.Sitecore().Placeholder("PremiumQuoteApplyNowPlaceHolder")
Not sure if it was the best approach but it achieves what I need it to.
A/B testing could be applied only to controls(XSLT renderings, sublayouts, action controller renderings, view renderings). If you want to make A/B testing only for button then you should create additional control for it as you did.
Technical details for MVC: A/B testing is applied on mvc.customizeRendering pipeline where rendering arguments are processed. This pipeline operates on renderings level. It means that you are not able to create A/B testing for particular field(button) without your own customization.
Related
I have an Aurelia app where a user can click on a button and create a new tab. The tab and its content (a custom element) do not exist on the page before the user clicks the button. I am generating the HTML for the content at runtime (via Javascript) in my view model.
I keep seeing mention of using the template engine's compose or enhance functions, but neither are working for me. I don't know how I would use the <compose> element (in my HTML) since I am creating the element based on the user clicking a button.
My thought was that the button has a click.delegate to a function that does ultimately does something like
const customElement = document.createElement('custom-element');
parentElement.appendChild(customElement);
const view = this.templatingEngine.enhance({
element : customElement,
container : this.container, // injected
resources : this.viewResources, // injected
bindingContext: {
attrOne: this.foo,
attrTwo: this.bar,
}
});
view.attached();
But all this does is create an HTML element <custom-element></custom-element> without actually binding any attributes to it.
How can I create a custom element analogous to <custom-element attr-one.bind="foo" attr-two.bind="bar"></custom-element> but via Javascript?
As you pointed out in your own answer, it's the missing resources that caused the issue. One solution is to register it globally. That is not always the desired behavior though, as sometimes you want to lazily load the resources and enhance some lazy piece of HTML. Enhance API accepts an option for the resources that you want to compile the view with. So you can do this:
.enhance({
resources: new ViewResources(myGlobalResources) // alter the view resources here
})
for the view resources, if you want to get it from a particular custom element, you can hook into the created lifecycle and get it, or you can inject the container and retrieve it via container.get(ViewResources)
I found the problem =\ I had to make my custom element a global resource.
Is what I am trying to do in this gist possible?
https://gist.run/?id=bea803b05ad8d5b5e3e0afd59bb8dbb1
In app.html, I'm trying to use a repeat.for to create a custom element called button-row
In app.js, I'm creating two button row instances which I then place into a button row array so that I can use the repeat.for to iterate over it and create the button rows on the app.html view.
In button-row.html, I have a repeat.for to create the buttons using the btns array and setting the name of the button.
In button-row.js, I have two properties. label is the label for the button row and btns is the array of all the button names I want to create.
Sorry if this is a noob question, I have only been working with web development and aurelia for about a month.
Instantiating the ButtonRow objects yourself won't work. Aurelia instantiates the instances. you'll need to use the binding system to pass in the information to the custom element. I updated your gist here: https://gist.run/?id=6ec71143f566571960b7a182d4d98ed4
Also, you should refrain from abbreviating words like "Button" let your tooling save you the keystrokes, while making your code more readable :-)
I have a Data View in an Xpage application which is using the Bootstrap theme. I started to use a View, but could never get the pagers lines up, and the data view is working better.
But I do not understand where to put my table class css. For example, if I want a stripped table I enter "table table-striped" in the styleClass of the view (or maybe it is the dataStyleClass). If I do that in the styleClass of the data view, I do not get strips.
I tried the suggestion from Mark below, but something is not working. I added a script call and used the id of the tableview. It already has a class of "cleearfix table" on it.
I have added Chrome's web inspector to show what is going on.
The xe:dataView control does have a styleClass attribute, but classes that you set there are added to the div element that wraps the dataview, not the table (and that's where Bootstrap needs the table-striped class. I would solve this with some JavaScript to add the classes you need on the table element
<xp:scriptBlock
id="scriptBlock1">
<xp:this.value><![CDATA[
$("table.dataview").addClass("table-striped table-hover")
]]></xp:this.value>
</xp:scriptBlock>
In my Grails project I'm using the PDF plugin to generate PDFs from gsp page.
It works well, but I would like to add more functionalities to users, so I would like to allow user to edit the PDF basic template (defined in a gsp page), in particular I would like to allow the editing of the text inside the template and store it somewhere.
Anybody knows how could it be done?
If you only want to change the text, you could store the edited text in a database with the user id and loading it into the gsp page instead of the standard text.
If you also want to change the style of the page you could try to store the whole gsp page in the database and let the user editing it with an HTML Editor.
that's how i would start with, maybe someone has an better idea
The underlying components of the pdf plugin don't strictly require a .gsp file. It simply uses .gsps rendered as strings and feeds those into the flyingsaucer lib. So you could use a WYSIWYG type editor to allow users to create html snippets, save those strings somehow and then feed those though to the flyingsaucer libs yourself. just look into the included service methods of the plugin for an example. That might sound scary, but it really isn't very complicated.
You might want to wrap the user generated content with some of your own HTML markup for sanity and styling purposes of course, but the idea you are going for is entirely doable.
You could have a GSP that behaves of two different ways. First of all the GSP will be rendered in a Editable state. In this state the user could do some edits in some parts of the GSP. After that, the GSP will be rendered in a Preview state, where user could check the modifications that he has done in the previous step (nothing can be edited in this state). Finally, the GSP will be rendered as a PDF (using Grails Rendering Plugin).
Note that user will not edit the GSP itself. You need to allow him to edit through HTML elements as Text Areas, for instance. In this case we're using an WYSWYG editor. This editor allows the user to put text as Bold, Italic, etc.
Therefore, the most important step of this solution is to distinguish the two states in the same GSP. To do that you can use a boolean variable (called editing, for instance). This variable, if true, will render the GSP with the elements that will allow him to perform changes in the document. For other side, if the editing variable is false, the GSP will be rendered just with texts, not allowing any kind of editing.
The user could Check or Uncheck checkboxes (to show or hide some part of the document) and write or change texts in the Text Areas elements.
Below I'll show how this solution works.
GSP
The GSP is a template GSP and is called _quote.gsp
The piece of code below shows the use of the editing variable. Note that if editing = true, then a textarea is rendered and user can edit the text. There is a standard text that can be changed.
The post variable keeps what user has done after the editing phase. We use JQuery serialize to get all paramaters and pass it to a Grails Controller.
<p>
<g:if test="${editing}">
<pgs:textArea html="true" autosize="true" name="fraseInicial" rows="2" cols="80">
${post?.fraseInicial?post.fraseInicial:"Conforme sua solicitação, a empresa tem a satisfação de informar-lhe os métodos e preços."}
</pgs:textArea>
</g:if>
<g:else>
${post.fraseInicial}
</g:else>
</p>
pgs:textArea is a specific taglib of this system and is used to render a WYSWYG editor, you can replace it for a simple TextArea HTML element.
An example with checkbox:
<g:if test="${editing || post.temPrazoAnalise}">
<h1>
Teste teste
</h1>
<g:if test="${editing}"><g:checkBox name="temPrazoAnalise" value="${!post?true:post?.temPrazoAnalise == null?false:true}"/></g:if>
<g:if test="${editing || post.temPrazoAnalise}">
<p>Teste teste teste </p>
</g:if>
</g:if>
Controller
The previewQuote() is called from an AJAX call that serializes (via JQuery) all parameters of GSP.
The back() action allows the user to back to the editing state from the preview state. This is the reason why we set session["paramsReport"] = params inside previewQuote(). Doing this way it's possible to use session["paramsReport"] inside back() and restore the values changed by the user.
def editQuote() {
def quote = Quote.get(params.id)
render(template: "/quote/report/quote", model: [editing:true, quote:quote])
}
def previewQuote() {
Quote quote = Quote.get(params.id)
session["paramsReport"] = params
render(template: "/quote/report/quote", model: [quote:quote, post:params])
}
def back() {
def quote = Quote.get(params.id)
if (session["paramsReport"]) {
render(template: "/quote/report/quote", model: [editing:true, post:session["paramsReport"], quote:quote])
}
}
def generateQuote() {
Quote quote = Quote.get(params.id)
def f = new File(grailsApplication.mainContext.servletContext.getRealPath("/app/temp/${quote.code}.pdf"))
if (f.exists())
f.delete()
f.withOutputStream { os ->
pdfRenderingService.render([template: '/quote/report/quote', model: [quote:this, post:session["paramsReport"], pdf:true]], os)
}
}
This solution was developed by wanderson-santos (https://stackoverflow.com/users/128857/wanderson-santos) and me.
I hope you understand the overall idea of the solution. I understand that could be a bit complicated at a first view. Anyway, is a solution that allows flexibility for this kind of requirement (i.e. allow the user to customize a report before the PDF is generated).
In our application, there is a tabpanel in which we are adding/removing the panel dynamically.
The panels get added at the click of a menu item by the following code in menu handler:
Ext.getCmp('mainTabPanelId').add(getPanel());
Here getPanel() method returns the panel after creating it.
Assuming that the id of main tab panel is mainTabPanelId and that of the child panel is panelId, in this context, could someone guide at the following:
Is it necessary to call doLayout() on mainTabPanel after the add method?
Should the doLayout() be called on the mainTabPanel or on the newly added child panel, that is, Ext.getCmp('mainTabId').doLayout() or Ext.getCmp('panelId').doLayout()?
Will a call to doLayout() take care of all the issues related to rendering, like scrollbars esp.?
The method getPanel() should return an already created panel (using Ext.create) or should it return a config object (having xtype:'panel')? Which one should be preferred for better performance keeping time in mind?
AbstractContainer::add()
<...> If the Container was configured with a size-managing layout manager, the Container will recalculate its internal layout at this time too.
So you don't have to do 1 — 3 because:
AbstractContainer::doLayout()
<...> The framework uses this internally to refresh layouts form most cases.
AbstractContainer::defaults
For defaults to work, the child items must be added using {xtype: ......} NOT using Ext.create("widget.type",{}) © roger.spall
So I'd prefer return configuration object instead of components itself.