How to toggle svg properties using Raphaeljs and jQuery - javascript

I'm making a sidebar with sliding, accordion-like areas, so that when you click on a heading the associated content toggles visibility, as per the portion of code below. The initAccordion function also appends an svg "+" to the headings to indicate their action.
The final task is getting the svg held in the cross variable to rotate by 45 degrees on each click - which is where I'm having trouble.
Obviously, the two statements in the makeButtons function aren't accessible outside their containing function. I've tried to re-factor the code, but it ends up being a jumbled mess and I can't help thinking there must be a simple solution.
toggleContent : function(){
this.toggleClass('on').next().slideToggle(200);
cross.transform("r45");
},
makeButtons : function(el) {
var btn = Raphael(el,15,15);
var cross = btn.path(".....");
},
initAccoridon : function(){
$('#eventSideBar').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
sidebar.makeButtons(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this);
});
});
}
I'm not sure if this is better suited to the code-review section of SO - apologies if it is; I can move it on request.
Fiddle here...
Edit: I've managed to get part of the way there, although with multiple elements, I can't get the corresponding svg to rotate; just the last one. Fiddle here, and updated code:
makeSvg : function(el) {
this.btn = Raphael(el,15,15);
this.cross = this.btn.path(".....");
return {
btn : this.btn,
cross : this.cross
};
},
toggleContent : function(){
if (this.hasClass('on')) {
sidebar.cross.transform("r0");
} else {
sidebar.cross.transform("r45");
};
this.toggleClass('on');
},
initAccordion : function(){
$('body').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
sidebar.makeSvg(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this);
});
});
}

You will need to pass a parameter of the relevant element or capture it somehow. This example should work for multiple objects, you'll just need to add the reverse action.
jsfiddle
Relevant bit...
var sidebar = {
makeSvg : function(el) {
var btn = Raphael(el,15,15);
return btn.path(pathString).attr({fill:"#fff",stroke:"none"});
},
toggleContent : function( myCross ){
this.toggleClass('on');
myCross.transform("r45");
},
initAccordion : function(){
$('body').find('h3').each(function(){
var btn = $('<div/>', {
class : 'sideBarBtn'
});
btn.appendTo(this);
var btnContainer = btn.get(0),
$this = $(this);
var myCross = sidebar.makeSvg(btnContainer);
$this.on('click', function(){
sidebar.toggleContent.call($this, myCross);
});
});
}
};

Related

How to Add a popover in a specific sale order line column, in the xml view? Using One2ManyListView js inheritance?

I want to show a popover in a specific sales order line column, like shown in the first screenshot here : https://ibb.co/n0FP1Jf
I tried to show a popover by inheriting the Javascript class FieldOne2Many and One2ManyListView, this trick is working with FormView inheritence but the same code dosen't work with the One2ManyListView as showing in the code and the screenshot below:
CODE WORKING WITH "FormView" :
odoo.define("saleorderline_on_hover.ListView", function (require) {
"use strict";
var ListView = require("web.ListView");
var KanbanView = require("web_kanban.KanbanView");
var FormView = require("web.FormView");
var instance = openerp;
var core = require('web.core');
FormView.include({
do_show: function () {
if (this.dataset.model == 'sale.order'){
this.$el.find('tr').popover(
{
'content': function(e){
return '<p>SHOW INFORMATION HERE</p>'
},
'html': true,
'placement': function(c,s){
return $(s).position().top < 200?'bottom':'top'
},
'trigger': 'hover',
});
}
return this._super();
}
});
});
The Result of this code is the Popover shown in the screenshot here : https://ibb.co/r7bxSzq
THE SAME CODE NOT WORKING WITH "FieldOne2Many" and "One2ManyListView" :
odoo.define("saleorderline_on_hover.ListView", function (require) {
"use strict";
var ListView = require("web.ListView");
var KanbanView = require("web_kanban.KanbanView");
var FormView = require("web.FormView");
var instance = openerp;
var core = require('web.core');
var FieldOne2Many = core.form_widget_registry.get('one2many');
FieldOne2Many.include({
init: function() {
this._super.apply(this, arguments);
var One2ManyListView = this.x2many_views.list;
One2ManyListView.include({
do_show: function () {
this.x2m.$el.find('tr').popover({'content': function(e){
return '<p>SHOW INFORMATION HERE</p>'},
'html': true,
'placement': function(c,s){
return $(s).position().top < 200?'bottom':'top'
},
'trigger': 'hover',
});
}
return this._super();}
});
}
});
});
The problem here is that I can't find the "tr" or "td" element in "this.x2m.$el" by the method .find() or I'm missing something, can you please enlighten me on the subject, Thanks

Replacing text with jQuery goes wrong with multiple links

