I am designing a web page for state machine and I am using jsplumb state machine. I want to draw this state machine on multiple tabs. Tabs are created dynamically using 'New' button
<div id="tabs">
<ul>
<li>
Untitled-1
<span class="ui-icon ui-icon-close"></span>
</li>
</ul>
<div id="Untitled-1" class="content">
<div id="Untitled-1-content" class="fsm">
</div>
</div>
</div>
In the div area of class 'fsm', state machine is drawn dynamically by dragging state machine(SM) blocks and dropping it in 'fsm' div. If there are multiple tabs, after dropping SM block in fsm div, it is draggable in fsm div only on 1st tab and not in another tabs. For example, if there are 2 tabs, I can re-position SM blocks only in 1st tab, on 2nd tab when I start dragging SM block, it disappears from fsm div and its left-top coordinates become negative.
This is the code for fsm div -
$("div.fsm").droppable({
accept: ".cTemp, .rTemp",
hoverClass: "ui-state-hover",
drop: function (event, ui) {
var draggable = $(ui.draggable[0]);
if (draggable.attr("class").indexOf("rTemp") > -1) {
$("<div class='rBase' id='rect" + rectI + "'></div>").text("rect" + rectI).appendTo(this);
$("#rect" + rectI).append("<div class='ep'></div>").appendTo(this);
$("#rect" + rectI).addClass("draggable");
jsPlumb.draggable($(".rBase"),{containment: ".fsm", zIndex: 30});
} else if (draggable.attr("class").indexOf("cTemp") > -1) {
$("<div class='cBase' id='circle" + circleI + "'></div>").text("circle" + circleI).appendTo(this);
$("#circle" + circleI).append("<div class='ep'></div>").appendTo(this);
$("#circle" + circleI).addClass("draggable");
jsPlumb.draggable($(".cBase"),{containment: ".fsm", zIndex: 30});
}
jsPlumbDemo.init();
}
});
This might be because fsm div is created dynamically or there are more than 1 fsm divs. What would be the best option to handle this multitab situation ?
I tried to remove fsm div from all tabs except the active tab then it works. I can drag SM blocks from tab content even if there are more than 1 tabs since there is only 1 fsm div. But then I have to add fsm div back again to the tab when I switch to that tab.
Then which is the best way to save the tab's content before switching to any other tab and load it back when that tab is opened ?
To give my background, this is my first time to work on jquery/jsplumb so detailed explanation is very much appreciated.
Here is a couple of comments
You can use the Jquery command hasClass() instead of attr("class")indexOf
To handle tabs I guess you are using the Jquery command tabs() ? in that case you don't need to save anything.
As far as your dropping issue is concerned you should use the appendTo: property in the draggable() parameters specifying which div they are supposed to be dropped to. You can change the target tab by reacting to the jquery tab select event this way
$("#tabs").tabs({
select: function(event, ui) {
// Your code
}
});
You can play with this fiddle I made for you
http://jsfiddle.net/webaba/sy9PJ/
$(document).ready(function () {
// Calls custom select tab function, scroll down for implementation
selectTab(1);
// Block models are made draggable
function selectTab(tabIndex) {
var tabName = '#tab'+tabIndex;
$(".model").draggable({
appendTo: tabName,
helper: 'clone'
});
}
// Tabs div is formatted as tabs by jquery
$("#tabs").tabs({
select: function (event, ui) {
selectTab(ui.index + 1);
}
});
//Sets canvas as droppable
$(".tab").droppable({
accept: ".model",
drop: function (event, ui) {
var newBlock = ui.helper.clone();
$(newBlock).removeClass("model").addClass("block");
$(this).append($(newBlock));
$(newBlock).draggable({containment:$(this)});
return true;
}
});
});
and the html
<body>
<div id="container">
<div id="library">
<div class="model type1" type="1">Block1</div>
<div class="model type2" type="2">Block2</div>
</div>
<div id="tabs">
<ul>
<li>Tab1
</li>
<li>Tab2
</li>
<li>Tab3
</li>
</ul>
<div id="tab1" class="tab">Tab1</div>
<div id="tab2" class="tab">Tab2</div>
<div id="tab3" class="tab">Tab3</div>
</div>
</div>
</body>
and the CSS
#container {
width:100%;
height:300px;
}
#tabs {
height:300px;
width:70%;
float:right;
border:1px solid black;
}
.tab{
height:100%;
border:1px dotted blue;
}
#library {
border:1px solid black;
width:20%;
height:300px;
float:left;
}
.type1 {
width:50px;
height:50px;
border:1px solid green;
}
.type2 {
width:50px;
height:20px;
border:1px solid red;
}
Related
I am using Vue 3 to show a set of links for which I am assigning event handlers dynamically(based on link id).
The issues is: The first time when any link is clicked, the corresponding event is not triggered. But subsequently clicks are perfectly working.
The updated code is below:
<script setup>
const makeSizer = ([...sizes]) => {
sizes.map((size) =>{
console.log('size-' + size);
document.getElementById('size-' + size).style.display = "";
document.getElementById('size-' + size).onclick = ((e) =>{
e.preventDefault();
document.body.style.fontSize = e.target.text + 'px';
e.target.style.display = "none";
});
});
};
function zoomIt(){
return {
zoom: makeSizer([12,14,16,18])
}
}
</script>
<template>
<div class="greeting"> {{zoom}}
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<div class="link">
12
</div>
<div class="link">
14
</div>
<div class="link">
16
</div>
<div class="link">
18
</div>
</div>
</template>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
.link{
padding:5px; display:inline-table;
}
.greeting {
color: red;
font-weight: bold;
}
.greeting a{
border:2px solid blue;
padding:3px;
color:white;
background-color:blue;
}
#size-12{ font-size:12px;}
#size-14{ font-size:14px;}
#size-16{ font-size:16px;}
#size-18{ font-size:18px;}
</style>
The bad news is, the way you approach it is an anti-pattern in Vue. The good news is, with some small changes you will end up with code that is much more simple to read and maintain!
You are doubling your event listeners by calling onclick() inside makeSizer() and defining click events via #click.
However, let us not just fix the bug by altering the existing code. What we want to do is to get rid of the anti-patern. So instead, we try passing the desired value of 'zoom' to the handler directly and avoid the beforementioned duplications altogether.
// Script
// We define a function that adjusts zoom value using only the value that is being passed to it as an argument
setZoom(size) {*code*}
// Template
<button #click.prevent="setZoomTo(12)">
This is a general idea. I modified your code a bit more to make it more maintainable and added comments where changes were made. I hope this helps.
Script
<script setup>
import { ref } from "vue";
const currentZoom = ref(12); // Let us set default zoom to 12
const zoomOptions = [12, 14, 16, 18]; // We define zoom options as an array to dynamically generate buttons
function setZoomTo(size) {
currentZoom.value = size; // Set current zoom value
document.body.style.fontSize = currentZoom.value + "px"; // Adjust fontSize on body
}
</script>
Template
<div class="links">
<button // We use button tag for semantic correctness
v-for="zoom in zoomOptions" // For every value in zoomOptions a button is created
:key="zoom"
:disabled="zoom === currentZoom" // If zoom value represented by the button is also currentZoom value => add disabled attribute to the button
#click.prevent="setZoomTo(zoom)" // Adjust currentZoom value according to the zoom value represented by the button
>
{{ zoom }} // Button's zoom value
</button>
</div>
Style
.links {
display: flex;
gap: 16px;
}
.links button {
border: 2px solid blue;
padding: 3px;
color: white;
background-color: blue;
cursor: pointer;
}
.links button:disabled {
opacity: 0.7; // For better UX we change button's opacity instead of hiding it
}
I'm going to make Nuxt function like following sample code.
The work is being carried out without a good understanding of Nuxt.
It is not even using the tag <nuxt/>. Because it has to be made with iframe.
The reason why we want to use iframe is that we want existing information to remain even if the new content generated is tabbed.
The way I want to work doesn't seem to fit Nuxt's characteristics, but... I can't think of any other way.
The question I want is as follows.
Create tabMenu using Nuxt, and each tab content must maintain existing data even if the tab moves.
Is this possible with Nuxt?
// it just sample code, Not my question
$(function(){
function setPage(name,src){
const tabs = `<button role="button">${name}</button>`
const iframes = `<div>here is ifame area of ${name} page</div>`
$('.page-tab').append(tabs)
$('.page-frame').append(iframes)
}
function setView(number){
$('.page-frame > div').eq(number).removeClass('hide').siblings().addClass('hide')
}
$('.tab-content > li').click(function(){
const $this = $(this);
const index = $this.index()
const name = $this.text()
const src = $this.data('src')
setPage(name+index,src)
setView(index)
$('.page-tab button').click(function(){
setView($(this).index())
})
});
})
html,body,#sample {
height: 100%;
}
#sample {
display :flex;
}
aside {
flex: 0 0 auto;
height: 100%;
background-color:#eee;
}
.tab-content li {
text-align: center;
cursor: pointer;
padding: 10px 8px;
border-bottom: 1px solid #ddd;
}
main {
flex: 1 0 auto;
display: flex;
flex-direction:column;
}
.hide {
display: none !important;
}
<link href="https://cdn.jsdelivr.net/npm/reset-css#5.0.1/reset.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="sample">
<aside>
<ul class="tab-content">
<li role="button" data-src="https://www.reddit.com/">Apple</li>
<li role="button" data-src="https://finance.yahoo.com/quote/TSLA/">Orange</li>
<li role="button" data-src="https://github.com/dogecoin/dogecoin">Water</li>
</ul>
</aside>
<main>
<div class="page-tab"></div>
<div class="page-frame"></div>
</main>
</div>
You should really not try to do this in jQuery but in pure VueJS (or Nuxt, it's the same). Mixing declarative and imperative code is not a good idea.
For a tab functionality, you can use dynamic components to keep up the state while still toggling tabs.
I'm not sure if you're using SFC components or not, but here is a JSfiddle that may show you how to make tabs in VueJS: https://jsfiddle.net/chrisvfritz/Lp20op9o/
I've been struggling with implementing a dialog box / modal design and behavior from inside of children components in Vue.
So here's the set up, I have a Vue component called "WorkersComponent". This component is just a list of workers assigned to some case fetched from the backend (Laravel). This component is reusable an can be in any place/case/ticket/lookup where a user would want to add workers to.
The component has an "add" button in it. Once clicked, I want a new component to appear at that location (at the click location), which could be a dropdown, modal, dialogue - doesn't really mater. This subcomponent has a search bar and some controls to fetch workers info and add them to the parent component.
My problem is that I can't figure out how to get the nesting / positioning to work. Because it is a child component, its position is always against the parent component, so I can only control it's position within that parent component, but I want it to be displaying on top of other DOM elements and components if necessary - whatever makes sense. Worst case scenario - I want it to be in the middle of the page at least.
Now how do I implement this? I probably want it to be a unique subcomponent, not a global generic modal. On top of it, if it were a global generic, then I have an idea of how to populate the modal with relevant options but how to pass them back to the component that called the modal - no idea. So I'm struggling with the approach. It seems like such a simple thing and yet, I can't find a viable solution.
<workers-component name="Assigned Workers">
<button <!-- Vue controls in here to invoke a modal/dialogue/dropdown --> >Add Worker</button>
<!-- The subcomponent itself -->
<workers-select-component />
</workers-component>
Here's an example from Gmail: wherever this search bar is (let's say it's a parent component), if I click on a triangle, it will expand this other pane, which will (1) appear wherever the search bar is and (2) cover other elements to display it and (3) not dismiss the pane until manually dismissed (which is easy but normal Bootstrap dropdowns don't support this).
Here's a solution:
Vue.component('ToggleDialog', {
props: ['state'],
template: `
<button
#click="$emit('toggle', state)"
class="dialog-button"
>
TOGGLE MODAL
</button>
`
})
Vue.component('DialogModal', {
props: ['state'],
template: `
<div
class="dialog-backdrop"
>
<div
class="dialog-button"
>
<toggle-dialog
:state="state"
#toggle="toggleModal"
/>
</div>
</div>
`,
methods: {
toggleModal(state) {
this.$emit('toggle', state)
}
}
})
new Vue({
el: "#app",
data() {
return {
isModalOpen: false
}
},
methods: {
toggleModal(state) {
this.isModalOpen = !state
}
}
})
.dialog-backdrop {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.dialog-button {
padding: 10px 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<toggle-dialog :state="isModalOpen" #toggle="toggleModal">
OPEN MODAL
</toggle-dialog>
<dialog-modal v-if="isModalOpen" :state="isModalOpen" #toggle="toggleModal" />
</div>
As you can see the modal is not the child of the button, but the child of the main app. toggle events are emitted (and the modal re-emits it) to the app that controls the state of the modal dialog.
For more complex apps it might not be the best. You could use an event bus (deprecated in Vue3) or Vuex (state management) to overcome this multiple emit-re-emit stuff.
EDIT: NEW SOLUTION
Vue.component('ToggleDialog', {
data() {
return {
isModalOpen: false
}
},
template: `
<div
class="toggle-modal-wrapper"
>
<button
#click="isModalOpen = !isModalOpen"
class="dialog-button"
>
TOGGLE MODAL
</button>
<dialog-modal
v-if="isModalOpen"
#toggle="isModalOpen = !isModalOpen"
>
<slot></slot>
</dialog-modal>
</div>
`
})
Vue.component('DialogModal', {
props: {
innerComponent: {
type: String
}
},
template: `
<div
class="dialog-backdrop"
>
<div>
<slot></slot>
<br />
<button
#click="$emit('toggle')"
class="dialog-button"
>
TOGGLE MODAL
</button>
</div>
</div>
`
})
new Vue({
el: "#app",
})
.toggle-modal-wrapper {
z-index: 10000;
}
.dialog-backdrop {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.dialog-button {
padding: 10px 15px;
}
.other-part {
z-index: 1000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<toggle-dialog>
<template>
This is the first.
</template>
</toggle-dialog>
<toggle-dialog>
<template>
This is the other.
</template>
</toggle-dialog>
<div class="other-part">
OTHER PART OF THE UI
</div>
</div>
You could try playing with slots if you want a reusable component - or even better: the render function.
I made a simple vertical content slider based on tabs and found optical issue when animating . If you click on different tab, current content slides up and in the same time new one slides down. All contents have same height. The problem is that the height is changing a little bit during the animation (focus on bottom border). I don't really know how to fix it. Is there any way to prevent it?
Here is the complete code:
http://jsfiddle.net/YGY26/8/
JS:
var active = 1;
function item(id) {
if (id !== active) {
$("#description" + active).slideUp("slow");
if (active !== id) {
$("#description" + id).slideDown("slow");
active = id;
}
}
}
HTML:
<div class='slider_box' onclick='item(1);'>
<h3>Content1</h3>
<div id='description1' class='slider_content' style='display: block'>
some content to show
</div>
</div>
<div class='slider_box' onclick='item(2);'>
<h3>Content2</h3>
<div id='description2' class='slider_content'>
some content to show
</div>
</div>
<div class='slider_box' onclick='item(3);'>
<h3>Content3</h3>
<div id='description3' class='slider_content'>
some content to show
</div>
</div>
Basic CSS:
.slider_box {
position: relative;
width: 330px;
}
.slider_box h3 {
color: white;
background: black;
}
.slider_content {
height: 330px;
display: none;
}
I have a dojo dijit tab container and I want the tabs to flash a few times when an event occurs and it is not the selected tab. For example, when I receive a chat message I want the "Chat tab" to flash a few times as a visual notification that a chat has been received. I'm having a hard time finding the right control (the tab) to modify. Here is the code:
The HTML:
<div data-dojo-type="dijit.layout.TabContainer" data-dojo-props="region:'center',splitter: true">
<div id="tabChat" title="Chat" data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="iconClass:'i-chat', design: 'sidebar'">
<div id="pnlChatLog" style="background-color:#FFF; padding:0px;" data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'center', splitter:true">
<div id="divChatLog" style="width:100%; height:100%; overflow-y:scroll; overflow-x:hidden;">
</div>
</div>
<div id="pnlChatMessage" style="background-color:#FFF; padding:0px; overflow:hidden;" data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'bottom', splitter:false">
<input id="txtChatMessage" style="width:100%; margin:0px; border:0px;" data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="intermediateChanges:false,placeholder:'Enter Message'" />
</div>
</div>
<div id="tabQuestions" title="Questions" data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="iconClass:'i-help', design: 'sidebar'">
<div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="region:'center', splitter:false, gutters:false">
<div style="background-color:#FFF; padding:0px; border-top:0px;" data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'center', splitter:true">
<div id="gridQuestions"></div>
</div>
</div>
</div>
The javaScript:
//Chat message Event
chat.on("message", function(e) {
//Message code is here...
//TODO: Make the tab flash if it is not the current tab
});
Note: The messaging code (not shown here) works. I just need to know what javaScript will replace the TODO section so the tab blinks/flashes for a few seconds at this point.
To get to the tab button you have to use the tab element's "controlButton" then modify the domNode. Here is an example:
//A method for the blinking using setInterval. The top line shows
//how to get the actual tab that you want to modify. Then add and remove the
//Hover classes for a nice flashing/blinking effect.
function blinkTab(tabId, count, interval) {
var tabbutton = dijit.byId(tabId).controlButton.domNode;
var interval = setInterval(function(){
if(count % 2 == 0) {
tabbutton .className += " dijitTabHover";
tabbutton .className += " dijitHover";
}
else {
//Not sure this is the best way to remove a class but I couldn't find
//a "clean" way to do it with dojo.
tabbutton .className = tabbutton .className.replace( /(?:^|\s)dijitTabHover(?!\S)/ , '');
tabbutton .className = tabbutton .className.replace( /(?:^|\s)dijitHover(?!\S)/ , '');
}
if(count == 0) {
tabbutton .className = tabbutton .className.replace( /(?:^|\s)dijitTabHover(?!\S)/ , '');
tabbutton .className = tabbutton .className.replace( /(?:^|\s)dijitHover(?!\S)/ , '');
clearInterval(interval);
}
count--;
}, interval);
}
//Now make the calls where desired
//Chat message Event
chat.on("message", function(e) {
//Message code is here...
blinkTab("tabChat", 10, 500);
});
//Question Event
questions.on("message", function(e) {
//Question code is here...
blinkTab("tabQuestions", 10, 500);
});
You might just want to change the "class" of the tab title span (or is it a div? don't remember). The easy way is to use firebug, check the element used for the tab title, identify it in the node hierarchy, then put an id on your tab like tabMsg or something, then you justy ned to dijit.byId to get the right tab, and then go to the title node and addClass/removeClass every seconds or 0.5s to make it "blink".
You might want to add a "blinking" property to your tab, so that while this is true you switch classes, and when you click on the tab you set it to false and disable the blinking.