How to associate buttons with new objects (object nesting) in Javascript - javascript

So I need something similar to Facebook Messenger's gear button which can be seen only when a conversation box is hovered or is currently active (just to give an image to these words), but how can I associate the button to that object? The object nesting is really confusing me. This is what mine looks like right now.
table_data {
item_1: { fruits: [ {}, ..., {} ]},
item_2: { fruits: [ {}, ..., {} ]},
...
...
item_n: { fruits: [ {}, ..., {} ]},
}
Each item is a div that has 3 buttons:
Delete - deletes item and all content
Options - opens popover options menu
Expand - expands current item to show fruits array of objects
The user enters in a new item and then has the option to add objects to fruits array.
document.getElementById("save").addEventListener("click", function() {
table_data[newItem] = { fruits:[] };
appendHTML(newItem);
// helper function that appends HTML
}
So under options, user can "Add Fruits". How can I associate that "Add Fruits" to the correct item since the "Options" button is just appended HTML.
If it helps, the helper function looks like this.
function appendHTML(item) {
$("#list").append('<li class="enter"><div class="listContent"><span class="itemName">'+item+'</span></a></div></li>');
}
If anyone happens to need an answer to this in the future...
$(document).on('click', "#list > li > .listContent > .gear", function() {
$(this).next().slideToggle();
});

This is a duplicate of this question. There, you will find multiple answers. I think the best would probably be storing the element in a var and then you can easily target it with whatever you want after you append it

Related

What is the best or better implementation (using event handler) for this situation?

I want to set a click event for each list like this:
Example 0. use an inline event handlers
<ul class="fruits">
<li class="apple" onclick="show(apple)">Apple</li>
<li class="orange" onclick="show(orange)">Orange</li>
<li class="cherry" onclick="show(cherry)">Cherry</li>
<li class="grape" onclick="show(grape)">Grape</li>
<li class="kiwi" onclick="show(kiwi)">Kiwi</li>
</ul>
As shown above, Inline event handlers, is simple and intuitive, but it's deprecated. so I will use addEventListener method. However, the latter is many different ways to write and I don't know the best.
Here are a few writing ideas I've come up with.
Example 1. Get the arguments value from class name or data-attribute.
const fruits = document.querySelectorAll(".fruits> li");
fruits.forEach((fruit) => {
fruit.addEventListener("click", function () {
// use the class name for matching with arguments. more option is data-attributes.
const fruitName = fruit.className;
show(fruitName);
});
});
Example 2. Get the arguments value from a list in JS.
const fruitList = ["apple", "orange", "cherry", "grape", "kiwi"];
for (let fruit of fruitList) {
let fruitElem = document.querySelector(`.fruits> li.${fruit}`);
fruitElem.addEventListener("click", function(){
show(fruit)
});
}
Example 3. Get the arguments value from a list, more complex than EX2 for future management, in JS.
const fruitList = [
{event: "click", key: "apple", func: show},
{event: "click", key: "orange", func: show},
{event: "click", key: "cherry", func: show},
{event: "click", key: "grape", func: show},
{event: "click", key: "kiwi", func: show},
]
for (let fruit of fruitList) {
let fruitElem = document.querySelector(`.fruits> li.${fruit.key}`);
fruitElem.addEventListener(fruit.event, function(){
fruit.func(fruit.key);
});
}
What I would like to ask is:
Which is the best way to do this (EX0-3)?
If it was you, how to implement this?
Please teach me.
A combination of data attributes, which are designed to attach information to HTML elements that is not related to layout or presentation, and event delegation to avoid multiple event handlers to do the same thing is an alternative way of handling this design.
To illustrate with the posted example:
"use strict";
function show( fruit) {
console.log("show(%s)", fruit);
}
const fruitList = document.getElementById("fruitList");
fruitList.addEventListener("click",
event => {
let li = event.target.closest('li');
if( li && (li.parentNode === fruitList)) {
show( li.dataset.fruit)
}
}
);
li {
cursor: pointer;
margin-bottom: 0.5em;
}
<ul id="fruitList">
<li data-fruit = "apple">Apple</li>
<li data-fruit ="orange">Orange</li>
<li data-fruit ="cherry"><em>Cherry</em></li>
<li data-fruit ="grape">Grape</li>
<li data-fruit ="kiwi">Kiwi</li>
</ul>
Because one answer rarely fits all situations, if the class attributes are actually used to style layout using rules in a style sheet, then they might serve as a substitute for data attributes as a way of determining what fruit to show.
Updated to ignore clicks that are not on list items (or child elements thereof) within the list being monitored.
Clicks to ignore include
Clicks within list bullet areas,
Clicks In padding or borders around li elements if the default CSS box-sizing value is in effect,
Clicks in margins set on list elements
Clicks that are within a list-item but the list item belongs to an outer list within which the monitored list is nested.
The click handler also allows clicking on child elements of list items that do not set event.target - see the em tags around "Cherry" for testing.

Summernote - Custom dropdown for ordered and unordered list

I am using Summernote editor v0.8.9 for quite a long time. I have created a custom dropdown button for Ordered List and Unordered List by using below code
let orderedList = function (context)
{
let ui = $.summernote.ui;
// create button
let button = ui.buttonGroup([
ui.button({
className: 'dropdown-toggle',
contents: '<i class="fa fa-list-ol"/><span class="note-icon-caret"></span>',
container: false,
tooltip: 'Ordered List',
data: {
toggle: 'dropdown'
}
}),
ui.dropdown({
className: 'dropdown-style',
contents: "<ol style='list-style-type:none' class='ordered-list'><li data-value='1'>1</li><li data-value='1' style='display: none;'>1)</li><li data-value='I'>I</li><li data-value='A'>A</li><li data-value='a)' style='display: none;'>a)</li><li data-value='a'>a</li><li data-value='i'>i</li></ol>",
callback: function ($dropdown) {
$dropdown.find('li').each(function () {
$(this).click(function() {
selectedListType = orderedListMap[$(this).attr('data-value')];
$(context).summernote('editor.insertOrderedList');
});
});
}
})
]);
return button.render(); // return button as jquery object
};
And also I get the dropdown attached to the toolbar as shown in the image
I have changed some code in the summernote.js to change the list style type after clicking on the dropdown item.
I am adding following code in the Bullet.prototype.wrapList method as follows
//for bullets and numbering style
if (selectedListType != 'NA') {
listNode.setAttribute('style', 'list-style-type:' + selectedListType);
}
I have also added the following code in the method "function replace(node, nodeName)" of "dom" object.
//for bullets and numbering style
if ((nodeName == 'OL' || nodeName == 'UL' ) && selectedListType != 'NA') {
$(newNode).css('list-style-type', selectedListType);
}
When I click on the dropdown item I am calling below code.
$(context).summernote('editor.insertOrderedList');
At first instance everything is working fine. I can change ordered list to unordered list as well as to other types of lists. But the problem arises when I am trying to create a new list. When I try to create a new list below the existing list (note : after double entering new line, existing list closes, hence a new list is created after double entering new line),
the focus does not stay on the current line. Instead it goes to the old list and old list style (Ordered/unordered) is getting changed.
I have also kept the default UL/OL in the toolbar for deugging and I can see that the document.selection() in the method WrappedRange.prototype.nativeRange is giving proper selection for default UL/OL but is giving wrong selection for my dropdown UL/OL.
Please help.
Let me know if any info is needed from my side
I solved this problem.
The problem was with the custom dropdown I created.
The dropdown needs to be in the following structure in the 'contents' attribute inside 'ui.dropdown'
<li>
</li>
<li>
</li>
'a' tag inside 'li' tag

Can't make multiple inners draggable object from angular 5 & dragula

I'm trying since few days and can't make it works...
Little explanation :
I've in this example, an array of object like this :
public containers: Array<object> = [
{
"name": "container 1",
"items": ["1", "2", "3"]
},
{
"name": "container 2",
"items": ["4", "5"]
}
];
Where each object have an array of string.
I'm generating divs to make these object moves (Containers AND Items).
Now, i'm getting something like this :
Where red box is the main div, blacks boxes are containers, and blue boxes are items.
with this html :
<div class="feuille-drag" [dragula]='"containers"' [dragulaModel]='containers'>
<div *ngFor="let container of containers" class="drag-container" [dragula]='"blocks"'[dragulaModel]='container.items'>
<span class="handle">O</span>
<div *ngFor="let item of container.items" class="drag-bloc">
<span class="handleF">X</span>
{{item}}
</div>
</div>
And this typescript, where I only set fews options :
dragulaService.setOptions('containers', {
revertOnSpill: true,
copy: false,
moves: function (el, container, cHandle) {
return cHandle.className === 'handle';
}
});
dragulaService.setOptions('blocks', {
revertOnSpill: true,
copy: false,
moves: function (el, container, bHandle) {
return bHandle.className == 'handleF';
}
});
If you looks well, you can see in the screenshot that, there is an empty blue box. It wasn't here at the beginning, I only dropped a blue box into another one, and it created an undefined element into my object Containers.
An other problem :
If I move a blue box into an other black box (container), the blue box will disappear and an other blue box will move instead.
Example : If I move the blue box 1 into the container 2, the box 1 will disappears, and the box 2 will go into the container 2.
BUT It will not be deleted into the object Containers :
End, last thing, handle elements from containers (O) are being read like draggable object from dragula.
Its maybe just a css problem, but i'm not sure so...
I'm using Angular CLI, Angular 5, Dragula, and i'm pretty new on Angular, (I still was on AngularJS sometimes ago).
I hope it's well explained, hope someone can help me, and I'm sorry if there is already an answer about it, I didn't find it !
Have a nice day !
UPDATE
See this stackbliz
There is one html element that breaks your structure:
<span class="handle">O</span>
ng2-dragula gets wrong index when handles drop event
drake.on('drop', (dropElm: any, target: any, source: any) => {
if (!drake.models || !target) {
return;
}
dropIndex = this.domIndexOf(dropElm, target);
https://github.com/valor-software/ng2-dragula/blob/master/src/components/dragula.provider.ts#L78
target here is your div.drag-container that includes container.items.length + 1 elements.
After that new undefined element is added to your container.items,
To fix it I would suggest you wrapping dragging elements in its own container like:
...
<span class="handle">O</span>
<div [dragula]='"blocks"' [dragulaModel]='container.items'>
<div *ngFor="let item of container.items;" class="drag-bloc">
<span class="handleF">X</span> {{item}}
</div>
</div>
Forked stackblitz example

Displaying content based on radio button selection

I’m pretty new to backbonejs and i’m trying to create a basic application.
The application is something like this:
I have 5 sections: A, B, C, D and E
Each section has 2 radio buttons.
Section A - Radio1, Radio2
Section B - Radio3, Radio4
Section C - Radio5, Radio6
Section D - Radio7, Radio8
Section E - Radio9, Radio10
Depending on what radio button is selected, I need to display a section (previous sections must also display)
I have had a look at maybe using a model to determine which radio was selected and also what section is displayed. Is this the correct approach?
var State = Backbone.Model.extend({
defaults: {
id: null,
name: "",
isOn: false
}
});
var Section = Backbone.View.extend({
model: State,
events: {
'change [type="checkbox"]': function (event) {
var $checkbox = $(event.target);
this.model.set("isOn", $checkbox.is(":checked"));
this.model.get("dispatcher").trigger("toggle", this.model.get("id"));
}
},
initialize: function (options) {
this.listenTo(this.model, "change:isOn", function (model, isOn) {
if ( isOn ) {
this.$el.find(".section").show();
this.$el.find("input").prop("checked", true);
}
else {
this.$el.find(".section").hide();
this.$el.find("input").prop("checked", false);
}
});
this.listenTo(dispatcher, "toggle", function (id) {
if ( this.model.get("id") < id ) {
this.model.set("isOn", true);
}
if ( this.model.get("id") > id ) {
this.model.set("isOn", false);
}
});
},
render: function () {
this.$el.html('<div>' + this.model.get("name") + '</div><input type="checkbox"><div class="section" style="min-height:100px; background-color:grey; display:none;"></div>');
this.$el.appendTo("body");
}
});
var dispatcher = _.extend({}, Backbone.Events);
_.each([
{id: 1, name: "A", isOn: false, dispatcher: dispatcher},
{id: 2, name: "B", isOn: false, dispatcher: dispatcher},
{id: 3, name: "C", isOn: false, dispatcher: dispatcher},
{id: 4, name: "D", isOn: false, dispatcher: dispatcher},
{id: 5, name: "E", isOn: false, dispatcher: dispatcher}
], function (item) {
var view = new Section({model: new State(item)});
view.render();
});
I didn't understand the meaning of two radio.. so I used checkbox per section.. Hope this will uncover some basics of backbone.
Yes this approach will work. I recommend if you need to communicate between views to use models via events - this will generally result in better architecture (your views will be more decoupled).
You can react to the change event in the view (using the events hash) and update an attribute on a model for each group, e.g. this.model.set('termsAccepted', true/false) then as long as the other view(s) have access to this model you can react to the change event of that attribute, e.g. this.listenTo(this.model, 'change:termsAccepted', this.onTermsAcceptedChange).
There may be a very simple solution to your objective. If you monitor the radio-button state via a javascript function, then you can use that function to change a class statement for the parent div. In CSS, you can then define actions to hide or display a div based on it's class. This would only take a few lines of javascript and a few lines of CSS.
For example, this example in codepen shows a way to accomplish the hide/show for a variably sized div. The number of divs in this approach is arbitrary - you can have as many or few as you like, and the only action required of the user is to click on the header to expand or collapse the associated div. In this example, clicking on the header acts as a toggle to expand or collapse the associated div. The example is set up so that only one div is expanded at a time and clicking on a different header automatically collapses any open div so that only one div is open at a time. If you do not want that behavior, just remove the for loop in the accOff() function.
It's kinda like a choose-your-own-adventure, ya sipher_z?
I advise using the Backbone Router with route parameters storing the current state, that is, which section is currently showing.
Each component of the view should be a Backbone.View.extend({...}). Components might be Section and Radio.
On each Radio button, in HTML, put a data-go-to attribute, with a value of the section to go to next. Then, in your RadioView code, put a click event. When clicked, extract this data-go-to, and do something like location.hash = '/section/' + section to trigger your router.
Then, all your router does is hide all the Sections except the selected one whenever triggered. If there's no selection, it just shows the first one!
I'm not 100% sure of this strategy, but this is definitely "the Backbone way". Let me know if I can clear anything up.

How to get an object by clicking a button on the page?

I have that web app I am working on.
Here is a picture.
The idea is that there is an array of objects, it gets displayed as a row of divs. Each of these objects also has an array of objects inside them and they are displayed as a row of divs inside these divs.
The user should be able to add new objects to the objects listed on the page. That should be accessed by pressing "Edit".
The problem: How do I make the script grab the objects from that div that was clicked and display them in a window?
I am using meteor bootstrap package.
Ask me questions if you didn't understand something - that stuff is like a maze.
Javascript
var state = 0;
var
sides = [
{
name:"MURICA",
types:[
{
name:"EAGLE",
desc:"FREEDUM",
power:10
}
]
}
];
if (Meteor.isClient) {
Template.global.side = function(){
var obj = [], m;
m = 1;
for (var i in sides){
obj.push({
index : m,
object : sides[i]
});
}
console.log(JSON.stringify(obj));
return obj;
}
Template.content.state = function(){
return state;
}
Template.global.events ({
'click .edit': function(){
jQuery('#edit').toggle()
console.log("PRESSED FREEDOM");
}
});
}
HTML can be found here (was too big to post) http://pastebin.com/kmNnSV1w
This may be helpful:
html
<template name="asdf">
<div class="asdf-box" data-nameOfTheParam="{{putSomeValueHere}}">
<span class="asdf-button">Click me!</span>
</div>
</template>
js
Template.asdf.events({
'click .asdf-button': function(e) {
console.log( $(e.target).closest('.asdf-box').data('nameOfTheParam') );
}
});
When working with database items, you may just use the id: data-id={{_id}} and fetch the desired object in the callback based on this id.

Categories

Resources