Dojo - don't repeat yourself - dojo

Is there a simpler way to write something like this in dojo (instead of having a function for each thing i want to show or hide)? I know there must be a way to avoid such repetition but I'm not sure how to do it.
on(dom.byId("thing_toggle2"), "click", function(){
if(thing_list2.style.display == "none") {
thing_list2.style.display = "block";
dom.byId("toggle2_sign").innerHTML = "(-)";
} else {
thing_list2.style.display = "none";
dom.byId("toggle2_sign").innerHTML = "(+)";
};
});
on(dom.byId("thing_toggle3"), "click", function(){
if(thing_list3.style.display == "none") {
thing_list3.style.display = "block";
dom.byId("toggle3_sign").innerHTML = "(-)";
} else {
thing_list3.style.display = "none";
dom.byId("toggle3_sign").innerHTML = "(+)";
};
});

I didn't test this, but it should give you a starting point. Adding an additional section, would just involve adding to the array of data.
var fnToggle = function(nodeMap) {
var expand = domStyle.get(dom.byId(nodeMap.contentNode), 'display') == 'none';
domStyle.set(dom.byId(nodeMap.contentNode), 'display', expand ? 'block' : '');
html.set(dom.byId(nodeMap.expandoNode), expand ? '+' : '-');
};
var nodes = [
{ eventNode: 'thing_toggle2', contentNode: thing_list2, expandoNode: 'toggle2_sign' },
{ eventNode: 'thing_toggle3', contentNode: thing_list3, expandoNode: 'toggle3_sign' }
];
array.forEach(nodes, function(nodeMap) {
on(dom.byId(nodeMap.eventNode), "click", function(){ fnToggle(nodeMap); });
});
// domStyle -> dojo/dom-style
// html -> dojo/html
// array -> dojo/_base/array

You could use dojo/fx/Toggler which uses dojo/_base/fx.fadeOut and dojo/_base/fx.fadeIn

I actually decided to do this as a plain old javascript function which I can reuse elsewhere, including pages that might not need dojo. Something like this:
[1]
[2]
<div id="myName" style="display:none;">Antonio</div>
<div id="anothername" style="display:none;">Elliot</div>
<script type="text/javascript">
function showlayer(layer){
var myLayer = document.getElementById(layer).style.display;
if(myLayer=="none"){
document.getElementById(layer).style.display="block";
} else {
document.getElementById(layer).style.display="none";
};
}
</script>

Related

Update v-html without misbehaving focus on typing VUE JS

I need help,
Requirement
when the user types in an input box I want to highlight the link with blue color if any
My Research
when I dig into it, I realize that without using a contenteditable div it's not possible to do, also there is no v-model associated with contenteditable div I am manually updating the state.
so far I have this, courtesy- contenteditable div append a html element and v-model it in Vuejs
<div id="app"><div class="flex">
<div class="message" #input="updateHtml" v-html="html" contenteditable="true"></div>
<br>
<div class="message">{{ html }}</div>
</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
html: 'some text',
},
methods: {
updateHtml: function(e) {
this.html = e.target.innerHTML;
},
renderHtml: function(){
this.html += '<img src="https://cdn-images-1.medium.com/max/853/1*FH12a2fX61aHOn39pff9vA.jpeg" alt="" width=200px>';
}
}
});</script>
Issue
every time user types something, the focus is misbehaving which is strange to me, I want v-html to update along with user types #keyup,#keydown also have the same behavior.it works ok on #blur #focusout events, but that's not what I want
Appreciate Help.Thanks
I figured it out myself. Posting the answer so that may help other developers. v-HTML doesn't do all the trick. You’ll need to store the cursor position so it can be restored properly each time the content updates as well as parse the content so that it renders as expected. Here is the example
HTML
<p>
An example of live syntax highlighting in a content-editable element. The hard part is storing and restoring selection after changing the DOM to account for highlighting.
<p>
<div contentEditable='true' id='editor'>
Edit text here. Try some words like bold and red
</div>
<p>
Just a demo trivial syntax highlighter, should work with any syntax highlighting you want to implement.
</p>
JS
const editor = document.getElementById('editor');
const selectionOutput = document.getElementById('selection');
function getTextSegments(element) {
const textSegments = [];
Array.from(element.childNodes).forEach((node) => {
switch(node.nodeType) {
case Node.TEXT_NODE:
textSegments.push({text: node.nodeValue, node});
break;
case Node.ELEMENT_NODE:
textSegments.splice(textSegments.length, 0, ...(getTextSegments(node)));
break;
default:
throw new Error(`Unexpected node type: ${node.nodeType}`);
}
});
return textSegments;
}
editor.addEventListener('input', updateEditor);
function updateEditor() {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
const textContent = textSegments.map(({text}) => text).join('');
let anchorIndex = null;
let focusIndex = null;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
if (node === sel.anchorNode) {
anchorIndex = currentIndex + sel.anchorOffset;
}
if (node === sel.focusNode) {
focusIndex = currentIndex + sel.focusOffset;
}
currentIndex += text.length;
});
editor.innerHTML = renderText(textContent);
restoreSelection(anchorIndex, focusIndex);
}
function restoreSelection(absoluteAnchorIndex, absoluteFocusIndex) {
const sel = window.getSelection();
const textSegments = getTextSegments(editor);
let anchorNode = editor;
let anchorIndex = 0;
let focusNode = editor;
let focusIndex = 0;
let currentIndex = 0;
textSegments.forEach(({text, node}) => {
const startIndexOfNode = currentIndex;
const endIndexOfNode = startIndexOfNode + text.length;
if (startIndexOfNode <= absoluteAnchorIndex && absoluteAnchorIndex <= endIndexOfNode) {
anchorNode = node;
anchorIndex = absoluteAnchorIndex - startIndexOfNode;
}
if (startIndexOfNode <= absoluteFocusIndex && absoluteFocusIndex <= endIndexOfNode) {
focusNode = node;
focusIndex = absoluteFocusIndex - startIndexOfNode;
}
currentIndex += text.length;
});
sel.setBaseAndExtent(anchorNode,anchorIndex,focusNode,focusIndex);
}
function renderText(text) {
const words = text.split(/(\s+)/);
const output = words.map((word) => {
if (word === 'bold') {
return `<strong>${word}</strong>`;
}
else if (word === 'red') {
return `<span style='color:red'>${word}</span>`;
}
else {
return word;
}
})
return output.join('');
}
updateEditor();
Hope this helps...