I am using the following code in a list of links to translate their title according to the language the user is pointing to. If the user is pointing, for example, to the Chinese language icon, the "article-title" gets replaced with its Chinese version. On mouse off, the title goes back to English. This works just fine.
Where I am running into problems: sometimes I need to add another link after the "article-title", on the same line. Whenever I do this, the script panics and flicks the title between blank and default, because it is finding a link, but it's not telling it to change anything.
How do I add exceptions to this script? How do I make it check to see if the link has the class "foo", and if it does, please just ignore it?
$.each($("li"), function(i, elements) {
var links = elements.getElementsByTagName("a");
var article_title = elements.getElementsByClassName("article-title")[0];
$.each(links, function(j, link) {
var previous_title = article_title.innerHTML;
link.addEventListener("mouseover", function() {
$(article_title).fadeTo(200, 0.5, function(){
article_title.innerHTML = link.title;
$(article_title).fadeTo(200, 1, function(){});
});
});
link.addEventListener("mouseout", function() {
$(article_title).fadeTo(300, 0.5, function(){
article_title.innerHTML = previous_title;
$(article_title).fadeTo(300, 1, function(){});
});
});
});
});
jQuery has a built in function called hasClass(className). Simply use that to see if the a element has that particular class.
$.each($("li"), function(i, elements) {
var links = elements.getElementsByTagName("a");
var article_title = elements.getElementsByClassName("article-title")[0];
$.each(links, function(j, link) {
var previous_title = article_title.innerHTML;
if (!$(link).hasClass("foo")) {
link.addEventListener("mouseover", function() {
$(article_title).fadeTo(200, 0.5, function(){
article_title.innerHTML = link.title;
$(article_title).fadeTo(200, 1, function(){});
});
});
link.addEventListener("mouseout", function() {
$(article_title).fadeTo(300, 0.5, function(){
article_title.innerHTML = previous_title;
$(article_title).fadeTo(300, 1, function(){});
});
});
}
});
});
Simply replace the class "foo" in the above example with the actual class name of links you wish to ignore.

Attach material.io menu component to all elements with same class

I'm trying to figure out a way to attach the material.io menu across a webpage that has multiple menus. I tried to use jQuery each function but obviously the code does not work. Here is the original code suggested by material.io (works for one menu only)
var menu_element = document.querySelector('.obox-admin-menu') !== null;
if (menu_element) {
var menuEl = document.querySelector('.obox-admin-menu');
var menu = new mdc.menu.MDCMenu(menuEl);
var menuButtonEl = document.querySelector('.obox-admin-menu-btn');
menuButtonEl.addEventListener('click', function() {
menu.open = !menu.open;
});
}
And here is my attempt with jQuery but it failed.
$( '.mdc-menu-anchor' ).each(function() {
var menuEl = $( this ).find('.obox-admin-menu');
var menu = new mdc.menu.MDCMenu(menuEl);
var menuButtonEl = $( this ).find('.obox-admin-menu-btn');
$( document ).on( 'click', menuButtonEl, 'click', function() {
menu.open = !menu.open;
});
});
What I'm doing wrong here? I'm trying to make menu trigger for multiple elements with same class and markup, I thought that could be possible with a loop but I'm new to javascript.
UPDATE
based on comment by connexo I have now added the functionality I am looking for with this (hope this code approach is fine)
var list = document.querySelectorAll( '.mdc-menu-anchor' );
Array.prototype.forEach.call(list, function (item) {
var menuEl = item.querySelector('.obox-admin-menu');
let menu = new mdc.menu.MDCMenu(menuEl);
let menuButtonEl = item.querySelector('.obox-admin-menu-btn');
menuButtonEl.addEventListener('click', function() {
menu.open = !menu.open;
});
});

Call Javascript Function with argument and use argument as a variable name

