I know I can use $.data but how can I iterate trough all data- attributes and merge the values with the default plugin configuration?
(function($){
$.fn.plugin = function(opts){
opts = $.extend({
foo: 'abc',
boo: 45
}, opts);
return this.each(function(){
...
});
};
})(jQuery);
So if I use
$('.el').plugin();
it should look for data attributes on .el, like data-foo etc ...
In your each() loop, you can merge the object returned by data() with your defaults, then merge the opts argument with the result in a single call to $.extend():
$.fn.plugin = function(opts) {
return this.each(function() {
var thisOpts = $.extend({
foo: "abc",
boo: 45
}, $(this).data(), opts);
// Now use 'thisOpts' as operating parameters for this element...
});
};
This should achieve what you want: the opts argument has the highest priority, followed by the data- attributes of the current element, followed by the plugin defaults.
The .data() method supports data- attributes.
As of jQuery 1.4.3 HTML 5 data- attributes will be automatically
pulled in to jQuery's data object. The treatment of attributes with
embedded dashes was changed in jQuery 1.6 to conform to the W3C HTML5
specification.
Calling it without speciying a parameter will return an object with key/values pairs for all the data attributes:
var mydata = $("#mydiv").data();
You can get all the attributes for an element like this:
//get the element and setup an array for output
var el = document.getElementById("o"),
arr = [];
//loop through each attribute for the element
for (var i = 0, attrs = el.attributes, l=attrs.length; i < l; i++){
//if the current attribute starts with `data-` then add it to the array
if (attrs.item(i).nodeName.substr(0, 5) == 'data-') {
arr.push(attrs.item(i).nodeName);
}
}
Here is a demo: http://jsfiddle.net/ksbD3/1/
Also I got most of the above code from this answer: Get all Attributes from a HTML element with Javascript/jQuery
You can use data() method on the jQuery object which will give all the data attributes as key/value pair. Try this.
(function($){
$.fn.plugin = function(opts){
//this.data() will give { foo: 'abc', boo: 45 ... }
opts = $.extend(this.data(), opts);
return this.each(function(){
...
});
};
})(jQuery);
.data() reference: http://api.jquery.com/data/
Thank you for all the answers that pointing out using opts = $.extend(this.data(), opts);
One important fact need to be added here: is the transformation of HTML data-attribute.
data-coolName can be accessed like this this.data(coolname)
data-another-cool-name can be accessed like this this.data(anotherCoolName)
Details: Does jQuery internally convert HTML5 data attribute keys to lowercase?
Case sensitive of java and the transformation of the attribute name might be a pitfall if you don't know about.
Related
I have a list of html elements with data attributes, which I would like to assemble into a jQuery object and manipulate the values.
What is the best way to dynamically add these in an each loop so that I can easily access the data as so: data.name and data.name.prop?
I want all the naming conventions to be dynamic and based on the data.
I based my code on the top answer from here: How to create dynamically named JavaScript object properties?
So far I have:
$('.licences-list .data div').each(function(index) {
var data = {}
cats[$(this).find('p').data('cat')] = $(this).find('p').data('catname')
cats.push(data)
})
But when I try to iterate over the data array, like so:
$.each(cats, function(key, value){
$('<div class="card"><p>'+value+'</p></div>').appendTo('#commercial-licenses');
});
I just get [object Object] output... and I'm not sure why!
var data = {}
cats[$(this).find('p').data('cat')] = $(this).find('p').data('catname')
Each time you loop through, you're actually just adding an empty object (data) to your array (cats). You're then assigning a named property to that array (cats) which $.each has no idea about (it ignores them because it's iterating over an actual array).
My guess is you want an object map which is something like: var cats = { "f1": "feline 1", "f2": "feline " };
In that case what you want is:
var cats = {};
$('.licences-list .data div').each(function(index) {
cats[$(this).find('p').data('cat')] = $(this).find('p').data('catname')
})
If you want an array that contain more values than just strings (or whatever data you have added to the element), you create new objects each time and append them to the cats array:
var cats = [];
$('.licences-list .data div').each(function(index) {
cats.push({
'id': $(this).find('p').data('cat'),
'name': $(this).find('p').data('catname')
});
})
This will then give you an array that you can use $.each over, and access the values using: value.id, value.name
Don't over complicate it.
$('.div').attr('data-attribute', 'data-value');
using your example:
$('.licences-list .data div').attr('attribute-name', 'attribute-value');
I've gotten this to work but I would like to know if there is a straightforward way of accomplishing the update. I have a div where a data attribute has been assigned. When the div is clicked, I want one of the properties on the data attribute to be updated.
http://jsfiddle.net/quint/fq7dwsyv/
var originalObj = {
"x": 5,
"y": 58,
"selected": 'no'
};
var originalObjString = JSON.stringify(originalObj); // Convert object to JSON string
$('#test').attr('data-objData', originalObjString); // Attach data attribute to div
$('#test').on('click', function(event) {
var originalData = $(this).attr('data-objData'); // Store data attribute value
var originalDataJSON = JSON.parse(originalData); // Convert string to object
originalDataJSON['selected'] = 'yes'; // Update object property
var updatedData = JSON.stringify(originalDataJSON); // Convert object to string and store
$('#test').attr('data-objData', updatedData); // Update data attribute on div
});
I'd separate the update into its own function
$('[data-example]').click(function() {
function update(object) {
object.foo = 'bar';
return object;
}
var original = this.dataset.example;
original = original && JSON.parse(original) || {};
this.dataset.example = JSON.stringify(update(original));
});
You can store the original object with .data() and mutate it directly without having to store it again with the .data() method:
var originalObj = {
"x": 5,
"y": 58,
"selected": 'no'
};
$('#test').data('objData', originalObj);
$('#test').on('click', function(event) {
$(this).data('objData')['selected'] = 'yes';
console.log($(this).data('objData'));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="test">test me</button>
Remarks
The above solution assumes you are willing to change the way the object is stored with the element. If on the other hand the value must be stored on an element attribute, then there is not much that can be improved in your code. The steps you take are then all necessary (convert from/to JSON, read from attribute, write to attribute, ...etc).
Note however that writing to element attributes is not considered best practice. If anything, you should use .prop() in favour of .attr(): it will result in faster code. But you'll not see the change when consulting the element attributes. In other words, you cannot mix .prop() with .attr().
If there is no room for change in that either, then I would suggest that you ask this question on CodeReview.
I'm having a problem getting an array of information stored properly as JSON.
I made a fiddle to illustrate the problem. Enter a set of tags and take a look at the console to see the output.
More explanation:
So I have an input that takes in a comma-separated list of tags, which I then format.
function createTagArray() {
// given an input value of 'tag1, tag2, tag3'
// returns array = ['tag1', 'tag2', 'tag3']
}
I thought what I needed to do next was the following:
loop over the array and create a 'tag' object for each item which also includes an id for the tag and the id of the contact the tag is associated with.
Each object is pushed to tags, an observable array.
function single_tag(id, contactId, tagLabel) {
var self = this;
self.id = id;
self.contactId = contactId;
self.tagLabel = tagLabel;
}
function createTags() {
var array = createTagArray();
for (var i = 0; i < array.length; i++) {
self.tags().push(new single_tag(uuid.generate(), self.contactId, array[i]));
}
}
Then, I converted it into JSON
self.contactInformation = function() {
return ko.toJS({
"id": self.contactId,
"firstname": self.firstname(),
"lastname": self.lastname(),
... other fields ...
"tags": self.tags(),
})
}
But, when I inspect the console output of calling this function, tags is a collection of arrays, not a nice json object.
How do I get it formatted correctly?
I tried this suggestion, and the tag json is structured correctly, but it is stored with escaped quotes, so that seems wrong.
Thanks for all the help!
I would recommend you knockout.mapping plugin for KO, it allow map complicated JSON structure to view model, even without declarations.
From the documentation
Let’s say you have a JavaScript object that looks like this:
var data = {
name: 'Scot',
children: [
{ id : 1, name : 'Alicw' }
]
}
You can map this to a view model without any problems:
var viewModel = ko.mapping.fromJS(data);
Now, let’s say the data is updated to be without any typos:
var data = {
name: 'Scott',
children: [
{ id : 1, name : 'Alice' }
]
}
Two things have happened here: name was changed from Scot to Scott and children[0].name was changed from Alicw to the typo-free Alice. You can update viewModel based on this new data:
ko.mapping.fromJS(data, viewModel);
And name would have changed as expected. However, in the children array, the child (Alicw) would have been completely removed and a new one (Alice) added. This is not completely what you would have expected. Instead, you would have expected that only the name property of the child was updated from Alicw to Alice, not that the entire child was replaced!
...
To solve this, you can specify which key the mapping plugin should use to determine if an object is new or old. You would set it up like this:
var mapping = {
'children': {
key: function(data) {
return ko.utils.unwrapObservable(data.id);
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
In the jsfiddle you were using Knockout 3.0 which doesn't have support for textInput. This was added in 3.2. To use version 3.2 you need to use a cdn such as this: http://cdnjs.com/libraries/knockout
There was typeo in your binding. sumbit should be submit.
There was a problem with your constructor for single_tag. id was not used so I removed it:
function single_tag(contactId, tagLabel) {
var self = this;
self.contactId = contactId;
self.tagLabel = tagLabel;
}
Currently also contactId is not set because the observable has not been set to a value.
To convert to JSON you need to use ko.toJSON instead of ko.toJS:
self.contactInformation = function() {
return ko.toJSON({
"firstname": self.firstname(),
"tags": self.tags(),
})
}
Now when the console writes out an array appears:
{
"firstname":"test",
"tags":[
{"tagLabel":"test1"},
{"tagLabel":"test2"},
{"tagLabel":"test3"}
]
}
JsFiddle
So my problem was more basic than I was realizing. I'm using JSON Server to serve up my data, and I was pulling information from two parts of the database (contacts & tags).
When I tried to update my tags, I was trying to apply them to a property that didn't exist on the contact JSON in my database. Posting the tags separately worked though.
A javascript data object (JSON notation) has been created with the following content:
"[
{"range":"Shape","values":[{"idx":0,"val":"Random"},{"idx":1,"val":"Line"},{"idx":2,"val":"Square"},{"idx":3,"val":"Circle"},{"idx":4,"val":"Oval"},{"idx":5,"val":"Egg"}]},
{"range":"Color","values":[{"idx":0,"val":"Red"},{"idx":1,"val":"Blue"},{"idx":2,"val":"Yellow"},{"idx":3,"val":"Green"},{"idx":4,"val":"Cyan"}]}
]"
In a next step the index of an ordinal value has to be found in this object. The function should find the index of the value 'Blue' in the range 'Color'.
So the function should have the meta scripting form
f("Color")("Blue")=1
What is the most elegant form to create such a function in the context of D3 and javascript?
Depending on your use case, it might make sense to convert the data structure to a different structure more suitable for direct access. E.g. you could convert your structure to
var data = {
Shape: ['Random', 'Line', ...],
// ...
};
and access it with
data['Shape'].indexOf('Line') // or data.Shape.indexOf('Line')
Or go even one step further and convert to
var data = {
Shape: {
Random: 0,
Line: 1,
// ...
},
// ...
};
and access it with
data['Shape']['Line'] // or data.Shape.Line
What the best solution is depends on the actual use case.
Converting the structure dynamically is pretty straight forward. Here is an example to convert it to the first suggestion:
var newData = {};
data.forEach(function(item) {
newData[item.range] =
item.values.map(function(value) { return value.val; });
});
This would also reduce redundancy (e.g. idx seems to correspond with the element index).
Would this work for you ?
var dataJson = '[ \
{"range":"Shape","values":[{"idx":0,"val":"Random"},{"idx":1,"val":"Line"},{"idx":2,"val":"Square"},{"idx":3,"val":"Circle"},{"idx":4,"val":"Oval"},{"idx":5,"val":"Egg"}]},\
{"range":"Color","values":[{"idx":0,"val":"Red"},{"idx":1,"val":"Blue"},{"idx":2,"val":"Yellow"},{"idx":3,"val":"Green"},{"idx":4,"val":"Cyan"}]}\
]';
var data = JSON.parse(dataJson);
for (each in data){
if ( (data[each].range) === 'Color'){
for (eachVal in data[each].values){
if (data[each].values[eachVal].val === 'Blue'){
alert(data[each].values[eachVal].idx);
}
}
} ;
}
And here is the JSFiddle for you too.
I have object that all of its fields are observable (knockout.observable).
I have other regular object too, that its fields have the same names like the first-object-fields.
I want to insert into each field of the first object, the value of the match-field at the second object.
For example:
var first = {
a:ko.observable(),
b:ko.observable()
};
var second= {
a:'my'
b:'fields';
};
I want the first object to look like:
first = {
a:ko.observable('my'),
b:ko.observable('fields')
};
Yes, of course, I can do it by 'each' loop.
But, my question is:
Is there any build-in function that does it?
You can use the ko.mapping plugin:
var results = ko.mapping.fromJS(second, first);
See Documentation