How to access custom attributes from event object in React? - javascript

React is able to render custom attributes as described at
http://facebook.github.io/react/docs/jsx-gotchas.html:
If you want to use a custom attribute, you should prefix it with
data-.
<div data-custom-attribute="foo" />
And that's great news except I can't find a way to access it from the event object e.g.:
render: function() {
...
<a data-tag={i} style={showStyle} onClick={this.removeTag}></a>
...
removeTag: function(event) {
this.setState({inputVal: event.target????});
},
The element and data- property render in html fine. Standard properties like style can be accessed as event.target.style fine.
Instead of event.target I tried:
event.target.props.data.tag
event.target.props.data["tag"]
event.target.props["data-tag"]
event.target.data.tag
event.target.data["tag"]
event.target["data-tag"]
none of these worked.

event.target gives you the native DOM node, then you need to use the regular DOM APIs to access attributes. Here are docs on how to do that:Using data attributes.
You can do either event.target.dataset.tag or event.target.getAttribute('data-tag'); either one works.

To help you get the desired outcome in perhaps a different way than you asked:
render: function() {
...
<a data-tag={i} style={showStyle} onClick={this.removeTag.bind(null, i)}></a>
...
},
removeTag: function(i) {
// do whatever
},
Notice the bind(). Because this is all javascript, you can do handy things like that. We no longer need to attach data to DOM nodes in order to keep track of them.
IMO this is much cleaner than relying on DOM events.
Update April 2017: These days I would write onClick={() => this.removeTag(i)} instead of .bind

Here's the best way I found:
var attribute = event.target.attributes.getNamedItem('data-tag').value;
Those attributes are stored in a "NamedNodeMap", which you can access easily with the getNamedItem method.

Or you can use a closure :
render: function() {
...
<a data-tag={i} style={showStyle} onClick={this.removeTag(i)}></a>
...
},
removeTag: function (i) {
return function (e) {
// and you get both `i` and the event `e`
}.bind(this) //important to bind function
}

// Method inside the component
userClick(event){
let tag = event.currentTarget.dataset.tag;
console.log(tag); // should return Tagvalue
}
// when render element
<a data-tag="TagValue" onClick={this.userClick}>Click me</a>

<div className='btn' onClick={(e) =>
console.log(e.currentTarget.attributes['tag'].value)}
tag='bold'>
<i className='fa fa-bold' />
</div>
so e.currentTarget.attributes['tag'].value works for me

As of React v16.1.1 (2017), here is the official solution: https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers
TLDR: OP should do:
render: function() {
...
<a style={showStyle} onClick={(e) => this.removeTag(i, e)}></a>
...
removeTag: function(i, event) {
this.setState({inputVal: i});
}

This single line of code solved the problem for me:
event.currentTarget.getAttribute('data-tag')

You can access data attributes something like this
event.target.dataset.tag

If anyone is trying to use event.target in React and finding a null value, it is because a SyntheticEvent has replaced the event.target. The SyntheticEvent now holds 'currentTarget', such as in event.currentTarget.getAttribute('data-username').
https://facebook.github.io/react/docs/events.html
It looks like React does this so that it works across more browsers. You can access the old properties through a nativeEvent attribute.

You can simply use event.target.dataset object . This will give you the object with all data attributes.

I do not know about React, but in the general case you can pass custom attributes like this:
1) define inside an html-tag a new attribute with data- prefix
data-mydatafield = "asdasdasdaad"
2) get from javascript with
e.target.attributes.getNamedItem("data-mydatafield").value

