Is there a possibility to use the javascript method 'getElementsByTagNameNS' by passing the namespace alias instead of the namespace uri?
I'm parsing multiple XML-Documents and search for multitple tags with different namespaces in javascript.
I created the following function related to following stackoverflow article:
// The node name is passed with namespace e.g. 'c:label'
function getElementsByTagNameCrossBrowser (xmlNode, tagName) {
var elementCollection, pureTagName, nameSpace;
elementCollection = xmlNode.getElementsByTagName(tagName);
if (elementCollection.length === 0) {
nameSpace = tagName.split(':')[0];
pureTagName= tagName.split(':')[1];
elementCollection = xmlNode.getElementsByTagNameNS(nameSpace, pureTagName);
}
return elementCollection;
}
In FF and IE this functions provides the correct results, but in Chrome the 'getElementsByTagName' method returns no result when passing a tagName with namespace included.
If I only search for the 'pureTagName' without considering the 'nameSpace' I get too much results.
As I saw in the MDN-Article the 'getElementsByTagNameNS' method requires the namespace URI, but at the moment I can't access the URI. Is there a workaround or something else to use this method by passing the namespace alias?
Edit:
This is my current Workaraound:
function getElementsByTagNameChromeNamespace (xmlNode, tagName) {
var elementCollection, pureTagName, i, tmpNode, tmpNodeName, resultingCollection;
// If there is no namespace return normal collection.
if (tagName.indexOf(':') === -1) {
return xmlNode.getElementsByTagName(tagName);
}
resultingCollection = [];
pureTagName = tagName.split(':')[1];
elementCollection = xmlNode.getElementsByTagName(pureTagName);
for (i = 0; i < elementCollection.length; i++) {
tmpNode = elementCollection[i];
tmpNodeName = tmpNode.nodeName;
if (tmpNodeName === tagName) {
resultingCollection.push(tmpNode);
}
}
return resultingCollection;
},
function getElementsByTagNameCrossBrowser (xmlNode, tagName) {
var elementCollection;
elementCollection = xmlNode.getElementsByTagName(tagName);
if (elementCollection.length === 0) {
elementCollection = this.getElementsByTagNameChromeNamespace(xmlNode, tagName);
}
return elementCollection;
}
Related
I have got the below (Simplified for this issue) plugin, the node value which is the value of options.container it an HTMLObject however when I do typeof on it Object is returned.
This is an issue as when i try to override this using dataset values, I detect it's type and replace it accordingly.
// Define option defaults
constructor(node) {
const _ = this;
var options = {
container: node,
};
(function() {
for( const data in node.dataset ) {
var key = (data.replace("map", "")).charAt(0).toLowerCase() + (data.replace("map", "")).substr(1),
value = node.dataset[data];
// Check the value has been set
if (value !== '') {
switch(typeof options[key]) {
case 'object':
options[key] = JSON.parse(value);
break;
case 'HTMLElement':
options[key] = value.GetElement();
break;
default:
options[key] = value;
break;
}
}
}
console.log(options);
}.call(_));
// Initialize the map
// this.init();
};
static init(node) {
return new this(node);
}
static initAll(node) {
var _ = this,
instances = [];
window.addEventListener('load', function () {
var maps = document.querySelectorAll('[data-map]');
var current = [];
var InitializeMap = function (e) {
instances.push(_.init(e)); // Setup new instance of this class for each map
};
// Keep a reference for each map
for (var i = 0; i < maps.length; i++) {
var dataset = maps[i].dataset;
if (current.indexOf(dataset.googleMap) === -1) {
current.push(dataset.googleMap);
InitializeMap(maps[i]);
}
}
});
return instances;
}
As you can see in the code below, I get an attribute called data.map.container, which contains the value of #map_container. So during that switch function I need to detect that the node value is an HTMLObject not an object.
For the record is I do console.log(options.container) then the HTMLElement is displayed in the log.
P.S. GetElement() simply takes the string and returns the HTMLObject, so #element would do getElementById and .element would do getElementByClassName. Just my version of jQuery's $().
typeof can not tell such specific object types, see possible return values: https://developer.mozilla.org/hu/docs/Web/JavaScript/Reference/Operators/typeof
You can use instanceof to check for whatever class you need.
I would like to extend the HTMLElement interface with a custom object on a custom property. Is that even possible?
This is what I've got:
if (typeof HTMLElement.prototype.classList === "undefined") {
HTMLElement.prototype.classList = function ClassList() { return this.className.split(' '); };
HTMLElement.classList.prototype.add = function(name) {
if (classList.indexOf(name) !== -1) {
return;
}
classList[classList.length] = name;
this.className = classList.join(' ');
};
HTMLElement.classList.prototype.remove = function(name) {
var index = classList.indexOf(name);
if (index !== -1) {
this.className = classList.splice(index + 1, 1).join(' ');
}
}
}
This should give you a good idea about what I need.
I want to implement my own classList functionality for IE9.
In IE, this would result in undefined and would throw an error.
element.classList.add('fadeIn');
Is there a simple way to implement this?
EDIT
I've been toying around with this for a while but my knowledge isn't good enough yet to understand exactly what's going on. I still have to call document.getElementById('whatever').classList() to avoid getting "undefined". I would love to be able to call classList without braces (if the browser doesn't support classList, of course)
To define a getter (a function that can be invoked without parentheses), use Object.defineProperty. Works in IE9.
function getClassList()
{
var element = this;
var classList = this.className.split(' ');
classList.add = function(name) {
if (classList.indexOf(name) !== -1) {
return;
}
classList.push(name);
element.className = classList.join(' ');
};
classList.remove = function(name) {
var index = classList.indexOf(name);
if (index !== -1) {
classList.splice(index, 1);
element.className = classList.join(' ');
}
};
return classList;
}
Object.defineProperty(HTMLElement.prototype, 'classList', { get: getClassList });
I think you've setup the prototype wrong.
You should be assigning classList to HTMLElement.prototype, not directly on HTMLElement itself.
To access it like it works natively, you could set it up like so...
HTMLElement.prototype.classList = function()
// ...
};
HTMLElement.prototype.classList.add = function()
// ...
};
HTMLElement.prototype.classList.remove = function()
// ...
};
Explanation:
As a personal project, I'm trying to create my own lightweight version of Dependency Injection for JavaScript - Some would probably disagree with calling this DI because it has no interfaces, but I arrived at the conclusion that interfaces were overkill in JS since we can so easily type check. I have looked at the source of Angular, but I just feel like the complexity there may be overkill for my projects, and I'm interested in attempting my own for a learning experience anyway.
Question:
My question is, fundamentally, is the syntax I'm trying to implement impossible or not?
I'll explain my goal for the syntax, then provide the error and code snippet, and below that I'll post the full code.
Goal for Syntax:
I'd like the creation of a component, and injection of dependencies to work like this, where everything is a component, and anything can be a dependency. I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ...snip - see full code for the process component - snip ... */
JHTML.addComponent('/generate:init', function (jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
The inject function just takes an array of component paths in the order the component's arguments are expected. null can be used to skip, allowing direct argument input instead, as shown above.
I also have something I call hooks, which are components stored in a certain place, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods, clean and easy, and can produce the final product as a library without the wiring, no need for my DI framework as a dependency. Hopefully that makes sense.
Error:
Right now, running the code (which is a very simple library to generate HTML by parsing a JSON structure) I get the error that process is undefined in the line var html = process(jsonInput);. I was having trouble understanding whether this is a fundamental design problem, or just a bug. Maybe this syntax is not possible, I'm hoping you can tell me.
Code:
Here's the code, and a link to the JS Bin.
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string1.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
//console.log('yep1');
//console.log(scope.components[scopeName]);
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string2.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var args = [];
for (var i = 0; i < dependencyArray.length; i++) {
if (dependencyArray[i] !== null) {
that.returnComponent(dependencyArray[i], function(dependency) {
args.push(dependency);
});
}
}
console.log(that.focus);
that.focus.apply(null, args);
return that;
}
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:process', function(children) {
var html = [];
var loop = function() {
for (var i = 0; i < children.length; i++) {
if (children[i].tag) {
html.push('<' + tag + '>');
if (children[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject();
JHTML.addComponent('/generate:init', function(jsonInput, process) {
console.log(process);
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
JHTML.addHook('generate', function(jsonInput, init) {
var html = init(jsonInput);
return html;
}).inject([null, '/generate:init']);
handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
var htmlChunk = [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}];
console.log(handle.generate(htmlChunk));
is the syntax I'm trying to implement impossible or not?
It's absolutely possible, and I'm sure with a bit of bugfixing it'd work just fine.
What you're describing is essentially the same as Asynchronous Module Definition (AMD) which is used extensively for handling code dependencies.
Rather than continuing to pursue your own version of the same concept, I recommend that you give requirejs a try and follow the existing standards with your projects.
is there a way to define global namespace, so that i can call function from this namespace from all my page?
e.g
// in one file i define below code
DefineNameSpace("my.namespace.api", {
addObject: function(obj) {
// store obj into indexDB
},
readAllObject: function() {
// return array of object from indexdb
}
})
// so that in another javascript file i can do
my.namespace.api.addObject({name: "foo", desc: "bar"});
is there a way to implement "DefineNameSpace" method?
Thanks
one way to do it, which is very simple, is this:
my = {
namespace: {
api : {}
}
}
my.namespace.api.addObject = function (obj) { }
you're actually creating objects but in this way it will function as a namespace just as well :)
hm it's not the method you're implementing. But building a namespace with a method would require the function to be called before the script files are loaded where the namespace is used like that, otherwise those lines of code are called before the DefineNamespace method is called and you will run into parts of namespaces that are undefined at that point. With above solution that won't be the case, although it is not dynamic unfortunately.
building a namespace dynamically can be done in the following way:
// the root of the namespace would still be handy to have declared here
var my = {};
function defineNamespace(namespaceStr) {
var namespaceSegments = namespaceStr.split(".");
var namespaceSoFar = null;
// iterate through namespace parts
for (var i = 0; i < namespaceSegments.length; i++) {
var segment = namespaceSegments[i];
if (i == 0) {
// if namespace starts with my, use that
if (segment == "my") {
// set pointer to my
namespaceSoFar = my;
}
else {
// create new root namespace (not tested this, but think this should work)
var otherNamespace = eval(segment);
if (typeof otherNamespace == "undefined") {
eval(segment + " = {};");
}
// set pointer to created root namespace
namespaceSoFar = eval(segment);
}
}
else {
// further build the namespace
if (typeof namespaceSoFar[segment] == "undefined") {
namespaceSoFar[segment] = {};
}
// update the pointer (my -> my.namespace) for use in the next iteration
namespaceSoFar = namespaceSoFar[segment];
}
}
}
I was wondering if it was possible to assign values to an element object. In this case, I wish to assign the returns from the setTimeout() function to an object within an element object.
For example:
var elem = document.getElementById('target');
elem.timeouts = new Object();
elem.timeouts.sometimeout = setTimeout('...', 1000);
So I could then do:
clearTimeout(elem.timeouts.sometimeout);
I know this might seem bad practice etc, but is it possible or will it cause browsers to catch fire and turn on their user etc.
Thanks.
DOM elements are Host objects (aka non-native) and as such they can do almost anything they want. It's not guaranteed that your expandos will work. In particular IE used to have problems with them. It's highly recommended to read this article:
What’s wrong with extending the DOM by #kangax
(it is from one of the Prototype.js developers who experienced the drawbacks of such bad habits. They've rewritten the whole library just to save themselfs from more headaches)
Now if you add uniqueID to elements in non-IE browsers (IE has it by default) and then your data function becomes a simple lookup ~ O(1). The real information will be stored in a closure.
It's 2-4x faster than jQuery.data (test)
data(elem, "key", "value");
1.) Hash table
var data = (function () {
var storage = {};
var counter = 1;
return function (el, key, value) {
var uid = el.uniqueID || (el.uniqueID = counter++);
if (typeof value != "undefined") {
storage[uid] || (storage[uid] = {});
storage[uid][key] = value; // set
} else if (storage[uid]) {
return storage[uid][key]; // get
}
};
})();
2.) Array
If you want to avoid expandos all together you can use an array to hold elements (but it's slower)
var data = (function () {
var elements = [];
var storage = [];
return function (el, key, value) {
var i = elements.indexOf(el);
if (typeof value != "undefined") {
if (i == -1) {
i = elements.length;
elements[i] = el;
storage[i] = {};
}
storage[i][key] = value; // set
} else if (storage[i]) {
return storage[i][key]; // get
}
};
})();
Array.prototype.indexOf (fallback)
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (item) {
var len = this.length >>> 0;
for (var i = 0; i < len; i++) {
if (this[i] === item) {
return i;
}
}
return -1;
};
}
You're welcome! :)
It's possible, DOM elements retrieved by JS, are JS variables :) ..BTW it's not a common practice do that stuff in that way. I think the answer of #galambalazs is more deep and complete ;)
if you are using jquery, you can story it in the "data"
http://api.jquery.com/jQuery.data/
No, actually that should work just fine. From what I understand, adding that return to the object should be fine. It's better than storing it in a global container IMO.