calling a template helper function from inside a template event callback - javascript

I am very new to meteor and it is possible that I am going about this entirely incorrectly.
I have a simple template that represents a menu bar. When the user clicks an Icon, the menu is supposed to appear. When they click it again, it is supposed to disappear.
Here is the markup:
<template name="menu">
<div class="menu">
<div class="toggler">
<i class="fa fa-bars fa-3x"></i>
</div>
<div class="menu-body">
<!-- ... -->
</div>
</div>
</template>
Here is the JS that I have:
Template.menu.helpers({
self: Template.instance(),
menu_body: self.find('.menu-body'),
toggler: self.find('.toggler'),
currently_open: false,
open: function() {
menu_body.style.display = 'flex';
},
close: function() {
menu_body.style.display = 'none';
},
toggle: function() {
if(currently_open) close();
else open();
}
});
Template.menu.events({
'click .toggler': function(event, template) {
console.log(template);
template.toggle();
}
});
I thought the template instance would have access to the helper functions, but according to the log statement, this is what the template instance consists of:
B…e.TemplateInstance {view: B…e.View, data: null, firstNode: div.menu, lastNode: div.menu, _allSubsReadyDep: T…r.Dependency…}
_allSubsReady: false
_allSubsReadyDep: Tracker.Dependency
_subscriptionHandles: Object
data: null
firstNode: div.menu
lastNode: div.menu
view: Blaze.View
__proto__: Blaze.TemplateInstance
Can someone point me in the right direction here. Please feel free to be scrutinous if I am going about it wrong.