I've the following snip of a code:
var about = "about.html";
function loadPage(target){
$("#dashboard").load(target);
}
$(".nav li").click(function(){
loadPage($(this).attr("class"));
});
So when I click on a button like <li class="about">, target is = about.
But in that way, $("#dashboard").load(target); doesn't load the variable about which is the html-file which I want to load.
So how is it possible to call the variable in this way?
You seem to miss the .html part. Try with
$("#dashboard").load(target+'.html');
But, supposing you have only one class on your li element, you'd better use this.className rather than $(this).attr("class").
EDIT :
if you want to use your about variable, you may do this :
$("#dashboard").load(window[target]);
But it would thus be cleaner to have a map :
var pages = {
'about': 'about.html',
'home': 'welcome.jsp'
}
function loadPage(target){
$("#dashboard").load(pages[target]);
}
$(".nav li").click(function(){
loadPage(this.className);
});
A stupid answer : create a <a> tag, and set its href attribute to the correct value.
Otherwise :
A standard way to store key: values pairs in javascript is to use a plain object :
var urls = {};
urls['about'] = 'mysuperduperurlforabout.html';
function loadPage(target) {
var url = urls[target];
//maybe check if url is defined ?
$('#dashboard').load(url);
}
$(".nav li").click(function(){
loadPage($(this).attr("class") + ".html");
});
or
$("#dashboard").load(target+".html");
You can call the variables like this (if that's what you asked):
var test = 'we are here';
var x = 'test';
console.log(window[x]);
It's similar to the $$ in PHP. The output will be:
we are here in the console window.
You could put the "about" as an object or array reference similar to:
var pageReferences = [];
pageReferences["about"] = "about.html";
var otherReference = {
"about": "about.html"
};
function loadPage(target) {
alert(pageReferences[target]);
alert(otherReference[target]);
$("#dashboard").load(target);
}
$(".nav li").click(function () {
loadPage($(this).attr("class"));
});
Both of these alerts will alert "about.html" referencing the appropriate objects.
EDIT: IF you wished to populate the object based on markup you could do:
var otherReference = {};
$(document).ready(function () {
$('.nav').find('li').each(function () {
var me = $(this).attr('class');
otherReference[me] = me + ".html";
});
});
You could even store the extension in an additional attribute:
var otherReference = {};
$(document).ready(function () {
$('.nav').find('li').each(function () {
var me = $(this).attr('class');
otherReference[me] = me + "." + $(this).attr("extension");
});
});
Better would be to simply put the page reference in a data element:
<li class="myli" data-pagetoload="about.html">Howdy</li>
$(".nav li").click(function () {
loadPage($(this).data("pagetoload"));
});

Unintuitive removeClass() problem

I'm using this flip plugin, see the code in this fiddle. The goal is to flip one box at a time, e.g. the second box clicked should revertFlip() the previous one. During the animation I don't want the other boxes to be clickable. I noted that the removeClass() is not working.
<div class='flippable'>I'm unflipped 1</div>
...
<div class='flippable'>I'm unflipped 9</div>
$('.flippable:not(.reverted)').live('click',function()
{
var $this = $(this);
var $prevFlip = $('.reverted');
var $allBoxes = $('.flippable');
$this.flip(
{
direction: 'lr',
color: '#82BD2E',
content: 'now flipped',
onBefore: function()
{
$prevFlip.revertFlip();
$prevFlip.removeClass('reverted');
},
onAnimation: function ()
{
$allBoxes.preventDefault();
},
onEnd: function()
{
$this.addClass('reverted');
}
})
return false;
});
I'll appreciate a lot your advise and suggestions.
Edit:
Error Console Output: $allBoxes.preventDefault is not a function
I believe this has something to do with revertFlip() calling onBefore and onEnd. This is causing some weirdness with addClass and removeClass. Check out my modified example: http://jsfiddle.net/andrewwhitaker/7cysr/.
You'll see if you open up FireBug that onBefore and onEnd are called multiple times, with I think is having the following effect (I haven't exactly worked out what's going on):
The call to onEnd for the normal "flip" sets reverted class for the desired element.
The call to onEnd for the "revert flip" action sets the same element as above again when onEnd is called.
Here's a workaround. I've taken out using onBegin and simplified onEnd since I'm not exactly sure what's going on with the revertFlip() call:
$(function() {
var handlerEnabled = true;
$('.flippable:not(.reverted)').live('click', function() {
if (handlerEnabled) {
var $this = $(this);
var $prevFlip = $('.reverted');
var $allBoxes = $('.flippable');
handlerEnabled = false;
$prevFlip.revertFlip();
$prevFlip.removeClass("reverted");
$this.addClass("reverted");
$this.flip({
direction: 'lr',
color: '#82BD2E',
content: 'now flipped',
onEnd: function() {
handlerEnabled = true;
}
});
}
return false;
});
})
I'm using a boolean flag to enable and disable the event listener. Try out this example: http://jsfiddle.net/andrewwhitaker/bX9pb/. It should work as you described in your OP (only flipping one over at a time).
Your original code ($allBoxes.preventDefault()) is invalid, because $allBoxes is a collection of elements. preventDefault is a function on the jQuery event object.
Can you try this script
var $prevFlip;
$('.flippable:not(.reverted)').live('click',function() {
var $this = $(this);
var $allBoxes = $('.flippable');
$this.flip( {
direction: 'lr',
color: '#82BD2E',
content: 'now flipped',
onBefore: function() {
if($prevFlip){
$prevFlip.revertFlip();
$prevFlip.removeClass('reverted');
}
},
onAnimation: function () {
//$allBoxes.preventDefault();
//This is not a valid line
},
onEnd: function() {
$this.addClass('reverted');
$prevFlip = $this;
}
});
return false;
});
This reverts only one previous item. This is not a complete solution. I think there are more problems to this. I'll post any further updates if I found them.
I think #Andrew got the answer you are looking for.
You can filter $this out of $allBoxes by using the not() method.
$allBoxes.not($this).revertFlip();

Categories