I need some help in figuring out the best way to create a customAttribute that will allow for an easy edit-toggle. Here is what I'm looking for:
<tr toggle-edit>
<td edit-hide>${model.name}</td>
<td edit-show><input type="text" value.bind="model.name"></td>
<td><button edit-trigger>Edit</button></td>
</tr>
So basically I want a customAttribute named toggleEdit that will look for edit-trigger attribute and add an event listener to it that will toogle a variable true/false and depending on it will either show or hide the elements that have edit-hide / edit-show.
I'd prefer to not travers the DOM inside the element to find these attributes as it feels jQuerish, is this achievable?
I want to have a customAttribute like this because I have at least 10 elements that will use an edit button and having a variable for each one of them and then use if.bind seems like a bad idea. I could always do inside of the template itself through click.delegate="myShowVar = !myShowVar" but as far as I know puting logic inside html is a bad practice (coming from an angular background).
I would add a plunker/codepen but because of the whole compilation and libraries dependencies this does not seem like an easy task.
Many thanks for any ideas.
Use the contenteditable attribute
I recommend against trying to have a custom attribute automagically handling this for you. You'll probably run into more problems than you'll solve this way. Instead, I recommend that you create an editable property in your view model and bind to it.
The contenteditable attribute is a standard HTML attribute that allows for editing the content of HTML elements, such as DIVs, and is supported out of the box with Aurelia. I recommend leveraging it if it will meet your needs. Here's how:
table.html
<td contenteditable.bind="editable"></td>
<td><button click.delegate="editable = !editable"></td>
Full running gist here: https://gist.run/?id=c4e716f21f4f9c15a9346cfacbdae74b
Since my <tr></tr> turned out to be a 14 line code (with some animation on toggling) I decided it was best to create a customElement out of it. The problem I ran on was that customElements don't really work as table elements (similar as in other frameworks). The soultion to this is to use a as-element attribute.
Inside of the customElement I used contenteditable which is actually a better solution than using if.bind since swapping between input and a div produces a jumping effect because of the difference in styles (which of course could be circumnavigated by applying certain style to them but contenteditable works out-of-the-box).
This is more or less what I created (parent.html):
<tr as-element="my-custom-row"
title="My first row"
is-editable="true"
model.two-way="myModel.name"
value-changed-callback.call="updateModel(myModel)">
</tr>
And inside my-custom-row.html:
<template>
<td>${title & oneTime}</td>
<td>
<div contenteditable.one-way="editingEnabled ? 'true' : 'false'"
blur.trigger="valueChanged(model)"
textcontent.two-way="model">
</div>
<div class="my-class" class.one-way="editingEnabled ? 'my-class--active' : ''"></div>
</td>
<td>
<span class="edit-icon"
click.delegate="editingEnabled = !editingEnabled"
if.one-time="isEditable">
</span>
</td>
</template>
This way I don't have to create a variable for each edit since editingEnabled is unique to each customElement.
As I side note, I think it's better to be more explicit and to use one-way/two-way/one-time instead of bind since it's clear what is happening.
I will also provide the corresponding js for a full answer(my-custom-row.js):
import { bindable } from 'aurelia-framework';
export class MyCustomRow {
#bindable model;
#bindable title;
#bindable isEditable;
#bindable valueChangedCallback;
constructor() {}
valueChanged(modelValue) {
this.valueChangedCallback({ modelValue })
}
}
Related
Here is an example of the HTML I am working with:
<div //this is the Navigator I have
<div
<div class = 'myClass'
<div
<sort-table
<table
<thead </thead>
<tbody </tbody> //this is the Navigator I want
</table>
</sort-table>
</div>
</div>
... (you get the idea)
I have a method that is supposed to grab the table relative to a specific Navigator. The Navigator in question is linked up to the div I have marked in the HTML above. and I want it to return the tbody.
I am surprised there is no getAllDecendents method for Navigator.
right now my method looks like this:
Navigator getTable(Navigator config){
return config.children($(By.xpath("//div[#class='myClass']"))).children().children().children().children($(By.xpath("//tbody")))
}
It works fine, but I don't like that I have to do the children() chain. I have to imagine there is simply a way to grab a descendant based on a particular selector but I can not find anything like that in the API
I should add there is a very good reason I don't just use xpath to grab that table. This is just a small snippet of the HTML. This same structure is duplicated many times on the page which is why I have the getTable method.
Maybe you want to use .find() instead of .children()?
$("div.myClass").find("tbody")
Is it possible to dynamically define the type of an element inside a custom components template at runtime?
I'd like to avoid duplication of the inner contents of the button and a element in the following example:
<template>
<button if.bind="!isLinkBtn">
<span class="btn-icon">${icon}</span>
<span class="btn-text">${contentText}</span>
</button>
<a if.bind="isLinkBtn">
<!--
The content is a 1:1 duplicate of the button above which should be prevented
somehow in order to keep the view DRY
-->
<span class="btn-icon">${icon}</span>
<span class="btn-text">${contentText}</span>
</a>
</template>
Is it possible to write something like this:
<template>
<!--
The type of element should be defined at runtime and can be a standard HTML "button"
or an anchor "a"
-->
<element type.bind="${isLinkBtn ? 'a' : 'button'}">
<span class="btn-icon">${icon}</span>
<span class="btn-text">${contentText}</span>
</element>
</template>
I'm aware of dynamic composition with <compose view="${widget.type}-view.html"></compose> but as far as I know, this won't allow me to create default HTML elements but only custom components, correct?
I've asked this question on the Aurelia Gitter where Erik Lieben suggested to use a #processContent(function) decorator, replace the content within the given function and return true to let Aurelia process it.
Unfortunately I don't know how to actually apply those instructions and am hoping for some alternative approaches here or some details about how to actually accomplish this.
Edit
I've created a corresponding feature request. Even though possible solutions have been provided, I'd love to see some simpler way to solve this ;)
When you want to reuse HTML snippets, use compose. Doing so does not create a new custom element. It simply includes the HTML at the location of each compose element. As such, the view-model for the included HTML is the same as for the element into which it is composed.
Take a look at this GistRun: https://gist.run/?id=36cf2435d39910ff709de05e5e1bedaf
custom-link.html
<template>
<button if.bind="!isLinkBtn">
<compose view="./custom-link-icon-and-text.html"></compose>
</button>
<a if.bind="isLinkBtn" href="#">
<compose view="./custom-link-icon-and-text.html"></compose>
</a>
</template>
custom-link.js
import {bindable} from 'aurelia-framework';
export class CustomLink {
#bindable() contentText;
#bindable() icon;
#bindable() isLinkBtn;
}
custom-link-icon-and-text.html
<template>
<span class="btn-icon">${icon}</span>
<span class="btn-text">${contentText}</span>
</template>
consumer.html
<template>
<require from="./custom-link"></require>
<custom-link content-text="Here is a button"></custom-link>
<custom-link is-link-btn.bind="true" content-text="Here is a link"></custom-link>
</template>
You may want to split these into separate elements, like <custom-button> and <custom-link> instead of controlling their presentation using an is-link-btn attribute. You can use the same technique to reuse common HTML parts and composition with decorators to reuse the common code.
See this GistRun: https://gist.run/?id=e9572ad27cb61f16c529fb9425107a10
Response to your "less verbose" comment
You can get it down to one file and avoid compose using the techniques in the above GistRun and the inlineView decorator:
See this GistRun: https://gist.run/?id=4e325771c63d752ef1712c6d949313ce
All you would need is this one file:
custom-links.js
import {bindable, inlineView} from 'aurelia-framework';
function customLinkElement() {
return function(target) {
bindable('contentText')(target);
bindable('icon')(target);
}
}
const tagTypes = {button: 'button', link: 'a'};
#inlineView(viewHtml(tagTypes.button))
#customLinkElement()
export class CustomButton {
}
#inlineView(viewHtml(tagTypes.link))
#customLinkElement()
export class CustomLink {
}
function viewHtml(tagType) {
let result = `
<template>
<${tagType}${tagType === tagTypes.link ? ' href="#"' : ''}>
<span class="btn-icon">\${icon}</span>
<span class="btn-text">\${contentText}</span>
</${tagType}>
</template>
`;
return result;
}
Sorry, I was doing 2 things at once while looking at gitter, which I am not good at apparently :-)
For the thing you wanted to accomplish in the end, could this also work?
I am not an a11y expert or have a lot of knowledge on that area, but from what I understand, this will accomplish what you want. The browser will look at the role attribute and handle it as a link or button and ignores the actual element type itself / won't care if it is button or anchor it will act as if it is of the type defined in role.
Then you can style it like a button or link tag with css.
<a role.bind="type"><span>x</span><span>y</span></a>
where type is either link or button, see this: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_link_role
I have a template with several divs, each containing an #click that triggers a specific function and a corresponding visual effect
<template>
<div #click="function doThis(param1)></div>
<div #click="function doThis(param2)></div>
<div #click="function doThis(param3)></div>
<div #click="function doThis(param4)></div>
<div #click="function doThis(param5)></div>
</template>
method: {
doThis(param) {
lightUpDiv(corresponding param)
}
}
I'm also calling doThis from the parent via a $broadcast. What if any, is the best Vueish way to trigger the corresponding lightUpDiv call from the $broadcast? (just use getElementById and .click()???). I used individual divs vs a v-for in the hope that this is easier.
I should note that there are several instances of the template On the page. Even if the same visual effect is caused simultaneously on all the templates that is fine. If there is a way to call them on the specific template instances that is even better.
I've spent hours trying to figure this out and cannot seem to do it. Have read all the guide and API. I know there is some way to bind the data to make the template instances unique but can't seem to get it. Definitely couldn't see the #click from $broadcast solution
Thanks for any help!!
<div #click="doThis"></div>
OR
<div #click="doThis($event)"></div>
Both are functionally equivalent, but second version is recommended as it is more explicit.
In javascript,
doThis: function(ev){
//ev is event object and ev.target refers to the div you need.
alert(ev.target.innerHTML);
}
Demo
Event object details
Given the following:
<div id="#my-container">
<div class="title">Companies</div>
<div class="tab active tab-apple">Apple</div>
<div class="tab tab-google">Google</div>
</div>
When page is loaded without any tab clicks yet, whichever tab with the default active class, needs to go in the .title div. For the example above, <div class="title">Apple</div>
On click of a tab, the class is switched to active, and vue.js needs to update the .title div once again.
How can this be done with vue.js? I've tried but not able to get it to work as intended.
The answer by David is one way to do it. But Vuejs offers in-line computations for this. So, no need to hook into any CSS event. Here's some code to explain:
Create a data property active_tab, just like David mentioned. And then bind it's value just like he's done it. In your tabs, add an click event and at that event, assign appropriate value to active_tab.
<div class="tab active tab-apple" #click="active_tab = Apple">Apple</div>
<div class="tab tab-google" #click="active_tab = Google">Google</div>
Now, to dynamically assign the active class to the respective tab, make the class attribute, a computed property, like this:
<div
:class="['tab', active_tab == 'Apple' ? 'active' : '', 'tab-apple']"
>
Apple
</div>
What this code is basically doing is, :class makes class a computed property. Then the commas in the array divide the statement. So, the computation will always add tab and tab-apple classes. But, only if active_tab == 'Apple' then ? add 'active' else : add ''
Not sure which CSS framework you are using, but normally I hook into the events thrown by the tab switching (many CSS frameworks provide this access). Once hooked into it, you can write a Vue custom directive that will take that event and use it to update a VM attribute that indicates which tab is active.
Then you can use normal mustache templating to get it into your template:
<div class="title">{{ active_tab }}</div>
I want to use a classname to set a background on a element, but i'm not sure if it's possible with LESS js at the moment.
The case is:
I have a table with a list of athletes, on these lists i want to show a country flag as a background of a p tag.
In HTML it looks like:
<table>
<tr>
<td class="athlete"><p class="us">Athlete 1</p></td>
</tr>
</table>
I want to set the background image through a mixin, if i can get the classname from the P tag i would be there.
.athlete {
p {
.setBackgroundImage(#ClassName);
}
}
is there anyway in LESS to achieve this?
The problem is that i need a whole new rule for every possible country. My loop implementation didn't cut it either.
I went for the javascript implementation, i add a background image to a iso coded classname in a certain parent class.
<div class="nationality">
<p class="athlete">Athlete X<span class="nl"></span></p>
</div>
jQuery(".nationality span").css('background-image', 'url(../img/flags/60/'+jQuery(".nationality span").attr("class")+'.png);
Thanks guys.