If I have two HTML elements that should always contain the same inner HTML text, what is the most elegant way to update them both at the same time?
For example:
<head>
<title id="pageTitle">My Application</title>
</head>
<body>
<h1 id="screenTitle">My Application</h1>
</body>
I would like the innerHTML of screenTitle and pageTitle to remain identical whenever a JavaScript function changes one of them.
I know I can iterate through the pageTitle and screenTitle elements, updating each of them whenever I change the string "My Application", but is there a more elegant way?
document.title.innerHTML is read-only in IE. This supposed to be a cross-browser method:
document.getElementById('screenTitle').innerHTML = document.title = 'My Application';
Instead of a literal value you can use a variable ofcourse.
If you really need something "elegant" (=== exotic), you can use a setter:
var page = {
set title(text) {
document.getElementById('screenTitle').innerHTML = document.title = text;
}
};
When ever you need to change the titles, just set it: page.title = newTitle;. Only you need to care of, is that you can refer page object from the current scope. Also this will only work in modern browsers.
Use a single function that encapsulates writes to both elements. So instead of directly using DOM methods, the rest of your code would use a function such as:
function setTitle(newTitle)
{
document.getElementById('pageTitle').innerHTML = newTitle;
document.getElementById('screenTitle').innerHTML = newTitle;
}
Of course this could be optimized/DRYed/refactored/overengineered ad nauseam...
function setTitle(newTitle)
{
if (setTitle.elements)
{
var id = document.getElementByIdl
setTitle.elements = [id('pageTitle'), id('screenTitle')];
}
setTitle.elements.forEach(function (elt)
{
elt.innerHTML = newTitle;
});
}
You should make a JS method that updates both simultaneously as long as you are in control of all the code that is going to modify the elements. Something like:
function updateElementGroup( newInnerHTML ) {
document.getElementById( 'pageTitle' ).innerHTML = newInnerHTML;
document.getElementById( 'screenTitle' ).innerHTML = newInnerHTM;
}
Which you can use like this:
updateElementGroup( 'New Text' );
If you are not in control of all the code you could set up some listeners to catch whenever one is changed and update the other one accordingly, something like this:
var isUpdating = false;
var elements = [
document.getElementById( 'pageTitle' ),
document.getElementById( 'screenTitle' ),
];
for ( i in elements ) {
elements[i].addEventListener( 'DOMCharacterDataModified', function( evt ) {
if ( isUpdating ) {
return;
}
isUpdating = true;
for ( i in elements ) {
elements[i].innerHTML = evt.newValue;
}
isUpdating = false;
} );
}
Using jQuery:
$('#pageTitle').bind('DOMSubtreeModified', function() {
if ($('#pageTitle').html() != $('#screenTitle').html())
$('#screenTitle').html($('#pageTitle').html());
});
$('#screenTitle').bind('DOMSubtreeModified', function() {
if ($('#pageTitle').html() != $('#screenTitle').html())
$('#pageTitle').html($('#screenTitle').html());
});
Related
I need to choose the right design pattern. My page have 2 objects created from same constructor. The problem that i have is detecting which object is getting a click event.
I am doing this after object creation:
$("table tr, table button,.dataTables_paginate a").click(function (e) {
**myTableName.buttonPressed($(this));**
});
and
buttonPressed: function (el) {
if (el.is('a') && el.closest('li').hasClass('paginate_button')) {
var objName = el.attr('aria-controls');
debugger;
}
else {
var objName = $(el).closest('table').attr('id');
}
**this.getName(objName);**
tbl = myTableName.tbl;
editor = myTableName.editor;
//myTableName.acl(myTableName.currentForm);
},
and then
getName: function (objName) {
// search through the global object for a name that resolves to this object
for (var name in window)
if (window[name] == this) {
if (objName) {
myTableName = window[objName];
//window.myState[this.myInstanceName] = jQuery.extend({}, this);
break;
} else {
window[name] = this;
window[window[name]] = window[name];
myTableName = window[window[name]];
// window.myState[this.myInstanceName] = jQuery.extend({}, this);
}
break;
}
},
Besides these are all globals , i dont fell this is the correct way to do that.
Thoughts?
It seems you should use custom attributes. Custom attributes allow you to store additional relevant data to any DOM element. One way this could be used would be to associate the DOM with some JS bytecode. This is especially useful when you have to work backwards, i.e. finding the relevant object(s) to the HTML element. The standard says that these attributes must start with data- (although technically you could use anything not used by the browser, but this is not advised). Here's an example:
HTML:
<table data-instance-name="name1">
...
<button>Action</button>
<a>Another action</a>
</table>
<table data-instance-name="name2">
...
<button>Action</button>
<a>Another action</a>
</table>
JS:
$( 'table button, table a' ).on('click', function() {
var name = $(this).closest('table').attr('data-instance-name'),
table = tables[name];
});
I'm using the Microsoft Translation Widget, which I'd like to use to automatically translate a webpage without user interaction.
The problem is, I can't get rid of the widget that keeps popping up or hide it on document.ready because the CSS and JS get loaded from Microsoft's own script in the widget!
Does anyone know a way around this? I've looked everywhere and cannot find a solutuion for this.
Whoa, after some time playing around with that, I've finally achieved what you want.
It's kindda ugly, because of some needed workarounds, but it works, take a look at the fiddle.
The steps were:
Firstly, we must override the default addEventListener behavior:
var addEvent = EventTarget.prototype.addEventListener;
var events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
addEvent.apply(this, [].slice.call(arguments));
events.push({
element: this,
type: type,
listener: listener
});
}
Then, we create a helper function removeEvents. It removes all the event listeners of an element.
var removeEvents = function(el, type) {
var elEvents = events.filter(function(ev) {
return ev.element === el && (type ? ev.type === type : true);
});
for (var i = 0; i < elEvents.length; i++) {
el.removeEventListener(elEvents[i].type, elEvents[i].listener);
}
}
When creating the script tag, in the way Microsoft says:
var s = d.createElement('script');
s.type = 'text/javascript';
s.charset = 'UTF-8';
s.src = ((location && location.href && location.href.indexOf('https') == 0) ? 'https://ssl.microsofttranslator.com' : 'http://www.microsofttranslator.com') + '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=True&ui=true&settings=Manual&from=';
var p = d.getElementsByTagName('head')[0] || d.dElement;
p.insertBefore(s, p.firstChild);
We must add a load event listener to that script, and the code below is fully commented:
s.addEventListener('load', function() {
// when someone changes the translation, the plugin calls the method TranslateArray
// then, we save the original method in a variable, and we override it
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
// we call the original method
translate.apply(this, [].slice.call(arguments));
// since the translation is not immediately available
// and we don't have control when it will be
// I've created a helper function to wait for it
waitForTranslation(function() {
// as soon as it is available
// we get all the elements with an attribute lang
[].forEach.call(d.querySelectorAll('[lang]'), function(item, i) {
// and we remove all the mouseover event listeners of them
removeEvents(item, 'mouseover');
});
});
}
// this is the helper function which waits for the translation
function waitForTranslation(cb) {
// since we don't have control over the translation callback
// the workaround was to see if the Translating label is visible
// we keep calling the function, until it's hidden again
// and then we call our callback
var visible = d.getElementById('FloaterProgressBar').style.visibility;
if (visible === 'visible') {
setTimeout(function() {
waitForTranslation(cb);
}, 0);
return;
}
cb();
}
});
Update 1
After re-reading your question, it seems you want to hide all the widgets at all.
So, you must add the following code as soon as the translation is got:
waitForTranslation(function() {
document.getElementById('MicrosoftTranslatorWidget').style.display = 'none';
document.getElementById('WidgetLauncher').style.display = 'none';
document.getElementById('LauncherTranslatePhrase').style.display = 'none';
document.getElementById('TranslateSpan').style.display = 'none';
document.getElementById('LauncherLogo').style.display = 'none';
document.getElementById('WidgetFloaterPanels').style.display = 'none';
// rest of the code
});
I've created another fiddle for you, showing that new behavior.
Update 2
You can prevent the widget showing at all by adding the following CSS code:
#MicrosoftTranslatorWidget, #WidgetLauncher, #LauncherTranslatePhrase, #TranslateSpan, #LauncherLogo, #WidgetFloaterPanels {
opacity: 0!important;
}
And you can even prevent the before-translated text being showed, by hiding the document.body by default, and then showing it when the page is fully translated:
(function(w, d) {
document.body.style.display = 'none';
/* (...) */
s.addEventListener('load', function() {
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
translate.apply(this, [].slice.call(arguments));
waitForTranslation(function() {
/* (...) */
document.body.style.display = 'block';
});
}
});
});
Take a look at the final fiddle I've created.
For me, this was the solution:
on your < style > section add this class
.LTRStyle { display: none !important }
Also, if you are invoking the translation widget this way:
Microsoft.Translator.Widget.Translate('en', lang, null, null, TranslationDone, null, 3000);
then add this to your callback (in this example is TranslationDone) function:
function TranslationDone() {
Microsoft.Translator.Widget.domTranslator.showHighlight = false;
Microsoft.Translator.Widget.domTranslator.showTooltips = false;
document.getElementById('WidgetFloaterPanels').style.display = 'none';
};
This is a section of my view in which I want the result to show.
Can any one solve this problem?
When this.waitlist works, it should return the container like div#sntq-waitlist but it gives object[ ] instead.
Can anyone tell me why this is?
JavaScript
initLiveWaitList: function() {
this.waitlist_ = $('sntq-waitlist');
this.daily_status_ = $('sntq-daily_status');
this.waiting_ = $('sntq-waiting');
this.seated_ = $('sntq-seated');
this.oneFour_ = $('sntq-one-four');
this.fiveSix_ = $('sntq-five-six');
this.seven_ = $('sntq-seven');
this.running_ = true;
this.loadWaitList_();
this.intervalId_ = this.loadWaitList_.periodical(1200000, this);
window.addEventListener('focus', function() {
if (!this.running_) {
this.loadWaitList_();
this.intervalId_ = this.loadWaitList_.periodical(1200000, this);
}
}.bind(this));
window.addEventListener('blur', function() {
clearInterval(this.intervalId_);
this.running_ = false;
} .bind(this));
},
View
<div id="sntq-daily_status">
<div class="loading"></div>
</div>
I don´t think I completely understood your question, but to me it looks like you are using jQuery to get the containers at the top of your code.
If this is the case you are probably just missing the ID selector.
Try to change the code to
this.waitlist_ = $('#sntq-waitlist');
this.daily_status_ = $('#sntq-daily_status');
//[...]
(note the "#" selector if you want to look for elements by ID, which seems to be the case in your example).
I´m building a jQuery extension plugin with the following standard:
(function ($) {
var version = "1.1.0";
var active = false;
$.fn.inputPicker = function (options) {
return this.each(function () {
if ($(this)[0].tagName !== 'DIV')
throw new ReferenceError('mz.ui.dialog.dateTimePicker: Method works only on DIV types.');
/// Label
var labelObj = $("<label class='small'>Data Hora Inicial</label>");
$(this).append(labelObj);
/// Input
var inputObj = $("<input type='datetime-local' class='form-control input-sm'></input>");
$(this).append(inputObj);
})
});
};
}(jQuery));
And here is how I call it:
<div id='test'></div>
$('#test').inputPicker();
Later in code I wanna get the data that was entered in the input field, something like:
$('test').inputPicker().getInputData();
What´s the best way to accomplish that ? I´ve tried something like:
this.getInputData = function () {
return $(inputObj).val();
}
But got errors when calling the function.
Can someone help me with this ? Thanks in advance...
You could just make another method to get the input data like this using the DOM structure and class names that you added:
$.fn.getInputData = function() {
return this.eq(0).find("input.input-sm").val();
}
This would operate only on the first DOM element in the jQuery object (since it's returning only a single value).
So, after setting it up like you did:
$("#test").inputPicker();
You'd then retrieve the data like this:
var data = $("#test").getInputData();
I'm using only javascript, not jQuery or no other javascript frameworks.
I have created one anchor tag like:
var _a = document.createElement('a');
now I want to add onclick for this tag. I have tried following:
_a.onclick = function(){ mycode(id); }
The function applies on that but the anchor tags are create in loop... so mycode(id) is always taking the last value of the loop.
Can any one help me out in this ?
try following:
_a.onclick = (function(id){return function(){ mycode(id); }})(id);
Probably you need a function to create your handler like this:
function createHandler( id ) {
return function(){ mycode( id ); };
}
and then assign inside the loop
for ( i= ... ) {
_a.onclick = createHandler( i );
}
On the other hand you maybe should use addEventListener() (MDN docu) to add events to elements:
_a.addEventListener( 'click', createHandler( i ) );
for(var id = 0; id < 10; id++){
var _a = document.createElement('a');
_a.onclick = (function(id){
return function (){
mycode(id);
}
})(id);
}
That should fix it.