Helpers are for functional calls - not event driven works.
Meteor has an events handle that you can use to track events like clicks. Also you can use your css classes to define the styles nicely without programatically overwriting them.
Template.name.events({
'click .menuToggler': function(event, template) {
event.preventDefault();
var menu = template.find('.menu-body'); //(make this in ID!)
if($(menu).hasClass('menuOpen')) {
$(menu).removeClass('menuOpen');
//menu.style.display = 'none';
} else {
$(menu).addClass('menuOpen');
//menu.style.display = 'flex'; Use css to define these on the menuOpen class
}
});
Some things to note: This event handle assumes that your menu-body class is available under the template called "name" in my example. So you will want this event handler at the most top level template you have. It also assumes.

If you want to share state between the various components of your template (helpers, event callbacks etc) it should be done by setting properties directly on the template instances.
This can be done through the onCreated() callback
As per the documentation:
Callbacks added with this method [are] called before your template's logic
is evaluated for the first time. Inside a callback, this is the new
template instance object. Properties you set on this object will be
visible from the callbacks added with onRendered and onDestroyed
methods and from event handlers.
These callbacks fire once and are the first group of callbacks to
fire. Handling the created event is a useful way to set up values on
template instance that are read from template helpers using
Template.instance().
So, to provide a more relevant and concise example than the one in my original question.
Template.menu.onCreated(function() {
this.items = [
{title: 'Home', icon: 'home'},
{title: 'Profile', icon: 'user'},
{title: 'Work', icon: 'line-chart'},
{title: 'Contact', icon: 'phone'}
];
});
Template.menu.helpers({
items: function() {
var self = Template.instance();
return self.items;
}
});
Template.menu.events({
'click .toggler': function(event, template) {
console.log(template.items); //[Object,Object,Object,Object]
//do something with items
}
});

That's actually funny but I created a mini package that helps you do just that: https://atmospherejs.com/voidale/helpers-everywhere
But in your case it's not the right way of doing it. I can you see you want to add an display either flex or none it's better add CSS element like active that hold display: flex and add active or remove it on click like this: $('').addClass('active') or $().removeClass('active')
one liner can also work here: $('.menu-body').toggleClass('active')

Related

How to change dialog color dynamically? - Bootbox

To change the text in Bootbox dialog, I use <span id='someID'></span> and then use jQuery as follow: $("#someID").text("The new Text");
The problem I am facing is how to change the dialog color?
Inside my dialog I have the following (to set the dialog color):
className: "modal-danger nonumpad",
I want to change the class name to modal-success nonumpad when an action takes place.
Here is my code:
callDialog = bootbox.dialog({
className: "modal-danger nonumpad", // the class I want to change
closeButton: false,
animate: true,
title: 'Making Call',
message: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i><span id="test"> Waiting for Reply... </span></div>',
onEscape: null,
buttons: {
hangup: {
label: "<span id='hangup' <i class=\"fa fa-phone\"></i> Cancel </span>",
className: "btn-warning btn-lg pull-left",
callback: function(){
$("#dynamicMsg").text("This is dynamic msg");
return false;
}
}
}
});
callDialog.init(function(){
peer.on('connection', function(conn) {
conn.on('data', function(data){
// Will print 'hi!'
call = data;
console.log(call);
if(call == "ACCEPT"){
$("#test").text("This is dynamic msg");
$("#hangup").text("Hangup");
} else {
}
});
});
});
How can I change the className inside the init() function?
NOTE: <span id='someID'></span> doesn't work.
Since you have a reference to your dialog, something like this should work, using toggleClass:
callDialog.find('.modal-danger').toggleClass('modal-danger modal-success');
This will find the child element with the class modal-danger, and then remove it while adding the class modal-success.
Can you try:
$("#someID").removeClass('modal-danger').addClass('modal-success');
The someID will have to be the ID of your dialog. Or, if you have multiple or it's dynamic, reference a class name instead.
$(".someClass").removeClass('modal-danger').addClass('modal-success');
can you please guide what to replace someID with? because the way i am
creating the the dialog is different as it is shown in the question
with no ID
Answering your comment question, your should replace it with whatever individual selector you have for the element you want to change. If is just one element you sould think about giving it an ID. Depending on where you are calling that, you can define your selector by the event.currentTarget, or if at that moment there is only this element with those classes "modal-danger nonumpad", you can use it as a selector too.
Try this:
$(callDialog).removeClass('modal-danger').addClass('modal-success');
The $(callDialog) selects the variable that the bootbox dialog object was assigned to and turns it to a JQuery Object.
The .removeClass('modal-danger') is a JQuery method to remove the class passed as a parameter from the selected object.
The .addClass('modal-success') is also a JQuery method used to add the class passed as a parameter to the selected object.

Set Kendo UI Window values globally

I'm working with a lot of Kendo UI windows. Is there some way to specify default values somehow globally? Or maybe a more realistic version, can I create some parent with predefined values and then just overwrite the values I need to change?
For example, I want the same error behavior and a modal parameter for all of the windows, so I would like to do something like:
$("#parentWindow").kendoWindow({
modal: true,
error: function () {
this.close();
new Notification().error();
}
});
And then use the parent window as a base for new windows:
$("#newWindow").kendoWindow({
title: "This window should have the options (modal and error) of the parentWindow",
}).??getTheRestOfTheValuesFromParent()??;
Or rewrite some parameter:
$("#newWindow2").kendoWindow({
modal: false,
title: "A window with overwritten modal parameter",
}).??getTheRestOfTheValuesFromParent()??;
Is it somehow possible to achieve this, is there any possibility of something like C# inheritance?
Maybe it's a stupid question, but I'm not so familiar with JS.
I highly encourage you to create your own wrapper code over all - or at least those more complex - kendo widgets. My team has been doing it for years in a project we use kendo for everything and we are having very positivelly results. The main purpose is what you need: a global behaviour. If after thousand windows coded over your project, you need to change them all, just change the wrapper. It's just a simple jQuery function:
$.fn.MyWindow = function(options) {
var $target = $(this);
var widget = {
_defaultOptions: {
actions: ["Minimize", "Maximize", "Close"],
visible: false,
width: 400,
height: 400,
modal: true
},
_options: options || {},
_target: $target,
_widget: null,
_init: function() {
this._manageOptions();
this._createWidget();
return this;
},
_manageOptions: function() {
// Here you can perform some validations like displaying an error when a parameter is missing or whatever
this._options = $.extend(this._options, this._defaultOptions);
},
_createWidget: function() {
this._widget = this._target.kendoWindow(this._options).data("kendoWindow");
// Create here some behaviours that the widget doesn't haves, like closing the window when user click the black overlay
if (this._options.closeOnOverlayClick) {
$('body').off('click', '.k-overlay').on('click', '.k-overlay', function() {
this._widget.close();
}.bind(this));
}
},
Show: function(center) {
if (center) {
this._widget.center();
}
this._widget.open();
}
};
return widget._init();
};
var wnd = $("#wnd").MyWindow({
title: "My first window",
closeOnOverlayClick: true // Your own parameter
});
// Now you work with your own functions:
wnd.Show(true);
Demo.
There are so many customizations, like your own events - some of those kendo's widgets doesn't haves - etc..
I will just add that there is an article(here) about creating custom Kendo widgets where you can find more information about the specifics of different scenarios that may be implemented.
Ι had a case like yours with kendo windows, kendo grids and kendo dropdownlists. For that I created HtmlHelpers for all my elements and called them when I needed to. Since you are using kendo asp.net-mvc I would recommend to look at this way.
public static WindowBuilder GlobalKendoWindow(this HtmlHelper helper)
{
return helper.Kendo().Window()
.Draggable()
.Animation(true)
.Visible(false)
.AutoFocus(true)
.Modal(true)
.Scrollable(true)
.HtmlAttributes(new { #class = "atn-modal-container" })
.Actions(actions => actions.Minimize().Close())
.Deferred();
}
and render it in my Html like this
#(Html.GlobalKendoWindow()
.Name("addCandidateDialog")
.Title(Html.GetResource(cps, "AddCandidateDialogTitle"))
.LoadContentFrom("AddCandidate", "Candidate")
.Events(events => events.Open("athena.addCandidacy.onAddCandidateOpen").Close("athena.addCandidacy.onAddCandidateClose"))
)

Best way to hide 10000 dropdown menus

Context -
I have a chat component and each individual chat message has a dropdown.
And the dropdown menu is opened by clicking the "More Options icon"(3 dots).
Each individual chat message is a "backbone item view"
One solution is to listen to click on "body", loop through all the menus and then close the dropdown by removing a class on it.
$("body").on("click", function() {
$(".drop-down-menu").each(function(idx, item) {
$(item).removeClass("open"); // open class indicated it is open via CSS
});
});
The CSS -
.drop-down-menu {
visibility: hidden;
opacity: 0;
&.open {
opacity: 1;
visibility: visible;
}
}
Will there be any performance impact if there are 10,000 messages or more?
Hence, I am looking for the best solution to hide the drop down if user clicks anywhere on the screen.
Thanks.
You can make some trivial changes that should improve the performance of your code. The first thing is that there's no reason to loop like you are doing. jQuery objects are collections and jQuery operations usually loop over the elements of a jQuery object. So:
$("body").on("click", function() {
$(".drop-down-menu").removeClass("open");
});
This will automatically remove the class open from all elements matched by the selector ".drop-down-menu". jQuery will still go over a loop internally, but it is faster to let jQuery iterate by itself than to have .each call your own callback and then inside the callback create a new jQuery object on which to call .removeClass.
Furthermore, you logically know that removing the open class from elements that do not have this class is pointless. So you can narrow the operation to only those elements where removing open makes sense:
$("body").on("click", function() {
$(".drop-down-menu.open").removeClass("open");
});
These are principles that are widely applicable and that have trivial cost to implement. Anything more than this runs into the realm of optimizations that may have downsides, and should be supported by actually profiling your code. You could replace the jQuery code with code that only uses stock DOM calls but then if you need support for old browsers the cost of dealing with this and that quirk may not be worth it. And if you are using stock DOM methods, there are different approaches that may yield different performance increases, at the cost of code complexity.
Louis is offering a quick fix with efficient jQuery selectors.
For the long run, I would suggest making each message a MessageView component which has a ContextMenuView component. That way, each view only has one menu to take care of.
Catching clicks outside of an element
Then, use the following ClickOutside view as the context menu base view. It looks complicated, but it only wraps the blur and focus DOM events to know if you clicked outside the view.
It offers a simple onClickOutside callback for the view itself and a click:outside event which is triggered on the element.
The menu view now only has to implement the following:
var ContextMenuView = ClickOutside.extend({
toggle: function(val) {
this.$el.toggleClass("open", val);
this.focus(); // little requirement
},
// here's where the magic happens!
onClickOutside: function() {
this.$el.removeClass("open");
}
});
See the demo
var app = {};
(function() {
var $body = Backbone.$(document.body);
/**
* Backbone view mixin that enables the view to catch simulated
* "click:outside" events (or simple callback) by tracking the
* mouse and focusing the element.
*
* Additional information: Since the blur event is triggered on a mouse
* button pressed and the click is triggered on mouse button released, the
* blur callback gets called first which then listen for click event on the
* body to trigger the simulated outside click.
*/
var ClickOutside = app.ClickOutside = Backbone.View.extend({
events: {
"mouseleave": "_onMouseLeave",
"mouseenter": "_onMouseEnter",
"blur": "_onBlur",
},
/**
* Overwrite the default constructor to extends events.
*/
constructor: function() {
this.mouseInside = false;
var proto = ClickOutside.prototype;
this.events = _.extend({}, proto.events, this.events);
ClickOutside.__super__.constructor.apply(this, arguments);
this.clickOnceEventName = 'click.once' + this.cid;
},
/**
* Hijack this private method to ensure the element has
* the tabindex attribute and is ready to be used.
*/
_setElement: function(el) {
ClickOutside.__super__._setElement.apply(this, arguments);
var focusEl = this.focusEl;
if (focusEl && !this.$focusElem) {
this.$focusElem = focusEl;
if (!(focusEl instanceof Backbone.$)) {
this.$focusElem = Backbone.$(focusEl);
}
} else {
this.$focusElem = this.$el;
}
this.$focusElem.attr('tabindex', -1);
},
focus: function() {
this.$focusElem.focus();
},
unfocus: function() {
this.$focusElem.blur();
$body.off(this.clickOnceEventName);
},
isMouseInside: function() {
return this.mouseInside;
},
////////////////////////////
// private Event handlers //
////////////////////////////
onClickOutside: _.noop,
_onClickOutside: function(e) {
this.onClickOutside(e);
this.$focusElem.trigger("click:outside", e);
},
_onBlur: function(e) {
var $focusElem = this.$focusElem;
if (!this.isMouseInside() && $focusElem.is(':visible')) {
$body.one(this.clickOnceEventName, this._onClickOutside.bind(this));
} else {
$focusElem.focus(); // refocus on inside click
}
},
_onMouseEnter: function(e) {
this.mouseInside = true;
},
_onMouseLeave: function(e) {
this.mouseInside = false;
},
});
var DropdownView = app.Dropdown = ClickOutside.extend({
toggle: function(val) {
this.$el.toggle(val);
this.focus();
},
onClickOutside: function() {
this.$el.hide();
}
});
})();
var DemoView = Backbone.View.extend({
className: "demo-view",
template: $("#demo-template").html(),
events: {
"click .toggle": "onToggleClick",
},
initialize: function() {
this.dropdown = new app.Dropdown();
},
render: function() {
this.$el.html(this.template);
this.dropdown.setElement(this.$(".dropdown"));
return this;
},
onToggleClick: function() {
this.dropdown.toggle(true);
},
});
$("#app")
.append(new DemoView().render().el)
.append(new DemoView().render().el);
html,
body {
height: 100%;
width: 100%;
}
.demo-view {
position: relative;
margin-bottom: 10px;
}
.dropdown {
z-index: 2;
position: absolute;
top: 100%;
background-color: gray;
padding: 10px;
outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<div id="app"></div>
<script type="text/template" id="demo-template">
<button type="button" class="toggle">Toggle</button>
<div class="dropdown" style="display:none;">
This is a drop down menu.
</div>
</script>
Alternatives to detect a click outside an element
If you don't want, or can't use blur and focus events, take a look at How do I detect a click outside an element? for alternative techniques.
Lazy initialization of views
Another way to make an SPA more efficient is to delay the creation of new view to the very moment you need it. Instead a creating 10k context menu views, wait for the first time the user clicks on the toggle button and create a new view if it doesn't exist yet.
toggleMenu: function(){
var menuView = this.menuView;
if (!menuView) {
menuView = this.menuView = new ContextMenuView();
this.$('.dropdown').html(menuView.render().el);
}
menuView.toggle();
}
Pagination
Passed a certain threshold of HTML inside a webpage, the browser starts to lag and it impedes the user experience. Instead of dumping 10k views into a div, only show like a 100, or the minimum to cover the visible space.
Then, when scrolling to an edge (top or bottom), append or prepend new views on demand. Like the message list in any web-based chat app, like messenger.com.
Since you will only have one drop down menu open at a time, maybe you can keep a pointer to the element or index of the element it is attached to, instead of looping through all the menus.

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.

Where to place JQuery (set :visibility via CSS) in a BackBone View

I think this question ultimately boils down to where I should place the JQuery code, and I am unsure for backbone.js.
I have a nested div :
<div>Parent
<div class='pull-right remove-measure-btn'>Child</div>
</div>
and I want the child to only show when the parent is being hovered.
So I can use this code (top lines within the func() in the render() of the parent Backbone.View.extend:
render: function():
....
$(this.el).hover(
function() {
$('.remove-measure-btn').show();
// $('.remove-measure-btn').css('visibility' : 'visible');
},
function() {
$('.remove-measure-btn').hide();
// $('.remove-measure-btn').css('visibility' : 'hidden');
}
);
....
return this;
},
But this only toggles the display, and since I am using Bootstrap and taking advantage of .pull-right, I need to toggle CSS' :visibility, and not display: to keep the height of the child div in place when it is not visible. So if I use the second line from within the above code block, I get an undefined error, since the compiled template has been returned yet (i think....).
So where do I place the JQuery to change the CSS visibilty, or how do I change the rendering to accomodate the code where it is?
Notes:
There are many of these "parent" and "child" divs.
I am assuming it is best to use the selector with this.el to tie the interaction directly as opposed to using several global document.ready()s, but maybe I am not aware of a "safe/good" way to accomplish it using this method
As chcrist notes, the "Backbone" way of doing this is to use the events hash:
var MyView = Backbone.View.extend({
events: {
'mouseenter': 'showChild',
'mouseleave': 'hideChild'
},
render: function () {
//...
},
showChild: function () {
$('.remove-measure-btn').css({'visibility' : 'visible'});
},
hideChild: function () {
$('.remove-measure-btn').css({'visibility' : 'hidden'});
}
});
Also, I'm assuming this is a typo, but this code is wrong:
$('.remove-measure-btn').css('visibility' : 'hidden');
You can either pass an object (one or more style properties):
$('.remove-measure-btn').css({'visibility' : 'hidden'});
Or pass one property/value pair:
$('.remove-measure-btn').css('visibility', 'hidden');
This can be done with straight CSS. Javascript (backbone or otherwise) is not required.
Adding the following CSS to your page will get you exactly what you need without the use of javascript:
.remove-measure-btn {
visibility:hidden;
}
div:hover > .remove-measure-btn {
visibility:visible;
}
A fiddle showing this in action is here: http://jsfiddle.net/35TXY/
try $('remove-measure-button').addClass('hidden') and removeClass('hidden'). Define hidden:
.hidden {visibility: hidden;}

Categories

Resources