Dojo: Dijit loses selection after cloning their <div> containers - javascript

As a Dojo newbie, I am trying to create a simple wizard of pages using a plain JS array as a stack that I push and pop the cloned main content DOM node onto and off (without using the StackContainer)
The problem is that when I go back to a previous page and 'repopulate' the page with the cloned main content node popped off the stack, the controls (in this example code, the radio buttons, but I have similar problem with selecting rows in a DataGrid dijit that I'm not showing but is more of importance for my app) appears to be the same as I previously saw (e.g. the radio button is the same one as I previously selected). But
I can't seem to interact with them, e.g. couldn't select the other radio button at all
Neither control is, in fact, selected, despite of the page appearance.
If I change these radio buttons from dijits to plain html input controls, there is no such problem.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<link rel="stylesheet" type="text/css"
href="../dojo/dijit/themes/dijit.css">
<link rel="stylesheet" type="text/css"
href="../dojo/dijit/themes/soria/soria.css">
<title>TestSimplerStillS1</title>
<script>dojoConfig = {parseOnLoad: true}</script>
<script src='../dojo/dojo/dojo.js'></script>
<script>
// The stack used for the wizard
var mainContentStack=[]
dojo.ready(function(){
// Reset the stack
mainContentStack=[];
// Add the radio btns
var radioData11 = {value: "Radio1.1", label: "Radio1.1_lbl"};
var radioData12 = {value: "Radio1.2", label: "Radio1.2_lbl"};
populateNextPageRadioBtns("radios1", [radioData11, radioData12]);
// disable the back btn for the first page
dojo.byId("backBtn").disabled=true;
});
function onNextClicked() {
// Push the main content onto a stack to store
pushCloneOnStack();
// Find the selected radio btn and display it
displaySelectedRadio();
// Clear the existing radio btns
destroyAll("rdbtns_div_id");
// disable the next btn for the last page
dojo.byId("nextBtn").disabled=true;
}
function displaySelectedRadio() {
var rdDivs = dojo.byId("rdbtns_div_id").children;
for (var i = 0; i < rdDivs.length; i++) {
rdDiv = rdDivs[i];
if (rdDiv !== null) {
var rd = rdDiv.children[0].children[0];
if (rd.checked) {
dojo.byId("rdbtns_sel_div_id").innerHTML=rd.value;
}
}
}
}
function onBackClicked() {
popAndAssignFromStack();
}
function destroyAll(nodeId){
var wContainers = dojo.byId(nodeId);
dojo.forEach(dijit.findWidgets(wContainers), function(w) {
w.destroyRecursive(false);
});
dojo.byId(nodeId).innerHTML = "";
}
function populateNextPageRadioBtns(grpIdentifier, radioBtnDataArray){
// Create new radio btns
require(['dojo/_base/array'], function(array){
var i = 0;
array.forEach(radioBtnDataArray, function(radioBtnData){
// Create the radio btns and default the 1st radio btn to be checked
createRadioBtn(grpIdentifier, radioBtnData, (i==0));
i++;
});
});
}
function createRadioBtn(nextPageSqlStr, radBtnData, isChecked){
var radGrpName = "rd_" + nextPageSqlStr + "_grp_name";
var radVal = radBtnData.value;
var radLbl = radBtnData.label;
// Only create radio btn contrl set if there is a 'val' prop for the radio button
if(radVal){
var radId = "rd_" + radVal + "_id";
// Create new container DIV
var rdDiv = dojo.create("div", {
id : "rd_" + radVal + "_div_id"
});
// Create the radio btn and put it into the new DIV
dojo.require("dijit.form.RadioButton");
var radioB = new dijit.form.RadioButton({
id: radId,
checked: isChecked,
value: radVal,
name: radGrpName
});
dojo.place(radioB.domNode, rdDiv, "last");
// Create the label and put it into the new DIV
if(radLbl){
var radioBlbl = dojo.create("label", {
"for": radId,
innerHTML: radLbl
});
dojo.place(radioBlbl, rdDiv, "last");
}
// Put the new DIV into the static radio btns containing DIV
dojo.place(rdDiv, dojo.byId("rdbtns_div_id"), "last");
}
}
function pushCloneOnStack() {
/// Push cloned node onto the stack
var contentDomPush = dojo.byId("mycontentdiv");
var cloned = dojo.clone(contentDomPush);
dojo.attr(cloned, "id", "mycontentdivCloned");
mainContentStack.push(cloned);
// Every push means there is a page to go back to, so enable back btn
dojo.byId("backBtn").disabled = false;
}
function popAndAssignFromStack() {
if (mainContentStack && mainContentStack.length > 0) {
// Overwrite the main content with the version popped from the stack
var contentDomPop = mainContentStack.pop();
// Clear the div
destroyAll("mycontentdiv");
dojo.attr(contentDomPop, "id", "mycontentdiv");
dojo.place(contentDomPop, dojo.byId("mycontentcontainerdiv"), "only");
// Every pop means there is a page to go forward to, so enable next btn
dojo.byId("nextBtn").disabled = false;
}
if (mainContentStack.length == 0) {
// Nothing more to pop means there is no page to go back to, so disable the back btn
dojo.byId("backBtn").disabled = true;
}
}
</script>
</head>
<body class="soria">
<div id="mycontentcontainerdiv">
<div id="mycontentdiv">
Radios Btns:
<div id="rdbtns_div_id"></div>
<br>
Radio Selected:
<div id="rdbtns_sel_div_id"></div>
</div>
</div>
<br>
<div id="btnsDiv">
<button data-dojo-type="dijit/form/Button" type="button" id="backBtn">
Back
<script type="dojo/on" data-dojo-event="click">
onBackClicked();
</script>
</button>
<button data-dojo-type="dijit/form/Button" type="button" id="nextBtn">
Next
<script type="dojo/on" data-dojo-event="click">
onNextClicked();
</script>
</button>
</div>
</body>
</html>

The problem is that you are in an odd place, you cloned the widgets DOM, but the reference to the widget in the registry is removed in your destroy(if you do a dijit.registry.toArray() you will see the radio buttons are not there). So you have the DOM aspect of the widget but none of its backend support.
What you can do is keep a copy of the DOM before the parser has parsed it. You would have to create the radiobuttons as html first, clone then run the parse command. After you do the destroy you can re-add the nonparsed html back, then run the parser again.
Here is somebody that had the same problem:
http://www.developer-corner.com/2011/06/cloning-dijit-widgets.html

Related

Toggle hide/show not working on childs div

I have a script that gets data from a Google Sheet and displays it as a webpage - using JS and Tabletop.js.
There are multiple entries in the Sheet thus multiple entries in the webpage. To organise the Data I have a hide/show button. When the button is clicked on the first entry it works. However when the any of the other buttons are clicked it hides or shows the first entries data, not its own!
How do I hide/show each individual entries data? Below is the code I am working with!
I am new to JavaScript - Thanks in advance!
P.S - I struggled writing the Title to the questions!
<link href="../common/cats-copy.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<style>
#add-info {
display: none
}
</style>
<body>
<div class="container">
<h1>Resturants</h1>
<div id="content"></div>
<script id="cat-template" type="text/x-handlebars-template">
<div class="entry">
<h5>{{establishment_name}}</h5>
<h6>Area: {{area}}</h6>
<h6>Cuisine: {{cuisine}}</h6>
<button id="btn" class="button-primary" onclick="myFunction()">Hide</button>
<div id="add-info">
<h6>Address: {{address}}</h6>
<h6>Google Maps: {{google_maps_location}}</h6>
<h6>Opening Times: {{opening_times}}</h6>
<h6>Rating: {{rating}}</h6>
<h6>Added By: {{added_by}}</h6>
<h6>Date Added: {{date_added}}</h6>
</div>
</div>
</script>
</div>
<!-- Don't need jQuery for Tabletop, but using it for this example -->
<script type="text/javascript" src="handlebars.js"></script>
<script type="text/javascript" src="../../src/tabletop.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var public_spreadsheet_url = 'https://docs.google.com/spreadsheets/d/1h5zYzEcBIA5zUDc9j4BTs8AcJj-21-ykzq6238CnkWc/edit?usp=sharing';
$(document).ready( function() {
Tabletop.init( { key: public_spreadsheet_url,
callback: showInfo,
parseNumbers: true } );
});
function showInfo(data, tabletop) {
var source = $("#cat-template").html();
var template = Handlebars.compile(source);
$.each( tabletop.sheets("food").all(), function(i, food) {
var html = template(food);
$("#content").append(html);
});
}
</script>
<script>
function myFunction() {
var x = document.getElementById("add-info");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
</script>
</body>
</html>
Are all the entries on your page filled from the given template, meaning they are divs with the class entry? If so, I think your issue is the following: Your entry div has a child div with the id="add-info". And when you click the button, your handler function (myFunction()) tries to get a reference to that div via document.getElementById("add-info"); Now, if you have multiple such entries on a page, you will have multiple divs with id="add-info". But the id attribute of an element must be unique in your whole document. See the description of id or that of getElementById().
So the root cause of your problem is that the same id is used multiple times in the document when it shouldn't be. You get the behavior you're seeing because getElementById() just happens to be returning a reference to the first element it finds on the page, regardless of which button you click. But I believe you're in undefined behavior territory at that point.
One way to solve the problem is to somehow give myFunction() information about which button was clicked, while making each div you'd like to manipulate unique so they can be found easier. For instance, you can use the order of the restaurant on your page as its "index", and use that as the id of the div you'd like to hide/show. And you can also pass this index as an argument when you call your click handler:
...
<button id="btn" class="button-primary" onclick="myFunction('{{index}}')">Hide</button>
<div id="{{index}}">
<!-- The rest of the code here... -->
...
... add the index into your template context, so Handlebars can fill in the {{index}} placeholder:
...
$.each( tabletop.sheets("food").all(), function(i, food) {
food.index = i // Give your context its 'index'
var html = template(food);
$("#content").append(html);
});
...
... and then alter your function slightly to use the given argument instead of always looking for the div with id="add-info":
function myFunction(indexToToggle) {
var x = document.getElementById(indexToToggle);
// rest of the code is same
With this approach, I expect your DOM to end up with divs that have ids that are just numbers ("3", "4", etc.) and your click handler should get called with those as arguments as well.
Also note that your <button> element has id="btn". If you repeat that template on your page, you will have multiple <button>s with the same id. If you start trying to get references to your buttons via id you will have similar issues with them too since the ids won't be unique.

Click - Add text - Active textbox

I would like to add text to the active textbox when a button is clicked.
I have read many threads explaining how it is done when one is wishing to add to a specific textbox but nothing on simply adding text to whichever text field is active...
Any help would be appreciated. Thanks.
The below is a solution for a virtual keyboard.
Pure JS + HTML:
function bind() {
var keyArr = document.getElementsByClassName('key');
for(var i = 0; i < keyArr.length; i++) {
keyArr[i].addEventListener('click', function() {
document.getElementById('textinput').value += this.innerHTML;
});
}
var capsLock = document.getElementById('capslock');
capsLock.addEventListener('click', function() {
for(var i = 0; i < keyArr.length; i++) {
if(capsLock.capsactive) {
keyArr[i].innerHTML = keyArr[i].innerHTML.toLowerCase();
} else {
keyArr[i].innerHTML = keyArr[i].innerHTML.toUpperCase();
}
}
capsLock.capsactive = !capsLock.capsactive;
});
}
<body onload='bind()'>
<input id='textinput'><br>
<button class='key'>q</button>
<button class='key'>w</button>
<button class='key'>e</button>
<button class='key'>r</button>
<button class='key'>t</button>
<button class='key'>y</button><br>
<button id='capslock' capsactive=false>CapsLock</button>
</body>
You can access a textbox's value by element.value or by $(selector).val().
For changing, use: element.value = newvalue; (JS) or $(selector).val(newvalue); (jQuery).
In the example, ...addEventListener... attaches a function to each button. The function, here, changes the value of the textinput textbox, to be the previous value + the text of the button which was pressed.
For instance, if the even the capsLock button is given the class key, on clicking capsLock, the text "Caps Lock" will be appended to the textbox.
Note: This solution covers adding text to a definite field. If there are multiple textbox-es present on the page, and the text has to be added to the currently focused one, a different approach has to be taken:
var lfl = -1, capsactive = false;
$(document).ready(function() {
$('*').blur(function() {
lfl = this;
});
$('.key').click(function() {
if($(lfl).hasClass('vkballowed')) {
$(lfl).val($(lfl).val() + $(this).html());
$(lfl).focus();
}
});
$('#capslock').click(function() {
capsactive = !capsactive;
if(capsactive == true) {
$('.key').each(function() {
$(this).html($(this).html().toUpperCase());
});
} else {
$('.key').each(function() {
$(this).html($(this).html().toLowerCase());
});
}
$(lfl).focus();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<input id='input0' class='vkballowed'><p>Editable</p><br>
<input id='input1' class='vkballowed'><p>Editable</p><br>
<input id='input2'><p>Not Editable</p><br>
<button class='key'>q</button>
<button class='key'>w</button>
<button class='key'>e</button>
<button class='key'>r</button>
<button class='key'>t</button>
<button class='key'>y</button><br>
<button id='capslock'>CapsLock</button>
Example instructions: Focus on the Editable text-fields, then press a key on the virtual-keyboard, so that corresponding text is appended to the fields. The virtual-keyboard doesn't work on the Not Editable text-field.
Here, the last element on the page that lost focus (was blurred) is stored in a variable. Next, whenever a key on the virtual-keyboard is pressed, first, it is checked whether the keyboard is allowed on that control, then the button's text is appended to the control, and finally, the control is given its focus back. Note that if the class vkballowed is added to controls such as buttons, no action would be effective on those controls. On other controls such as textareas, which have a value property, the virtual-keyboard will be functional.
The above approach isn't wholly correct. If, for instance, a key on the virtual-keyboard is pressed right after some other interactive button on the page, that button would receive focus again (this may not re-cause the action attached to that button, though). It, hopefully, gives you a starting point though.
jquery
$('#buttonId').click(function(event) {
$('.tstboxClass').val('heelo i am text box value.');
})

Popup form on hover - how to make it the only one on the page?

I have 5 buttons 'Free call' on my site. On hover on them pops up contact form. I have a number of problems with it:
How to make the form be the only one on the page? I mean, from different buttons must be shown the same form. (For ex. I filled in the form in one place and when I hover other button, I see message 'You're done' or smth like that)
How to make the showing function work only once for every button? (The code below)
I tried to solve this problems but my methods didn't work
HTML
I have 5 such buttons on the page in different places
function showForm() {
var currentButton = $(this);
if ( currentButton.find('.popover-form') !== undefined ) {
var freeCallForm = "<form class=\"popover-form free-call-form\"><label for=\"\">Name</label><input type=\"text\"> <label for=\"\">Phonenum</label><input type=\"text\" value=\"+375\"><button>Call me!</button> </form>";
currentButton.append(freeCallForm);
}
}
$('.main-btn').on('mouseover', showForm);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="main-btn free-call">
<p>Use free call
<br/>
<i class="glyphicon glyphicon-chevron-down"></i>
</div>
This function above unfortunately doesn't work. With if I tried to make function work only when .main-btn hasn't .popover-form.
And other problem is that on hover on different buttons anyway appends NEW form for every button. I can't find correct solution for this problem.
var isOpen = false;
function showForm() {
var currentButton = $(this);
if ( currentButton.find('.popover-form') !== undefined && !isOpen) {
var freeCallForm = "<form class=\"popover-form free-call-form\"><label for=\"\">Name</label><input type=\"text\"> <label for=\"\">Phonenum</label><input type=\"text\" value=\"+375\"><button>Call me!</button> </form>";
isOpen = true;
currentButton.append(freeCallForm);
}
}
$('.main-btn').on('mouseover', showForm);
//on modal close set isOpen back to false
The solution is the append() method. It moves DOM elements, not copies them (I thought so).
So I inserted my <form id="free-call-form"> to the end of the document, before </body>.
JS
function showForm() {
var currentButton = $(this);
if ( ( currentButton.find('.popover-form') !== undefined && !currentButton.existsFreeCall ) ) {
var freeCallForm = $('#free-call-form');
currentButton.append(freeCallForm);
currentButton.existsFreeCall = true;
}
}
$('.main-btn').on('mouseover', showForm);
In this code the same form moves from one to another button without copying and multiple appending.

ZK Customize Calender Popup

I want to add clear button in Calender modal Popup.In my application lots of dateboxes are there.I restrict the user only to select the date not to enter. But in some cases I need to clear the date. Because of read only I am not able to clear it manually. I need to customize the calender which will reflect other places. And user can clear the datebox by clicking clear button in calender window.
Is there any way to add clear button in calender to fulfill my requirement?
Thanks in Advance!!
You can customize widget action with Client Side Programming (refer to Client Side Programming), for example:
<zk xmlns:w="client">
<!-- -->
<!-- Tested with ZK 6.5.3 -->
<!-- -->
<zscript><![CDATA[
// testing data
Date d = new Date();
]]></zscript>
<style>
/* invisible if not moved into datebox */
.invisible {
display: none !important;
}
</style>
put clear button under popup of datebox
<button label="show value" onClick="alert(((Datebox)self.getNextSibling()).getValue());" />
<datebox readonly="true" value="${d}">
<attribute w:name="doClick_"><![CDATA[
function (evt) {
// call original function
this.$doClick_(evt);
var pp = this.$n('pp'), // popup dom
$n = jq(this.$n()); // root dom
if (pp && !jq(pp).find('.datebox-inner-clear-button')[0]) {
// find button next to root dom
var btn = $n.next('.datebox-inner-clear-button')[0], // button dom element
btnWgt = zk.Widget.$('#' + btn.id), // button widget
popupWgt = this._pop;
// make button visible
jq(btn).removeClass('invisible');
// put button under popup dom
pp.appendChild(btn);
// store self at button widget
btnWgt.datebox = this;
var oldOnFloatUp = popupWgt.onFloatUp;
popupWgt.onFloatUp = function (ctl) {
if(ctl.origin == btnWgt) return; // do not close popup while mousedown on button
oldOnFloatUp.apply(this, arguments);
}
}
}
]]></attribute>
</datebox>
<button label="clear" sclass="datebox-inner-clear-button invisible">
<attribute w:name="doClick_"><![CDATA[
function (evt) {
// call original function
this.$doClick_(evt);
var dbx = this.datebox;
if (dbx) {
dbx.getInputNode().value = '';
dbx.updateChange_();
}
}
]]></attribute>
</button>
</zk>
You may also want to wrap the customized datebox and button by Macro Component or Composite Component as needed.

Keeping buttons in place when using .hide()

Not sure if this is because I'm new to meteor or if I am making an error in my syntax with my HTML or jQuery. Ideally I would like the whole grid to stay in place when a button is clicked. For example if you clicked the button in the middle of the grid there would be a empty spot where that button was before. My question is, why is it that when I click a button the button disappears but moves the whole grid and what do I do to fix this?
HTML:
<head>
<title>bubblepopper</title>
</head>
<body>
<center>{{> grid}}</center>
</body>
<template name ="grid">
<div id="container">
{{#each buttons}}
<button class="button" type="button"></button>
{{/each}}
</div>
</template>
JS:
Buttons = new Meteor.Collection("buttons");
if (Meteor.isClient) {
player = prompt("What is your name?")
Template.grid.buttons = function () {
}
Template.grid.buttons = function () {
var list = [];
for(var i=1; i<=64; i++){
list.push({value: i});
}
return list;
};
Template.grid.events({
'click .button': function(ev) {
$(ev.target).hide()
}
});
}
if (Meteor.isServer) {
}
.hide() works by adding the style display: none to the element. This removes the space used by the element in the rendered page.
If you want to make something invisible but keep its space on the page, use the visibility style:
$(ev.target).css('visibility', 'hidden');
To restore it, set the visibility to visible.

Categories

Resources