Vue.js list not updating when data changes

i'm trying re-organised a list of data. I have given each li a unique key, but still, no luck!
I have had this working before exactly like below, think i'm cracking up!
let app = new Vue({
el: '#app',
data: {
list: [
{ value: 'item 1', id: '43234r' },
{ value: 'item 2', id: '32rsdf' },
{ value: 'item 3', id: 'fdsfsdf' },
{ value: 'item 4', id: 'sdfg543' }
]
},
methods: {
randomise: function() {
let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
this.list = input;
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in list" :key="item.id">{{ item.value }}</li>
</ul>
Randomize
</div>
Edit:
Thanks for the answers, to be honest the example I provided may not have been the best for my actual issue I was trying to solve. I think I may have found the cause of my issue.
I'm basically using a similar logic as above, except i'm moving an array of objects around based on drag and drop, this works fine with normal HTML.
However, i'm using my drag and drop component somewhere else, which contains ANOTHER component and this is where things seem to fall apart...
Would having a component within another component stop Vue from re-rendering when an item is moved within it's data?
Below is my DraggableBase component, which I extend from:
<script>
export default {
data: function() {
return {
dragStartClass: 'drag-start',
dragEnterClass: 'drag-enter',
activeIndex: null
}
},
methods: {
setClass: function(dragStatus) {
switch (dragStatus) {
case 0:
return null;
case 1:
return this.dragStartClass;
case 2:
return this.dragEnterClass;
case 3:
return this.dragStartClass + ' ' + this.dragEnterClass;
}
},
onDragStart: function(event, index) {
event.stopPropagation();
this.activeIndex = index;
this.data.data[index].dragCurrent = true;
this.data.data[index].dragStatus = 3;
},
onDragLeave: function(event, index) {
this.data.data[index].counter--;
if (this.data.data[index].counter !== 0) return;
if (this.data.data[index].dragStatus === 3) {
this.data.data[index].dragStatus = 1;
return;
}
this.data.data[index].dragStatus = 0;
},
onDragEnter: function(event, index) {
this.data.data[index].counter++;
if (this.data.data[index].dragCurrent) {
this.data.data[index].dragStatus = 3;
return;
}
this.data.data[index].dragStatus = 2;
},
onDragOver: function(event, index) {
if (event.preventDefault) {
event.preventDefault();
}
event.dataTransfer.dropEffect = 'move';
return false;
},
onDragEnd: function(event, index) {
this.data.data[index].dragStatus = 0;
this.data.data[index].dragCurrent = false;
},
onDrop: function(event, index) {
if (event.stopPropagation) {
event.stopPropagation();
}
if (this.activeIndex !== index) {
this.data.data = this.array_move(this.data.data, this.activeIndex, index);
}
for (let index in this.data.data) {
if (!this.data.data.hasOwnProperty(index)) continue;
this.data.data[index].dragStatus = 0;
this.data.data[index].counter = 0;
this.data.data[index].dragCurrent = false;
}
return false;
},
array_move: function(arr, old_index, new_index) {
if (new_index >= arr.length) {
let k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing
}
}
}
</script>
Edit 2
Figured it out! Using the loop index worked fine before, however this doesn't appear to be the case this time!
I changed the v-bind:key to use the database ID and this solved the issue!
There are some Caveats with arrays
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:
Vue.set(vm.items, indexOfItem, newValue)
Or in your case
randomise: function() {
let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
Vue.set(input, randomIndex, input[i]);
Vue.set(input, i, itemAtIndex);
}
this.list = input;
}
Here is an working example: Randomize items fiddle
Basically I changed the logic of your randomize function to this:
randomize() {
let new_list = []
const old_list = [...this.list] //we don't need to copy, but just to be sure for any future update
while (new_list.length < 4) {
const new_item = old_list[this.get_random_number()]
const exists = new_list.findIndex(item => item.id === new_item.id)
if (!~exists) { //if the new item does not exists in the new randomize list add it
new_list.push(new_item)
}
}
this.list = new_list //update the old list with the new one
},
get_random_number() { //returns a random number from 0 to 3
return Math.floor(Math.random() * 4)
}
randomise: function() { let input = this.list;
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = this.list[randomIndex];
Vue.set(this.list,randomIndex,this.list[i])
this.list[randomIndex] = this.list[i];
this.list[i] = itemAtIndex;
} this.list = input;
}
Array change detection is a bit tricky in Vue. Most of the in place
array methods are working as expected (i.e. doing a splice in your
$data.names array would work), but assigining values directly (i.e.
$data.names[0] = 'Joe') would not update the reactively rendered
components. Depending on how you process the server side results you
might need to think about these options described in the in vue
documentation: Array Change Detection.
Some ideas to explore:
using the v-bind:key="some_id" to have better using the push to add
new elements using Vue.set(example1.items, indexOfItem, newValue)
(also mentioned by Artokun)
Source
Note that it works but im busy so i cant optimize it, but its a little bit too complicted, i Edit it further tomorrow.
Since Vue.js has some caveats detecting array modification as other answers to this question highlight, you can just make a shallow copy of array before randomazing it:
randomise: function() {
// make shallow copy
let input = this.list.map(function(item) {
return item;
});
for (let i = input.length-1; i >=0; i--) {
let randomIndex = Math.floor(Math.random()*(i+1));
let itemAtIndex = input[randomIndex];
input[randomIndex] = input[i];
input[i] = itemAtIndex;
}
this.list = input;
}

Javascript - define function with variable

Let's say I have the following code
var $variable = $('<li/>', {
tabindex: 0,
click: function() {
//more code in here
}
}
is it possible to define the click: with a variable? Baring in mind it is already inside a variable and will also include an if statement, like so:
if(condition) {
var functiontype = 'click';
}
else {
var functiontype = 'hover';
}
var $variable = $('<li/>', {
tabindex: 0,
functiontype: function() {
//more code in here
}
}
I tried doing this but it didn't work. Can someone please point me in the right direction?
if(condition) {
var functiontype = 'click';
}
else {
var functiontype = 'hover';
}
var options = {};
options["tabindex"] = 0;
options[functiontype] = function(){
// your code
}
var $variable = $('<li/>', options);
You may do the reverse :
var $variable = $('<li/>', {
tabindex: 0
};
$variable[condition?'click':'hover'] = function(){
// the code here
}

Sharing variable around different instances of YUI

I have made up the custom module as :
YUI.add('util', function(Y) {
Y.namespace('com.myCompany');
var NS = Y.com.myCompany;
NS.val = undefined;
}, '3.3.0', {
requires : []
});
What I am trying to do is share this variable val in the instances where I use this module "util". As in
YUI().use("util","node","event",function (Y) {
Y.namespace('com.myCompany');
var MV = Y.com.myCompany;
var setVal = function(e){
MV.val = 10;
}
Y.on("click", setVal,"#one");
});
Now if I want to get this in other instance I am doing as the following:
YUI().use("util","node","event",function (Y) {
Y.namespace('com.myCompany');
var MV = Y.com.myCompany;
var getVal = function(e){
alert(MV.val);
}
Y.on("click", getVal,"#two");
});
But this does not seem to be working. Is there a way to get this behavior. I am doing this only to split up the code.
In this case, you should only create one sandbox. The correct way to break up your code is to use YUI.add to create the modules and specify their dependencies. One way to do this is to structure your code as follows:
// util.js
YUI.add('util', function (Y) {
var NS = Y.namespace('com.MyCompany');
NS.val = null;
}, 'version', {
requires: ['some', 'dependencies']
});
// one.js
YUI.add('one', function (Y) {
var NS = Y.namespace('com.MyCompany');
Y.on('click', function (e) { NS.val = 23; }, '#one');
}, 'version', {
requires: ['util']
});
// two.js
YUI.add('two', function (Y) {
var NS = Y.namespace('com.MyCompany');
Y.on('click', function (e) { alert(NS.val); }, '#two');
}, 'version', {
requires: ['util']
});
// index.html
<button id="one">Set the value</button>
<button id="two">Get the value</button>
<script>
YUI.use('one, 'two', 'node', 'event', function (Y) {
// main application logic here
});
</script>
This allows you to break up your code into separate modules that share the same YUI sandbox instance.
Note also YUI.namespace returns the namespace in question, so you don't need the extra variables.
The problem is that YUI() is creating a new sandbox with each execution. If you want to reuse it you need to capture its value after the first "use" execution and reuse that value later. There may be a better YUish way to do this but I use a global YUI_MAIN:
var YUI_MAIN = YUI().use("util","node","event",function (Y) {
Y.namespace('com.myCompany');
var MV = Y.com.myCompany;
var setVal = function(e){
MV.val = 10;
};
Y.on("click", setVal,"#one");
});
YUI_MAIN.use(function (Y) {
Y.namespace('com.myCompany');
var MV = Y.com.myCompany;
var getVal = function(e){
alert(MV.val);
};
Y.on("click", getVal,"#two");
});
If you really wanted to share between separate sandboxes and avoid an extra global you could use a closure to create a private variable with something like this:
YUI.add('util', (function () {
var privateUtilNS = {};
return function(Y) {
privateUtilNS['val'] = undefined;
Y.setVal = function(e){
privateUtilNS.val = 10;
};
Y.getVal = function(e){
alert(privateUtilNS.val);
};
};
}()), '3.3.0', {
requires : []
});
YUI().use("util","node","event",function (Y) {
Y.on("click", Y.setVal,"#one");
});
YUI().use("util","node","event",function (Y) {
Y.on("click", Y.getVal,"#two");
});

Page refresh with onclick event in dojo and I don't want a page refresh

For some reason in IE8 when I run this function after an onclick event it causes a page refresh. I don't wan the page refresh to happen.
var edealsButton = dojo.byId("edeals_button");
var edealEmailInput = dojo.byId("edeals_email");
var edealsSignup = dojo.byId("edeals_signup");
var edealsThankYou = dojo.byId("edeals_thankyou");
var currentValue = dojo.attr(edealEmailInput, 'value');
if (currentValue != '' && currentValue != 'Enter your email') {
var anim = dojox.fx.flip({
node: edealsSignup,
dir: "right",
duration: 300
})
dojo.connect(anim, "onEnd", this, function() {
edealsSignup.style.display = "none";
edealsThankYou.style.display = "block";
})
dojo.connect(anim, "onBegin", this, function() {
var criteria = { "emailAddress": '"' + currentValue + '"' };
alert("currentValue " + currentValue);
var d = essentials.CallWebserviceMethod('AlmondForms.asmx/SignupEdeal', criteria);
d.addCallback(function(response) {
var obj = dojo.fromJson(response);
alert(obj.d);
if (obj != null && obj.d != null) {
//alert(obj.d);
if (obj.d == false)
{
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
alert("edealError: " + edealError);
dojo.style(edealSuccess, "display", "none");
dojo.style(edealError, "display", "inline-block");
}
else
{
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
dojo.style(edealSuccess, "display","inline-block");
dojo.style(edealError, "display", "none");
}
}
else {
var edealSuccess = dojo.byId("edeals_succes_thankyou");
var edealError = dojo.byId("edeals_error_thankyou");
dojo.style(edealSuccess, "display", "none");
dojo.style(edealError, "display", "inline-block");
}
});
})
anim.play();
edealEmailInput.innerHTML == 'Enter your email';
}
else
{
dojo.attr(edealEmailInput, 'value', 'Enter your email');
dojo.style(edealEmailInput, 'color', '#CC2525');
}
It looks like your "d.addCallback" code might not be disposed of properly. You might want to try placing "dojo.stopEvent()" possibly before the "anim.play();" line and see if this would stop the postback.
From the api.dojotoolkit.org site, the dojo.stopEvent() "prevents propagation and clobbers the default action of the passed event". From the docs.dojocampus.org site, they say that "dojo.stopEvent(event) will prevent both default behavior any any propagation (bubbling) of an event."
Good luck, and hope this helps you out some.