Aurelia: always call method in the view (problems after upgrade) - javascript

We've upgraded Aurelia (in particular aurelia-framework to 1.0.6, aurelia-bindong to 1.0.3) and now we're facing some binding issues.
There's a list of elements with computed classes, and we had a method int the custom element that contained the list:
getClass(t) {
return '...' +
(this.selected.indexOf(t) !== -1
? 'disabled-option' :
: ''
) + (t === this.currentTag
? 'selected-option'
: ''
);
}
And class.one-way="$parent.getClass(t)" for the list element, everything was OK.
After the upgrade it simply stopped to work, so whenever the selected (btw it's bindable) or currentTag properties were modified, the getClass method just wasn't called.
I partially solved this by moving this logic to the view:
class="${$parent.getClass(t) + (selected.indexOf(t) !== -1 ? 'disabled-option' : '') (t === $parent.currentTag ? 'selected-option' : '')}"
I know that looks, well... bad, but that made t === $parent.currentTag work, but the disabled-option class still isn't applied.
So, the question is:
How do I force Aurelia to call methods in attributes in the view?
P.S.
I understand that it might cause some performance issues.
Small note:
I can not simply add a selected attribute to the list element since I don't to somehow modify the data that comes to the custom element and I basically want my code to work properly without making too many changes.
UPD
I ended up with this awesome solution by Fabio Luz with this small edit:
UPD Here's a way to interpret this awesome solution by Fabio Luz.
export class SelectorObjectClass {
constructor(el, tagger){
Object.assign(this, el);
this.tagger = tagger;
}
get cssClass(){
//magic here
}
}
and
this.shown = this.shown(e => new SelectorObjectClass(e, this));
But I ended up with this (defining an extra array).

You have to use a property instead of a function. Like this:
//pay attention at the "get" before function name
get getClass() {
//do your magic here
return 'a b c d e';
}
HTML:
<div class.bind="getClass"></div>
EDIT
I know that it might be an overkill, but it is the nicest solution I found so far:
Create a class for your objects:
export class MyClass {
constructor(id, value) {
this.id = id;
this.value = value;
}
get getClass() {
//do your magic here
return 'your css classes';
}
}
Use the above class to create the objects of the array:
let shown = [];
shown[1] = new MyClass('someId', 'someValue');
shown[2] = new MyClass('someId', 'someValue');
Now, you will be able to use getClass property:
<div repeat.for="t of shown" class.bind="t.getClass">...</div>
Hope it helps!

It looks pretty sad.
I miss understand your point for computing class in html. Try that code, it should help you.
computedClass(item){
return `
${this.getClass(item)}
${~selected.indexOf(item) ? 'disabled-option': ''}
${item === this.currentTag ? 'selected-option' : ''}
`;
}
Your code not working cause you miss else option at first if state :/
Update:
To toggle attribute state try selected.bind="true/false"
Good luck,
Egor

A great solution was offered by Fabio but it caused issues (the data that was two-way bound to the custom element (result of the selection) wasn't of the same type as the input and so on). This definitely can be fixed but it would take a significant amount of time and result in rewriting tests, etc. Alternatively, yeah, we could put the original object as some property blah-blah-blah...
Anyway:
There's another solution, less elegant but much faster to implement.
Let's declare an extra array
#bindable shownProperties = [];
Inject ObserverLocator
Observe the selected array
this.obsLoc.getArrayObserver(this.selected)
.subscribe(() => this.selectedArrayChanged);
Update the shownProperties
isSelected(t) {
return this.selected.indexOf(t) !== -1;
}
selectedArrayChanged(){
for(var i = 0; i < this.shown.length; i++){
this.shownProperties[i] = {
selected: this.isSelected(this.shown[i])
}
}
}
And, finally, in the view:
class="... ${shownProperties[$index].selected ? 'disabled-option' : '')} ..."
So, the moral of the story:
Don't use methods in the view like I did :)

Related

Get element by ID returns null

const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function libraryRoot() {
load();
return (`<div id="appDiv">
${data.map(function(value){
return `<div><p>Color ${value.color} from libraryRoot</p>`
}).join("")}
</div>
`);
}
window.onload = libraryRoot;
function load() {
let a = document.getElementById("appDiv");
console.log(a);
}
let defaultLayout = libraryRoot();
document.getElementById("root").innerHTML = defaultLayout;
<div>
<div id="root"></div>
</div>
Hi Guys i modified the script as you guys suggested, but still the return value at the first instance prints null, and then it prints the div.can you guys help me where im going wrong.
All i wanted to do is i want to call the "appDiv" id and wirte a button funcion to it. like on click {//do something}.
updated Codepen Project
You won't be able to access the element until it's written to the DOM.
Notice the word document in document.getElementById(). It's a method of the document API.
DOM stands for Document Object Model.
If you want to modify it before then then split your string literal into different pieces. Assign specific variables to important parts of the element.
Then modify them. Concatenate them back into one string and add them to the DOM.
Your code could use some comments. It's a little unclear what you're trying to do.
Here I've modified your pen to show some different ways to handle each color in your data array. You can do some conditional logic and return different template strings based on that. You could easily pass data object properties to another javascript function using the onclick attribute.
Hopefully this helps you get closer to your goal
const data = [{color : "red"},{color : "blue"}, {color : "green"} ];
function alertFunction(color) {
alert(color);
}
function libraryRoot(){
return('<div id="appDiv">' +
data.map(function(value) {
var result = `<div><p>Color ${value.color} from libraryRoot</p>`;
if (result.indexOf("red") > -1) { // checking if the div includes red
return result;
} else if (`${value.color}` == "blue") { // checking if the objects color prop includes blue
return "<p>blue</p>";
} else {
return `<button onclick="alertFunction('${value.color}')">Button with function</button>`;
}
}).join("")
+ "</div>"
);
}
window.onload = libraryRoot;
document.getElementById("root").innerHTML = libraryRoot();
<div>
<div id="root"></div>
</div>
The first issue is that you are calling load() first, which is trying to access the appDiv node that doesn't exist yet. So rethink the order on that.
The second issue is that you never actually added the appDiv content to the DOM. You can use things like document.appendChild(libraryRoot()); or something like that, though I have never injected a string as a DOM node - I use createElement for that. The link has some examples.

Custom word translator in React

Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.

Reduce not removing correct array item

Edit 1
Shortened the code to
removeContentNew(i) {
var contents = this.state.templateContent;
contents.splice(i, 1);
this.setState({ templateContent: contents });
}
It might have something to do with this:
componentDidMount() {
this.setState({ templateContent: this.props.template.content });
}
Still removing the wrong one on screen. When I log the state, it does give me the right array though. Maybe something wrong with the map?
--
I'm trying to bug fix this piece of code but I can't seem to find the error.
removeContent(i) {
var $formgroup = false;
const regex = new RegExp('^content.', 'i'),
contents = _.reduce(_.keys(this.refs), (memo, k) => {
if (regex.test(k) && k !== 'content.' + i) {
var $formgroup = $(this.refs[k]);
if (this.props.customer.getSetting('wysiwyg_enabled', true)) {
var html = CKEDITOR.instances['html_' + i].getData();
} else {
var html = $formgroup.find('[name="html"]').val();
}
memo.push({
subject: $formgroup.find('[name="subject"]').val(),
html: html,
text: $formgroup.find('[name="text"]').val(),
language: $formgroup.find('[name="language"]').val()
});
}
return memo;
}, []);
this.setState({ templateContent: contents });
}
i is the ID of the item I want to remove from the array templateContents. Every time I press the remove button of one of the items it always seems to delete the last one and ignores the other ones.
I've been doing some testing with the k variable and that one might be the cause of the problems, but I am not sure at all.
I'm really quite new to the RegExp way of doing things.
Any ideas how I can fix this?
Update your state in the constructor instead of inside componentDidMount method.
constructor(pops) {
super(props);
this.state = {
templateContent: this.props.template.content
}
}
Also calls to setState are async so you don't have any security when the changes is being executed
The issue was in the mapping of the array. I'll leave this here because it helped me solve my issue.
Bad (Usually)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
This is arguably the most common mistake seen when iterating over an
array in React. This approach will work fine unless an element is
added or removed from the rows array. If you are iterating through
something that is static, then this is perfectly valid (e.g an array
of links in your navigation menu). Take a look at this detailed
explanation on the official documentation.

Rendering custom html tag with react.js

What I'm trying to do is quite easy at first however I get an (obviously completely useless) error from webpack and I'm wondering how it can be fixed, I want a simple "custom" tag to be rendered by React, the code is as follows:
let htmlTag = "h" + ele.title.importance;
let htmlTagEnd = "/h" + ele.title.importance;
return(
<{htmlTag} key={elementNumber}>{ele.title.content}<{htmlTagEnd}>
);
Basically instead of having a predefined tag I want to have my own {template} tag, I know in this situation there would be work arounds for this (e.g. defining a className with my "importance" value and adding some css for that), but for the sake of science I'd like to know how (and if) this can be done in react/jsx.
JSX doesn't allow you to use dynamic HTML tags (dynamic components would work). That's because whenever you use something like <sometag ... />, an HTML element with tag name sometag is created. sometag is not resolved as a variable.
You also can't do what you have shown above. JSX expressions are not valid in place of a tag name.
Instead, you have to call React.createElement directly:
return React.createElement(
"h" + ele.title.importance,
{
key: elementNumber,
},
ele.title.content
);
Edit
My initial answer was not correct, you cannot use a variable directly and would need to use the createElement method described in Felix's answer. As noted below, and utilised in the blog post I originally linked, you can use object properties, so I've made an example of this, which hopefully will be useful as an answer to the question.
class Hello extends React.Component {
constructor() {
super();
this.state = {
tagName: "h1"
};
}
sizeChange(i) {
this.setState({
tagName: 'h' + i
});
}
changeButtons() {
var buttons = [];
for (let i=1; i<=6; i++) {
buttons.push(<button onClick={() => this.sizeChange(i)}>H{i}</button>);
}
return buttons;
}
render() {
return (
<div>
{this.changeButtons()}
<this.state.tagName>
Change Me
</this.state.tagName>
</div>
);
}
}
JSFiddle here
Original Answer
It can be done, although I don't think it is officially supported so may break in the future without warning. The caveat to this approach is that the variable name you choose for your tag cannot be the same as an HTML element.
var Demo = React.createClass({
render: function() {
const elementTag = 'h' + ele.title.importance;
return(
<elementTag>
Header x contents
</elementTag>
);
}
});
More explanation and a fuller example can be found here

Why use jQuery's css() function instead of maxHeight or the other named CSS functions?

After reading jQuery's CSS documentation, it doesn't look like it offers any advantages over just getting the Javascript element directly and manipulating its CSS by updating the property. Am I missing something?
You should use the jQuery css method, it provides many benefits:
You can set multiple css properties inside a single .css call.
You can pass integer values and it will automatically convert to px.
It normalizes many cross-browser issues. For example, I can just use .css('opacity', 0.8) without having to test if the user is using IE and applying ugly alpha workarounds.
I find $('#foo').css('prop', 'value') more organized and readable than
$('#foo')[0].style.prop = 'value';
Let alone .css provides other jQuery's functionalities, such as chaining methods and automatically iterating through element arrays.
jQuery makes DOM lookup much easier, and I like the CSS function in jQuery because I don't need to remember the names of additional function to manipulate the style. I can use .css() in conjunction with the standard CSS properties and values.
One advantage could be to separate what styles you're setting from the act of setting them. Perhaps you dynamically construct the styles in JavaScript code elsewhere, for example. This would allow you to tweak that logic without having to tweak the code that applies the styles.
Or perhaps you'd like to make a "configurable" script and put all of the styles into header variables to separate into a section of configurable options. (Writing your own jQuery plugin often involves this.) You could bury the code which applies the styles in the "do not modify below this line" section of the file, leaving the settable properties where people can configure them.
jQuery's $.fn.css really doesn't do much of anything. I mean, here's the source function itself:
css: function( elem, name, extra ) {
var ret, hooks;
// Make sure that we're working with the right name
name = jQuery.camelCase( name );
hooks = jQuery.cssHooks[ name ];
name = jQuery.cssProps[ name ] || name;
// cssFloat needs a special treatment
if ( name === "cssFloat" ) {
name = "float";
}
// If a hook was provided get the computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
return ret;
// Otherwise, if a way to get the computed value exists, use that
} else if ( curCSS ) {
return curCSS( elem, name );
}
}
Oh, I guess when I said "doesn't do much of anything" i really meant that it normalizes names so that you can use hyphen-notation instead of camelCase, supports cross-browser compatibility for opacity and normalizes the property name for float before returning the appropriate value.
I suppose I also glossed over the fact that this is only the accessor version of the function, the mutator method is:
style: function( elem, name, value, extra ) {
// Don't set styles on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
// Make sure that we're working with the right name
var ret, type, origName = jQuery.camelCase( name ),
style = elem.style, hooks = jQuery.cssHooks[ origName ];
name = jQuery.cssProps[ origName ] || origName;
// Check if we're setting a value
if ( value !== undefined ) {
type = typeof value;
// convert relative number strings (+= or -=) to relative numbers. #7345
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
// Fixes bug #9237
type = "number";
}
// Make sure that NaN and null values aren't set. See: #7116
if ( value == null || type === "number" && isNaN( value ) ) {
return;
}
// If a number was passed in, add 'px' to the (except for certain CSS properties)
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
}
// If a hook was provided, use that value, otherwise just set the specified value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
// Fixes bug #5509
try {
style[ name ] = value;
} catch(e) {}
}
} else {
// If a hook was provided get the non-computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
}
// Otherwise just get the value from the style object
return style[ name ];
}
}
So, all-in-all the advantages are that you don't have to worry about cross-browser issues when trying to dynamically style HTML elements, because the dedicated jQuery devs have already normalized everything nicely into one function.
code from jQuery version 1.7.2
There are quite a few benefits over the base JS implementation, here are my favorites:
use a selector $('a').css(....) and it will apply that CSS to ALL selector matched "a"s. You would have to use a loop otherwise which would create more code
can pass an object {} in and it adds styles that way
can execute a function to compute the value (like in the loop mentioned above).
All this results in a little bit cleaner and more concise code in my opinion.

Categories