Using the meteor.js framework, how can the value of a HTML element be selected in the "meteor way"? By using a jQuery-selector the browser would iterate through the DOM for every item, which is very expensive, wouldn't it?
The meteor tutorial uses a submit form and handles the template variable in a onSubmit-event. But how is it done if there is no onSubmit (and therefore no template-variable containing the element in question?
Could someone help out with the following example given, please?
cars.html
<template name="Car">
<div class="car-item" contenteditable="true">BMW</div>
<div class="edit-bar">save</div>
</template>
cars.js
'click .save'(event, template){
//access content of '.car-item' here when '.save' is clicked
}
You can use the template instance's jQuery. It will scope only the elements of the current template:
The template instance serves as the document root for the selector.
Only elements inside the template and its sub-templates can match
parts of the selector.
This results in a higher performance but requires you to control the granularity and scope of the elements to be searched.
Example of selector scopes
Just compare the output of the follwing example:
'click .save'(event, templateInstance){
//access content of '.car-item' here when '.save' is clicked
// global scope search
console.log($('div'));
// template scope search
console.log(templateInstance.$('div'));
}
Applied to your code
it results in the following code:
'click .save'(event, templateInstance){
// access content of '.car-item' here when '.save' is clicked
const carItems = templateInstance.$('.car-item');
// ... process car items
}
Try adding names to your divs
<div class="car-item" contenteditable="true" name="car">
Then in your click event:
click.save(event, template){
var car = event.target.car.value;
}
Let us know if it worked.
Related
On a project I'm working on, a HTML file is defining a Javascript template used on selection buttons. All buttons have a "Change..." label that I want to localize (set dynamically). In other cases I'm searching for the element ID and setting the InnerHTML accordingly. But in this case, the ID of the buttons are defined dynamically. Is it possible to have a text element inside the button element, search for this element, and set its InnerHTML value?
<script id="optionSelectionTemplate" type="text/x-handlebars-template">
<div class="sub-section option-selection">
{{#if name}}<h4>{{name}}</h4>{{/if}}
<div class="current"></div><button class="button" id="{{id}}" data-action-id="{{id}}">Change...</button>
</div>
</script>
I've been searching this for a while now. But given that my forte is not web development, I'm not really sure what to search for...
You may be able to get the button element(s) by its class instead; for example:
var x = document.getElementsByClassName("button");
As you suggested, you can improve your selection's precision by first getting the 'optionSelectionTemplate' element(s) like so:
var x = document.getElementById("optionSelectionTemplate").getElementsByClassName("button");
Or if you prefer:
var x = document.getElementById("optionSelectionTemplate").getElementsByTagName("button");
Here are some links for more on these method:
https://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
https://www.w3schools.com/jsref/met_document_getelementsbytagname.asp
Depending on how dynamic your localization should become, you could also specify the text inside a (locale-dependent) CSS as in https://jsfiddle.net/1gws5kat/ :
[HTML]
<button class="button btn_change" id="{{id}}" data-action-id="{{id}}"></button>
[CSS]
.btn_change:before { content: "Change..."; }
In particular when dealing with a large number of identically-named elements (i.e. many "Change" buttons), this might be pretty handy.
You find those btns by this command:
var btnlist= $(':button')
This Camano get you all button in your html file, then loop ton in and apply your changing.
Before call this command, jquery must be install.
how can I create in an AngularJS directive some DOM elements and set on them a click event? In my directive I create my elements in this way:
var list = document.createElement("div");
list.classList.add('myList');
for(var i = 0; i < n; i++) {
var item = document.createElement("div");
item.classList.add('myItem');
list.appendChild(item);
}
so I have an external div container that contains some div elements.
This is my generated HTML:
<div class="myList">
<div class="myItem">
<div class="myItem"></div>
<div class="myItem"></div>
<div class="myItem">
</div>
In the same directive I have to set a click event on those elements, in jQuery I can do:
$(".myItem" ).on( "click", function() {
// Do something
});
I try to that in Angular in many ways but I have problems to set the on click event:
var list = document.querySelector('.myList');
_.forEach(list.children, function(value, index){
var item = document.querySelector(value);
item.bind("click",function(){
// Do something
});
});
I get an error:
Failed to execute 'querySelector' on 'Document': '[object HTMLDivElement]' is not a valid selector.
Also, if I want get all myItem directly (without list.children) I write use:
var item = document.querySelector('.myItem');
I get:
item.bind is not a function (caused by "undefined")
I can set an ng-click in the directive... how?
item.on( "click", function() {
// Do something
});
If I use .on() method it's undefined like .bind().
Anyone can help me? Thanks in advice :)
I believe what you need is
<div class="myList">
<div class="myItem" ng-click="yourClickFn()">
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()">
Then in your Angular controller:
$scope.yourClickFn = function(){
//the code you want to execute here
}
Do you have a real reason to create your elements this way? It's really not the angular-way of doing things. You should use an ng-repeat on to create your html.
Also if you have a chance to update to anguar 1.5+ you could use components instead of directives, this would make your life easier.
Update
Alternatively you could do it with jQuery after the elements are created actually. I think it's easier to read than the plain js version.
Put a class on them and do something like:
$('.myClass').on('click', function() {
//your code
if (!$scope.$$phase) { // this checks whether you are already in a digest cycle or not - you probably won't be at this phase.
$scope.$apply(); // this will update the html if you did something to the model above this if
}
});
If you have a directive put this code to the link function, if you have a component then put it into the $postLink lifecycle hook (works after 1.5.3 I think) as these functions are called after your html has been already generated.
Usually these are the places for "messy" or "non-angular" code ^_^
My code is below: The question is:
How do I make it so that my jade file will render a description of the event based on the value of the select menu? Basically if the select option has a value of 1 then p should render p #{event[1].description}.
considering Jade precompiles I don't have access to the DOM which eliminates document.getElementbyId("test").selectedIndex; Below is my attempt at solving it on server side. I think I am missing something though because to me no matter the approach I will need access to the DOM to see the value change in the select menu to spit out the "event description".
Any help would be appreciated.
Jade File
extends layout
block content
form(role='form', action="/doSomthing", method="post")
select#test.form-control(name = "choices")
each events, i in event
option(value=i) #{events.event}
button.btn.btn-default(type='submit') Submit
a(href='/')
button.btn.btn-primary(type="button") Cancel
p#{events[0].description} //I want to pass an array instead of explicitly calling [0]
Route
router.get('/eventreg', function(req, res) {
var ei = event.eventid;
var eiarray = Event.findOne({'eventid': cc });
Event.find({}, function(err, event){
res.render('eventreg', {user : req.user, event: event});
console.log(event);
console.log(eiarray);
});
});
My thought was that I could create the looping array on server side so it would compile, but once the page has rendered and I change the selected option value it wouldn't recompile again on the fly.
Would React solve this problem? It seems like a bit overkill.
Thank you again for your time everyone.
You can add a piece of JavaScript to your page that then does the desired text changes in the browser. Add something like this to the bottom of your Jade page:
script.
$('#theOptionField').on('change', function(e) {
// get value that the user selected
// modify the HTML of the element that should display the value
});
I've built a page with 3 elements, each of which looks like this:
<div class="col-md-4 event-type">
<a href="{{ pathFor 'step2' }}" id="eventchoice" name="eventchoice" value="corporate">
</a>
</div>
I'm trying to pass the value or name or id of the the <a> element on to a collection using the following code:
EventsController.events({
'click #eventchoice' : function(event) {
console.log(event.target.getAttribute("id"));
console.log(event.target.getAttribute("name"));
console.log(event.target.getAttribute("value"));
var eventchoice = event.target.value;
var params = {
eventchoice: eventchoice
}
//Insert Event
Meteor.call('addEvent', params);
FlashMessages.sendSuccess('Event Added');
}
});
I added the console.log's to see if I can get the id/name/value of the <a> element, but the console outputs 'null' for all of these. Therefore, there is nothing to pass to the collection in the eventAdd method.
I don't believe the problem is with the EventsController, the addEvent method or the Events collection. Any ideas how I can pass these values through?
Thank you for your help!
I think there must be something wrong with your controller then, because if you check the Meteorpad here, it works just fine.
Although you might want to use a class instead of an id if you have many similar elements.
There are several ways of solving your problem but the way I consider as "The Meteor Way" is to use a separate template for every choice (or just use #each loop), if you do that your "this" inside the event code will contain the values you need in your scope, so you won't have to rely on the event.target for them.
I am using Template.registerHelper to register a helper that would given some boolean value will output either class1 or class2 but also some initial class if and only if it was the first time it was called for this specific DOM element.
Template.registerHelper('chooseClassWithInitial', function(bool, class_name1, class_name2, initial) {
var ifFirstTime = wasICalledAlreadyForThisDOMElement?;
return (ifFirstTime)?initial:"" + (bool)?class_name1:class_name2;
});
I am having a hard time figuring out how to know if the helper was called already for this specific form element.
If I could somehow get a reference to it, I could store a flag in the data attribute.
Using Template.instance() one can get to the "template" instance we are now rendering and with Template.instance().view to the Blaze.view instance, however, what if we have more than one html element inside our template ?
Oh, you are doing it in the wrong direction.
If you want to manipulate the DOM, you should do it directly in the template, not the jquery way ;)
0. Helper
html
<template name="foo">
<div data-something="{{dataAttributeValue}}"></div>
</template>
js
Template.foo.helpers({
dataAttributeValue: function() {
return 'some-value';
}
})
If you cannot avoid accessing the DOM from outside, then there is Template.onRendered(callback), callback will be called only once, when the template is rendered for the first time.
1. Component style
<template name="fadeInFadeOut">
<div class="fade">{{message}}</div>
</template>
Template.onRendered(function() {
// this.data is the data context you provide
var self = this,
ms = self.data.ms || 500;
self.$('div').addClass('in'); // self.$('div') will only select the div in that instance!
setTimeout(function() {
self.$('div').removeClass('in')
self.$('div').addClass('out')
}, ms );
});
Then you can use it somewhere else:
<div>
{{>fadeInFadeOut message="This message will fade out in 1000ms" ms=1000 }}
</div>
So you would have a reusable Component..
The way I solved it for now was to manually provide some kind of global identifier, unique to that item, this is hardly the proper way, if anyone has suggestions let me know.
let chooseClassWithInitialDataStore = {};
Template.registerHelper('chooseClassWithInitial', function(bool, class_name1, class_name2, initial, id) {
if(!chooseClassWithInitialDataStore[id]){
chooseClassWithInitialDataStore[id] = true;
return initial;
}
return (bool)?class_name1:class_name2;
});
To be used like:
<div class="ui warning message lowerLeftToast
{{chooseClassWithInitial haveUnsavedChanges
'animated bounceInLeft'
'animated bounceOutLeft'
'hidden' 'profile_changes_global_id'}}
">
Unsaved changes.
</div>
Regarding this specific usage: I want to class it as 'animated bounceInLeft' haveUnsavedChanges is true, 'animated bounceOutLeft' when its false, and when it is first rendered, class it as 'hidden' (that is, before any changes happen, so that it doesnt even display when rendered, thus, the need for the third option, however this isnt a question about CSS, but rather about Meteor templateHelpers).