Try instead of assigning dom properties (which is slow) just pass your value as a parameter to function that actually create your handler:
render: function() {
...
<a style={showStyle} onClick={this.removeTag(i)}></a>
...
removeTag = (customAttribute) => (event) => {
this.setState({inputVal: customAttribute});
}

This worked for me... My attribute is named "attr" in the example.
e.target.selectedOptions[0].attributes.attr.value

If you have multiple icons with different data-label (age,name,email):
<button
data-label="name"
onMouseOver={handleValue}
className="icon"
>
<FaUser />
</button>
when the mouse is over an icon, you change the title by accessing data-label
const handleValue = (e) => {
// making sure mouse is over an icon
if (e.target.classList.contains("icon")) {
const newValue = e.target.dataset.label;
setTitle(newValue);
setValue(person[newValue]);
}
};

In React you don't need the html data, use a function return a other function; like this it's very simple send custom params and you can acces the custom data and the event.
render: function() {
...
<a style={showStyle} onClick={this.removeTag(i)}></a>
...
removeTag: (i) => (event) => {
this.setState({inputVal: i});
},

I think it's recommended to bind all methods where you need to use this.setState method which is defined in the React.Component class, inside the constructor, in your case you constructor should be like
constructor() {
super()
//This binding removeTag is necessary to make `this` work in the callback
this.removeTag = this.removeTag.bind(this)
}
removeTag(event){
console.log(event.target)
//use Object destructuring to fetch all element values''
const {style, dataset} = event.target
console.log(style)
console.log(dataset.tag)
}
render() {
...
<a data-tag={i} style={showStyle} onClick={this.removeTag.bind(null, i)}></a>
...},
For more reference on Object destructuring
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring

Related

calling vue function from js on change

I'm trying to make a call from a on.("change") event to a vue method and that works fine but trying to give the received data from the change event to a Vue variable, the console log says that the variable has the new data, but it doesn't really change the variable correctly, it changes the last variable when you duplicate the components.
here is some of my code:
Vue.component('text-ceditor',{
props:['id'],
data: function (){
return {
dataText: "this is something for example"
}
},
template: '#text-ceditor',
methods: {
setData: function(data){
console.log(data)
this.dataText = data
console.log(this.dataText)
}
},
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
})
the component works correctly but the variable just change the last one
here is the fiddle: https://jsfiddle.net/labradors_/3snmcu84/1/
Your problem isn't with Vue but with CKEDITOR and its instances (with the ids you defined in the template and the way you reference them).
First problem is that you're duplicating ids in the text-ceditor component:
<textarea ref="text" v-model="dataText" id="texteditor" rows="10" cols="80"></textarea>
Why do we need to fix this? Because CKEDITOR instances in Javascript are id-based.
So now we need to change the id attribute to use the one passed in the component's props, like this:
<textarea ref="text" v-model="dataText" :id="id" rows="10" cols="80"></textarea>
Once we took care of that, let's reference the correct CKEDITOR instance from within the mounted method in the component.
We want to reference the one that matches with the id in our component.
From:
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
To:
mounted: function () {
CKEDITOR.replace(this.$refs.text);
var self = this;
var myCKEInstance = CKEDITOR.instances[self.id]
myCKEInstance.on('change', function () {
self.dataText = myCKEInstance.getData()
})
}
Notice that I also removed the call to setData as there is no need to have it and also declared self as a variable avoiding the global scope (which would overwrite it everytime and reference the latest one in all different components).
Now everything is updating correctly, here's the working JSFiddle.

Execute a nested function in vue

I have two functions that are nested in vue, the parent function is supposed to get the value of an attribute, while the child is supposed to use the value of the attribute to make an api call. How can I execute this function once to ensure I get this attribute and make the api call at once?
//button with the attribute I want
<button :data-post-id="My id">Click Me</button>
//Here I'm calling the parent function
<button #click="getPostId">Submit to api</button>
Javascript
getPostId: function (evt) {
const postId = evt.target.getAttribute('data-postid');
//console.log(postId);
function usePostId(){
console.log("I am accessible here " +postId)//null
}
return usePostId()
}
Your approach will create function multiple time, Just start with the simple function and keep separate.
new Vue({
el: '#app',
data: {
postid: ''
},
methods:{
setPostId: function (id){
this.postid = id;
},
getPostId: function () {
console.log(this.postid);
}
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<div id="app">
<button #click="setPostId(11)">Set 11</button>
<button #click="setPostId(22)">Set 22</button>
<button #click="setPostId(33)">Set 33</button>
<button #click="getPostId">Get postid</button>
<div>{{postid}}</div>
</div>
I am no vue expert but I can spot one inconsistency.
You are binding your callback to child but set the attr data-post-id on parent and then expecting child to have that attr. Also, it seems the attribute name doesn't match i.e. what you have set and what you are trying to get.
As for the original problem, i am not sure why you didn't add the attribute to child element as well and in case you can't do that you will need to find the desired parent through DOM.
#mots you could do something like the below,
usePostId: function(id) {
console.log("I am accessible here " + id)
},
getPostId: function(evt) {
const postId = evt.target.getAttribute('data-post-id')
const output = this.usePostId(postId)
return output
}

jsViews $.observable(arr).insert() not triggering DOM update

I am using $.observable(array).insert() to append items to a list. This is updating my view as it should: new list items are rendered to the DOM. However, I would like to issue a click event on the new DOM node (I'm relying on the event to add a class to expand the item and attach another listener to the body so the area can be closed).
I have tried both
$.observable(_model.leadTimes).insert(leadTime);
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
...and
function watchLeadTimes() {
var changeHandler = function (ev, eventArgs) {
if (eventArgs.change === 'insert') {
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
}
};
$.observe(_model.leadTimes, changeHandler);
}
And neither of them worked, however, if I wrap the jQuery method in a setTimout, like setTimeout(function () { $leadTimes.find('.lead-time-data').last().find('.start-editing').click(); }, 400);, it does work, leading me to believe this is an issue of timing with the DOM render somehow not finishing before my jQuery click() method is invoked.
Since the odds are decent that you will see this, Borris, thank you for the library and all that you do! I think jsViews is an excellent middle ground between the monolithic frameworks out there and plain old jQuery noodling!
Edit 02/09/17
It turns out my issue was overlapping click events--I was inadvertently handling a click to deselect my element immediately after it was selected. However I took the opportunity to rewrite things to use a more declarative approach following Borris' linked example.
Now in my template I am using a computed observable, isSelected to toggle the .editing class:
{^{for leadTimes}}
<tr class="lead-time-data" data-link="class{merge:~isSelected() toggle='editing'}">
<span>{^{:daysLead}}</span>
</tr>
{{/for}}
And this JS:
function addNewLeadTimeClickHandler() {
var onNewLeadTimeClick = function () {
e.stopPropagation(); // this is what I was missing
var leadTime = {
daysLead: 1,
description: ''
};
$.observable(_model.activityMapping.leadTimes).insert(leadTime);
selectLeadtime(_model.activityMapping.leadTimes.length -1);
}
$leadTimes.on('click', '.add', onNewLeadTimeClick);
}
function selectLeadtime(index) {
var addStopEditingClickHandler = function () {
var onClickHandler = function (event) {
if ($(event.target).closest('tr').hasClass('editing')) {
setHandler();
return;
}
selectLeadtime(-1)
};
function setHandler() {
var clickEvent = 'click.ActivityChangeRequestDetailController-outside-edit-row';
$('html:not(.edit)').off(clickEvent).one(clickEvent, onClickHandler);
};
setHandler();
}
if (_model.selectedLeadtimeIndex !== index) {
$.observable(_model).setProperty('selectedLeadtimeIndex', index)
addStopEditingClickHandler();
}
}
function isSelected() {
var view = this;
return this.index === _model.selectedLeadtimeIndex;
}
// isSelected.depends = ["_model^selectedLeadtimeIndex"];
// for some reason I could not get the above .depends syntax to work
// ...or "_model.selectedLeadtimeIndex" or "_model.selectedLeadtimeIndex"
// but this worked ...
isSelected.depends = function() {return [_model, "selectedLeadtimeIndex"]};
The observable insert() method is synchronous. If your list items are rendered simply using {^{for}}, then that is also synchronous, so you should not need to use setTimeout, or a callback. (There are such callbacks available, but you should not need them for this scenario.)
See for example http://www.jsviews.com/#samples/editable/tags (code here):
$.observable(movies).insert({...});
// Set selection on the added item
app.select($.view(".movies tr:last").index);
The selection is getting added, synchronously, on the newly inserted item.
Do you have other asynchronous code somewhere in your rendering?
BTW generally you don't need to add new click handlers to added elements, if you use the delegate pattern. For example, in the same sample, a click handler to remove a movie is added initially to the container "#movieList" with a delegate selector ".removeMovie" (See code). That will work even for movies added later.
The same scenario works using {{on}} See http://www.jsviews.com/#link-events: "The selector argument can target elements that are added later"

Knockout two way binding

i am trying to make a two way binding in Knockout.js, but i am not pretty sure, that my approach is the right suggestion.
What i need is very simple:
I need the id of the binded element of my observable.
Here is my first approach:
HTML:
<div id='test' data-bind="attr {id: 'test'}, html: id"></div>
Javascript:
var vm = {
id: ko.observable()
};
ko.applyBindings(vm);
In the end, i need the id iformation in my viewmodel.
Maybe it´s not possible and not really reliable to knockout. But i dont want to go through the domtree with jquery selector if dont have the information in my viewmodel.
Thanks in advance
You need to give id in observable
id: ko.observable('test')
this will produce id
Fiddle Demo
From the comments on the original question, I don't think you're looking for two-way binding - you're looking for a way to cache the jQuery selector so that it can be accessed in your view model.
For that, I would suggest the following:
Add properties or variables in your view model that will hold the selector results. These do not need to be observables, as the IDs of your elements will never change.
Create a function that you call once on initialization of your view model, that will assign the results of the jQuery selectors to their respective properties/variables.
Subscribe to whatever observable contains the data, and trigger your animation from there.
Here's an example of how this could be done in your view model (JSFiddle example):
var ViewModel = ( function () {
var ViewModel = function () {
// ... stuff
this.data = ko.observable( 'No data here :(' );
this.data.subscribe( this.animate.bind( this ) );
};
// This is the function where you store the result of the jQuery selectors
ViewModel.prototype.cacheSelectors = function () {
this.testElement = $( '#test' );
};
// This is an example function that will load your data
ViewModel.prototype.loadData = function () {
this.data( 'Oh wait, here\'s some data!' );
};
// This is an example function that you could trigger to animate your element
ViewModel.prototype.animate = function () {
this.testElement.animate( { 'padding-left': '+=250px' }, 'slow' );
};
return new ViewModel();
}() );
ViewModel.cacheSelectors();
ko.applyBindings( ViewModel );

Refer to immediate selector object with jQuery?

I'm trying to learn some jQuery, and I setup a test page with the following code:
<a id='encode' href='javascript: void(0)'>encode</a> |
<a id='decode' href='javascript: void(0)'>decode</a> |
<br/>
<textarea id='randomString' cols='100' rows='5'></textarea>
<script type='text/javascript'>
$(document.ready(function () {
$('#encode').click(function() {
$('#randomString').val(escape($('#randomString').val()));
});
$('#decode').click(function() {
$('#randomString').val(unescape($('#randomString').val()));
});
});
</script>
The idea is I can put something in the textarea and click either "encode" or "decode", and it will either escape or unescape what I put into the textarea.
This code works just fine, but my question has to do with how I am changing the value of the textarea. In my code, I am selecting the textarea value twice: once to (un)escape it, and once again to change the value. IMO this seems clunky and maybe unnecessary. I thought maybe I could do something like this instead:
$('#randomString').val(escape(this));
But this seems to refer to the object of the link I clicked, not the #randomString selector, so is there some other magic word I can use to reference that $('#randomString')?
$('#randomString').val(escape(this));
This does not get the object you want. It is effectively the equivalent of doing this:
var foo = escape(this);
$('#randomString').val(foo);
this only means something different when you start a new scope with a function definition.
jQuery does offer this kind of functionality with a callback option:
$('#randomString').val(function (idx, oldVal) {
return escape(oldVal);
});
The second parameter is the current value of the element; the return value sets a new value for the element.
You can try this
$(document.ready(function () {
$('#encode').click(function() {
var $randomString = $('#randomString');
$randomString.val(escape($randomString.val()));
});
$('#decode').click(function() {
var $randomString = $('#randomString');
$randomString.val(unescape($randomString.val()));
});
});
The short answer, if I understand you correctly, is no. There isn't a way to refer to $('#randomString') where you're talking about. It's just a parameter to the val method, so it's just plain JavaScript syntax, no jQuery "magic".
To accomplish the task at hand and make the code cleaner and less clunky, I would save off the jQuery object for #randomString so you don't have to keep creating it:
$(document.ready(function () {
var $rndStr = $('#randomString');
$('#encode').click(function() {
$rndStr.val(escape($rndStr.val()));
});
$('#decode').click(function() {
$('#rndStr').val(unescape($rndStr.val()));
});
});
You could make it a little generic:
$.fn.applyVal = function(func) {
return this.each(function() {
$(this).val( func( $(this).val() ) );
});
};
Then the following call is enough:
$('#randomString').applyVal(escape);

Categories

Resources