I have a page, that is made of frames. I want to access elements of the frame and manipulate them. I want to use module to implement my changes. This is how my code looks like.
<html>
<body>
<frame name="header" ></frame>
<frame name="center" >
<input name='quantity' type='text'>
<input name='ok' type='radio' value='ok'>
</frame>
<frame name="footer" ></frame>
Then I have the groovy page classes like this
class myMainPage extends Page {
static content = {
myModule{module FrameModule}
}
}
class FrameModule extends Module {
def FrameElement
static content = {
Myframe { $('frame', name:'center') }
FrameElement {Myframe.find('input[name="quantity"]')}
myQuantity = FrameElement.module(TextInput)
myQuantity.text = '10'
}
}
I tested this like this
Then:
to myMainPage
and:
myModule.myQuantity
with setup of the module I get
no such property TextInput for class: FrameModule
How do I manipulate those frame's element
It looks like you need to import
import geb.module.TextInput
unless you did do that and just didn't include it in your post.
Actually, looking more closely, you should eliminate your FrameElement, and instead declare myQuantity like below. This will eliminate some fluff and simplify it into one line that finds the element and instantiates it as a TextInput module.
myQuantity = {$("input", name: "quantity").module(TextInput)}
Than you should be able to do myModule.myQuantity like you are currently doing.
Related
In Vue.js I can define named slots for my components, besides my default slot:
<article>
<header>
<slot name="header">
<h2>Default heading</h2>
</slot>
</header>
<slot/>
</article>
and then use it like this:
<template>
<FooArticle v-for="item in items">
<template #heading>
<h3>{{item}} Heading</h3>
</template>
<p>Just content</p>
</FooArticle>
</template>
<script>
export default {
name: 'App',
components: {
FooArticle
},
data() {
return {
items: ['First', 'Second']
}
}
}
</script>
Is this possible with Neos Fusion, to create a mechanism like this?
Yes this is possible, as you can use the #path decorator to overwrite a property of the wrapper element.
First you define your props and then output them in the renderer.
prototype(Foo.Components:Article) < prototype(Neos.Fusion:Component) {
heading = afx`<h2>Default heading</h2>`
content = ''
renderer = afx`
<article>
<header>
{props.heading}
</header>
{props.content}
</article>
`
}
Then you want to override these "slots" (props) from the outside with the #path decorator. The whole element the decorator is defined on will override the specified prop "heading" of the wrapping element.
prototype(Foo.Site:Home) < prototype(Neos.Fusion:Component) {
items = ${['First', 'Second']}
renderer = afx`
<Neos.Fusion:Loop items={props.items}>
<Foo.Components:Article>
<Neos.Fusion:Fragment #path="heading">
<h3>{item} heading</h3>
</Neos.Fusion:Fragment>
<p>just some content</p>
</Foo.Components:Article>
</Neos.Fusion:Loop>
`
}
FYI, we use a Neos.Fusion:Fragment object to define the path decorator, so the fragment does not render any additional markup like an enclosing <div>. In this simple case, where we only want to render a single element into the slot, we could have omitted the fragment and just set the #path="heading" directly to the <h3>.
Working example in FusionPen
Fusion AFX Docs
Neos Fusion Docs
I'm creating my custom accordion element. In which I'll have 2 components 1 for ul and other for li.
Content in file accordion-ul.ts, in which I've a slot where I want my li.
import { html, customElement, property, LitElement } from 'lit-element';
import { Accordion } from 'carbon-components';
#customElement('accordion-panel')
export default class AccordionPanel extends LitElement {
firstUpdated() {
const accordionElement = document.getElementById('accordion');
Accordion.create(accordionElement);
}
connectedCallback() {
super.connectedCallback();
}
render() {
return html`
<ul data-accordion class="accordion" id="accordion">
<slot></slot>
</ul>
`;
}
createRenderRoot() {
return this;
}
}
NOTE: I'm getting an error in the console in the firstUpdated() : Uncaught (in promise) TypeError: Cannot read property 'nodeName' of null.
The way I'm using it for testing:
<accordion-panel><li>test</li></accordion-panel>
IDK, it's not working and nothing is printing on the screen. On inspecting the element, I can see there's empty in DOM.
Your problem is that you're trying to use slots, which are a shadow DOM feature but you're not using shadow DOM (since you're overwriting the createRenderRoot method to prevent the creation of the shadowRoot)
So, if you want to use slots, just remove the createRenderRoot function from your class and use shadow DOM
Edit:
You should also update your firstUpdated method so that this part:
const accordionElement = document.getElementById('accordion');
Uses the element from your shadow DOM
const accordionElement = this.shadowRoot.querySelector('.accordion');
Then again, CarbonComponents styling will probably not work so you'll need to add those in some other way
Does lit-html have by any change something like React's ref feature?
For example in the following pseudo-code inputRef would be a callback function or an object { current: ... } where lit-html could pass/set the HTMLElement instance of the input element when the input element is created/attached.
// that #ref pseudo-attribute is fictional
html`<div><input #ref={inputRef}/></div>`
Thanks.
In lit-element you can use #query property decorator. It's just syntactic sugar around this.renderRoot.querySelector().
import { LitElement, html, query } from 'lit-element';
class MyElement extends LitElement {
#query('#first')
first;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
lit-html renders directly to the dom so you don't really need refs like you do in react, you can use querySelector to get a reference to the rendered input
Here's some sample code if you were only using lit-html
<html>
<head>
<title>lit-html example</title>
<script type="module">
import { render, html } from 'https://cdn.pika.dev/lit-html/^1.1.2';
const app = document.querySelector('.app');
const inputTemplate = label => {
return html `<label>${label}<input value="rendered input content"></label>`;
};
// rendering the template
render(inputTemplate('Some Label'), app);
// after the render we can access it normally
console.log(app.querySelector('input').value);
</script>
</head>
<body>
<div class="app"></div>
<label>
Other random input
<input value="this is not the value">
</label>
</body>
</html>
If you're using LitElement you could access to the inner elements using this.shadowRoot.querySelector if you're using shadow dom or this.querySelector if you aren't
As #WeiChing has mentioned somewhere above, since Lit version 2.0 you can use the newly added directive ref for that:
https://lit.dev/docs/templates/directives/#ref
-- [EDIT - 6th October 2021] ----------------------------
Since lit 2.0.0 has been released my answer below
is completely obsolete and unnecessary!
Please check https://lit.dev/docs/api/directives/#ref
for the right solution.
---------------------------------------------------------
Even if this is not exactly what I have asked for:
Depending on the concrete use case, one option to consider is the use of directives.
In my very special use-case it was for example (with a little luck and a some tricks) possible to simulate more or less that ref object behavior.
const inputRef = useElementRef() // { current: null, bind: (special-lit-html-directive) }
...
return html`<div><input ref=${inputRef.bind}/></div>`
In my use case I could do the following:
Before rendering, set elementRef.current to null
Make sure that elementRef.current cannot be read while the component is rerendered (elementRef.current is not needed while rendering and an exception will be thrown if someone tries to read it in render phase)
That elementRef.bind directive will fill elementRef.current with the actual DOM element if available.
After that, elementRef.current can be read again.
For lit-html v1, you can define your own custom Derivative:
import { html, render, directive } from "lit-html";
function createRef(){ return {value: null}; }
const ref = directive((refObj) => (attributePart) => {
refObj.value = attributePart.committer.element;
});
const inputRef = createRef();
render(html`<input ref=${ref(inputRef)} />`;
// inputRef.value is a reference to rendered HTMLInputElement
I would like to create a custom message renderer to renders h:message as a 'p' html element instead of as a 'span' element. It concerns the following message tag:
<h:message id="firstNameErrorMsg" for="firstname" class="error-msg" />
I've written to code underneath, but that's only rendering an empty 'p' element. I suppose I have to copy all attributes and text from the original component and write it to the writer. However, I don't know where to find everything and it seems to be a lot of work for just a replacement of a tag.
Is there a better way to get an h:message tag rendered as a 'p' element?
Code:
#FacesRenderer(componentFamily = "javax.faces.Message", rendererType = "javax.faces.Message")
public class FoutmeldingRenderer extends Renderer {
#Override
public void encodeEnd(final FacesContext context, final UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("p", component);
writer.endElement("p");
}
}
It isn't exactly "a lot of work". It's basically a matter of extending from the standard JSF messages renderer, copypasting its encodeEnd() method consisting about 200 lines then editing only 2 lines to replace "span" by "p". It's doable in less than a minute.
But yes, I agree that this is a plain ugly approach.
You can consider the following alternatives which are not necessarily more easy, but at least more clean:
First of all, what's the semantic value of using a <p> instead of a <span> in this specific case? To be honest, I'm not seeing any semantic value for this. So, I suggest to just keep it a <span>. If the sole functional requirement is to let it appear like a <p>, then just throw in some CSS. E.g.
.error-msg {
display: block;
margin: 1em 0;
}
You can obtain all messages for a particular client ID directly in EL as follows, assuming that the parent form has the ID formId:
#{facesContext.getMessageList('formId:firstName')}
So, to print the summary and detail of the first message, just do:
<c:set var="message" value="#{facesContext.getMessageList('formId:firstName')[0]}" />
<p title="#{message.detail}">#{message.summary}</p>
You can always hide it away into a custom tag file like so:
<my:message id="firstNameErrorMsg" for="firstname" class="error-msg" />
Use OmniFaces <o:messages>. When the var attribute is specified, then you can use it like an <ui:repeat>.
<o:messages for="firstNameErrorMsg" var="message">
<p title="#{message.detail}">#{message.summary}</p>
</o:messages>
I have a razor view where I call this:
#Html.EditorFor(m => m.Code)
Then I have an EditorTemplate view which is rendered then, it looks like this:
#model string
#Html.HiddenFor(m=>m)
And I get this output:
<input id="Code" name="Code" type="hidden" value="" />
Everything fine.
But now I just want to get the property name Code inside the EditorTemplate view, not the whole input string. The #Html.HiddenFor(m=>m) method is able to get it from the model object somehow and put it into the input field but how can I do this?
(And no, I don't want to parse it from the input field string... :)
#Html.HiddenFor(m=>m) seems kind of weird... but anyway:
HiddenFor uses Expression<Func<TModel, TProperty>> so you could make your own function to only return the name:
public static class GenericHelper<T>
{
public static String GetPropertyName<TValue>(Expression<Func<T, TValue>> propertyId)
{
var operant = (MemberExpression)((UnaryExpression)propertyId.Body).Operand;
return operant.Member.Name;
}
}
And create a helper to use that inside your view, which is explained here