ECMAScript 6
So far there are two buttons on the page: edit and delete. There will be more (add comment etc.). So, I would like to develop some general approach and not just operate on each button individually.
Each button is of class "custom-control" and should send an AJAX request.
Into the button tag I have included the information necessary for the request (url etc.):
<button id="main-object-delete" data-url="{{ object.get_delete_url }}" data-redirect="{{ object.get_delete_success_url }}" type="button" class="custom-main custom-control custom-delete btn btn-default " aria-label="Left Align">
The code:
$( document ).ready(function() {
class GeneralManager {
// Creates managers for each type of controls.
constructor() {
this.handle_buttons();
} // constructor
handle_buttons(){
let $button_list = $('.custom-control')
$button_list.each(function(index, button){
let button_manager = new ControlManager(button);
});
}
} // GeneralManager
function show_get_form(data, button, url, redirect){
let nest = button.closest(".custom-nest")
nest.innerHTML = data;
let act_cancel_manager = new SubmitCancelManager(url, redirect);
}
class ControlManager {
// Operates on main object only.
ajax_get(){
$.ajax({method: "GET",
url: self.url,
success: function(data){ show_get_form(data,
self.button,
self.url,
self.redirect); },
error: generalFail
});
} // ajax_get
constructor(button){
self = this; // Protection against binding "this" to something else.
this.button = button;
this.url = this.button.getAttribute("data-url")
this.redirect = this.button.getAttribute("data-redirect")
this.button.onclick = this.ajax_get;
} // constructor
}
let general_manager = new GeneralManager();
}); // $( document ).ready(function()
The idea was that for each button a new ControlManager object is created.
The problem is that both buttons trigger the request to the url for deletion. Delete button was the last of the two. If I change the order of the buttons, both buttons will send the request to the edit url.
Could you help me understand why my idea of assigning a separate instance of the ControlManager class to different buttons doesn't work. And how to cope with this problem most elegantly?
constructor(button){
self = this;
This creates a (=one) variable named self. So the second instance overwrites the value set by the first one.
ajax_get(){
$.ajax({method: "GET",
url: self.url,
Here you refer to the global variable self.
Forget self and use arrow functions where necessary:
ajax_get(){
$.ajax({method: "GET",
url: this.url,
success: data => show_get_form(data,
this.button,
this.url,
this.redirect),
Related
This is how i have created the button
<button type="submit" id="likebtn" onclick="likedpost(this) " data-postid="<%=blog._id%>" data-author="<%=user.username%>">Like</button>
<span ><p id="likescounter"><%=blog.likes%> </p></span> // I want to pass the reference of this p (id) when clicked the above button
This is my liked post function-
function likedpost(el)
{
var id = el.dataset.postid;
var author = el.dataset.author;
console.log("id form likedpost function =" +id+"and author of this post is="+author);
console.log('post liked');
var request = $.ajax({
type: "POST",
url: "api/index/like",
data: {id: id, author:author},
async:false,
dataType:"html",
success: function(data){
findingnumberoflikes(data);
console.log("action performed successfully")
},
error: function(){
console.log("error occured");
}
});
}
function findingnumberoflikes(data)
{
console.log("the data returned is "+ data);
$(#likescounter).html(data); // here I can not select the likescounter id
console.log("likes counter updated");
}
Basically, I want to select the respective p with jquery's $ selector and update its data but it is giving me private identifiers are not allowed outside class bodies error.
How can I do that. Thank you!
There is a list of above buttons and p tag are for every blog post in the databse. Sorry! earlier I couldn't frame my question better.
Although we can do this with sibling selector etc.. I suggest you to arrange this differently for example,
<span ><p id="likescounter"><%=blog.likes%> </p></span>
in this line, change id="likescounter" to something that would give likescounter+blogid
something that would replace like the following
<span ><p id="likescounter-<%=blog._id%>"><%=blog.likes%> </p></span>
you wanna get "id=likescounter-1" for blog id 1, id="likescounter-2" for blog id 2 etc...
so essentially the id will be unique for each with "likescounter-" and corresponding blogid combied with it.
then in the button click function, simply do this
function likedpost(el){
var id = el.dataset.postid;
// Here's the reference to the element
// with jquery syntax it would be
var likesCounter = $("#likescounter-"+id)
// for vanilla js it would be
var likesCounter = document.getElementById("likescounter-"+id)
...
}
In certain cases, usually after values have been cached from an initial search, I cannot get the mdb_autocomplete drop down to close on a single click. I often have to double click on my selection to get it to close. I am dynamically populating the values in the drop down via an Ajax call to a service method via and controller action method that searches the active directory for the first 30 name values. No call back occurs until a user enters at least 3 values into the mdb_autocomplete selection box. The callback and population of the drop down is working very well, but the click event following the selection of a value often will not close the dropdown. Additionally, we are being forced to use mdb boostrap over the normal bootstrap library. I have included the view, service, and typescript code that is being used to generate and populate the drop down.
View:
<div class="form-group">
<label class="font-weight-bold" for="RequestorSearchInput">Requestor</label>
<input type="text" id="RequestorSearchInput" name="RequestorSearchInput" class="form-control mdb-autocomplete" />
<span id="loading_data_icon"></span>
</div>
Service Method:
public List<string> GetRequestor(string aRequestor)
{
IEnumerable<Dictionary<string, string>> requestorNames;
using (ActiveDirectoryService service = new ActiveDirectoryService(<LDAP Domain Name>))
{
string[] propertiesToLoad = new string[] { "displayName" };
//requestorNames = service.SearchByPartialUsername(aRequestor, propertiesToLoad).Take(30).Union(service.SearchBySurname(aRequestor, propertiesToLoad).Take(30));
requestorNames = service.SearchBySurname(aRequestor, propertiesToLoad).Take(30);
}
return requestorNames.SelectMany(x => x.Values).Where(y => y.IndexOf(",", StringComparison.Ordinal) >= 0).Distinct().ToList();
}
TypeScript:
private handleRequestorSearch() {
let options: string[] = new Array();
// #ts-ignore
let searchval = $("#RequestorSearchInput").val().length;
if (searchval >= 3) {
$.ajax({
url: main.getControllerHREF("GetRequestor", "Search"),
method: "POST",
data: {
requestor: $("#RequestorSearchInput").val()
},
async: false,
dataType: "json",
success: function (names) {
$.each(names, function () {
options.push(this);
});
// #ts-ignore
$("#RequestorSearchInput").mdb_autocomplete({
data: options
}).select(function () {
$("#RequestorSearchInput").focus;
});
}
});
}
}
I am currently coding within a ViewComponent (ViewComponent1) view. Within this View, I have listed a few items:
As you can see, the channels 11, 12, 13 and 14 are clickable. Each channel has some additional information (OBIS, avtalsid.. etc). What I´m trying to do is to invoke ViewComponent2, within ViewComponent1, and pass along some of the data, based on the clicked item.
What I tried to do is to create another View called "Test" and within that View invoke ViewComponent2 along with its parameters, like this:
<div class="row">
<div class="col-md-2 canalstyle">
<a asp-controller="Customer" asp-action="Test" asp-route-pod="#item.STATION"
asp-route-idnr="#item.IDNR" asp-route-kanal="#item.KANAL" asp-route-start="#Model.start"
asp-route-end="#Model.end"> #Html.DisplayFor(modelItem => item.KANAL)</a>
</div>
</div>
This works, but this method redirects me away from my current View (ViewComponent 1). I don't want that. I want the current view to load the additional information from ViewComponent2.
My function that runs the ajax:
function myFunction() {
var data = JSON.stringify({
'idnr': id,
'start': this.start,
'end': this.end
});
$.ajax({
url: '#Url.Action("Test2","Customer")',
type: 'GET',
data: { idnr: id, start: this.start, end: this.end },
contentType: 'application/json',
success: handleData(data)
})
};
function handleData(data) {
alert(data);
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url, function (res) {
$target.html(res);
});
//do some stuff
}
And my Test2 Action:
public async Task<IActionResult> Test2(string idnr, string start, string end)
{
ServiceClient r2s = new R2S.ServiceClient();
R2S.Konstant[] kData = r2s.GetKonstantListAsync(new string[] { "IDNR" }, new string[] { idnr}).Result; // mätarnummer in... --> alla konstanter kopplade till denna.
return ViewComponent("MeterReader2", new { k = kData[0], start = start, end = end });
}
I am trying to target the same DOM.. Any ideas?
Your current code is rendering links (a tags) and normally clicking on a link will do a new GET request, which is what you are seeing , the redirect to the new action method.
If you do not want the redirect, but want to show the result of the second view component in same view, you should use ajax.
For example, If you want to show the result of second view component just below each link, you may add another html element for that. Here i am adding an empty div.
<div class="row">
<div class="col-md-2 canalstyle">
<a class="myClass" asp-controller="Customer" asp-action="DetailsVc"
asp-route-id="#item.Id" > #item.KANAL</a>
<div class="details"></div>
</div>
</div>
Here i just removed all those route params you had in your orignal question and replaced only with on param (id) . Assuming your items will have an Id property which is the unique id for the record(primary key) and using which you can get the entity (from a database or so) in your view component to get the details.
This will generate the link with css class myClass. You can see that, i used asp-action attribute value as "DetailsVc". We cannot directly use the view component name in the link tag helper as attribute value to generate the href value. So we should create a wrapper action method which returns your view component result such as below
public IActionResult DetailsVc(int id)
{
return ViewComponent("DetailsComponent", new { id =id });
}
Assuming your second view components name is DetailsComponent and it accepts an id param. Update the parameter list of this action method and view component as needed. (but i suggest passing just the unique Id value and get details in the server code again)
Now all you have to do is have some javascript code which listen to the click event on those a tags and prevent the normal behavior (redirect) and make an ajax call instead, use the ajax call result to update the details div next to the clicked link.
You can put this code in your main view (or in an external js file without the #section part)
#section Scripts
{
<script>
$(function() {
$("a.myClass").click(function(e) {
e.preventDefault();
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url,function(res) {
$target.html(res);
});
});
});
</script>
}
I built a plugin that's supposed to transform any button into a modal style form, given a url where the form can be fetched.
It works fine with only one element, but when the selector returns multiple elements, all buttons use the last element's data when the get & post methods are called inside the plugin.
I've tried several answered question in SO, but haven't been able to locate and fix the bug. Looks like I'm missing something.
Here's the complete code. You'll see some {% django tags %} and {{ django context variables }} but just ignore them.
Thanks!
A.
EDIT: typo; EDIT2: added html; EDIT3: removed django tags and context vars.
<div class="modal fade" id="modal-1234" data-uuid="1234">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="title-1234">Title</h4>
</div>
<div class="modal-body" id="body-1234">Body</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" id="cancel-1234">Close</button>
<button type="button" class="btn btn-primary" id="confirm-1234">Save changes</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
(function($){
// define the modalform class
function ModalForm($button){
/*
You can use ModalForm to automate the ajax-form-modal process with TB3.
Usage:
var mf = new ModaForm($('#my-button')); // that's it
*/
self = this;
self.$button = $button;
self.$modal = $('#modal-1234');
// get vars
self.target = self.$button.attr('data-target');
self.uuid = self.$modal.attr('data-uuid');
self.$modal_title = $('#title-' + self.uuid);
self.$modal_body = $('#body-' + self.uuid);
self.$modal_confirm = $('#confirm-' + self.uuid);
self.modal_confirm_original_text = self.$modal_confirm.html()
self.$modal_cancel = $('#cancel-' + self.uuid);
self.$alerts = $('[data-exsutils=push-alerts]').first();
self.$spinner = $('<p class="center"><i class="ace-icon fa fa-spinner fa-spin orange bigger-300"></i></p>');
// bind button click to _get
self.$button.on('click', function(e){
e.preventDefault();
self._get();
});
}
ModalForm.prototype._get = function(){
/*
Issue a get request to fetch form and either render form or push alert when complete.
*/
var self = this;
// show modal, push spinner and change title
self.$modal.modal('show');
self.$modal_body.html(self.$spinner);
self.title = typeof(
self.$button.attr('data-title')) !== 'undefined' ?
self.$button.attr('data-title') : 'Modal form';
self.$modal_title.html(self.title);
// get content
$.ajax({
type: 'GET',
url: self.target,
statusCode: {
403: function(data){
// close modal
// forbidden => close modal & push alert
setTimeout(function(){
self.$modal.modal('hide');
self.$alerts.html(data.responseText);
}, 500);
},
200: function(data){
// success => push form
// note that we will assign self.$form
var $response = $(data);
self.$form = $response.filter('form').first();
self.$modal_body.html($response);
self.$modal_confirm.click(function(e){
e.preventDefault();
self._submit(self.$form);
});
}
},
error: function(data){
console.log(data);
}
});
}
ModalForm.prototype._submit = function(){
/*
Post this.$form data and rerender form or push alert when complete.
*/
var self = this;
// change submit button to loading state
self.$modal_confirm.addClass('disabled').html('Loading...');
// issue pot request
// cleanup
// rebind if rerender or push alerts
$.ajax({
type: 'POST',
url: self.$form.attr('action'),
data: self.$form.serialize(),
statusCode: {
200: function(data){
// this is a form error
// so we must rerender and rebind form
// else we need to rerender and rebind
self.$form.remove();
var $response = $(data);
self.$form = $response.filter('form').first();
self.$modal_body.html($response);
self.$modal_confirm.on('click', function(e){
e.preventDefault();
self._submit(self.$form);
});
},
201: function(data){
// this means object was created
// so we must push an alert and clean up
self.$form.remove();
delete self.$form;
self.$modal.modal('hide');
self.$modal_body.html('');
// we will push alerts only if there is no 201 callback
var callback_201 = self.$button.attr('data-callback-201');
if (typeof(window[callback_201]) !== 'undefined') {
window[callback_201](data);
} else {
self.$alerts.prepend(data);
};
},
403: function(data){
// this means request was forbidden => clean up and push alert
self.$form.remove();
delete self.$form;
self.$modal.modal('hide');
self.$modal_body.html('');
self.$alerts.prepend(data.responseText);
}
},
complete: function(){
// reset button
self.$modal_confirm.removeClass('disabled').html(
self.modal_confirm_original_text);
}
});
}
window.ModalForm = ModalForm;
// define plugin
$.fn.modalForm = function(){
var self = this;
return self.each(function(){
var el = this;
var _ = new window.ModalForm($(el));
$.data(el, 'modalform', _);
});
}
// run plugin
$('[data-exsutils=modal-form]').modalForm();
})(jQuery);
</script>
Edit by #Daniel Arant:
A jsFiddle with a simplified, working version of the plugin code can be found here
Note by me: Please read the selected answer. This jsfiddle + adding var self = this will give you a complete picture of the problem and a good way around it.
The source of your problem is the line self = this in the ModalForm constructor. Since you did not use the keyword var before self, the JavaScripts interpreter thinks that self is a property of the global window object and declares it as such. Therefore, each time the ModalForm constructor is invoked, self takes on a new value, and all of the references to self in the event handlers created by the constructor for previous buttons then point to the new, most recent instance of ModalForm which has been assigned to the global self property.
In order to fix this particular problem, simply add the keyword var before self = this. This makes self a local variable rather than a global one, and the click event callback functions will point to their very own instance of ModalForm instead of the last instance that was assigned to self.
I created a working jsFiddle based on your code, which can be found here
I stripped down the plugin code to eliminate the ajax calls for the sake of simplicity. I also eliminated all of the uuid references. Once you get a reference to the modal as a jQuery object, you can use jQuery's .find() method to obtain references to the various components of the modal.
If you have any questions about my quick and dirty revision of your plugin
I have the skeleton of a chat page but am having issues tying it all together. What I'm trying to do is have messages sent to the server whenever the user clicks send, and also, for the messages shown to update every 3 seconds. Any insights, tips, or general comments would be much appreciated.
Issues right now:
When I fetch, I append the <ul class="messages"></ul> but don't want to reappend messages I've already fetched.
Make sure my chatSend is working correctly but if I run chatSend, then chatFetch, I don't retrieve the message I sent.
var input1 = document.getElementById('input1'), sendbutton = document.getElementById('sendbutton');
function IsEmpty(){
if (input1.value){
sendbutton.removeAttribute('disabled');
} else {
sendbutton.setAttribute('disabled', '');
}
}
input1.onkeyup = IsEmpty;
function chatFetch(){
$.ajax({
url: "https://api.parse.com/1/classes/chats",
dataType: "json",
method: "GET",
success: function(data){
$(".messages").clear();
for(var key in data) {
for(var i in data[key]){
console.log(data[key][i])
$(".messages").append("<li>"+data[key][i].text+"</li>");
}
}
}
})
}
function chatSend(){
$.ajax({
type: "POST",
url: "https://api.parse.com/1/classes/chats",
data: JSON.stringify({text: $('input1.draft').val()}),
success:function(message){
}
})
}
chatFetch();
$("#sendbutton").on('click',chatSend());
This seems like a pretty good project for Knockout.js, especially if you want to make sure you're not re-appending messages you've already sent. Since the library was meant in no small part for that sort of thing, I think it would make sense to leverage it to its full potential. So let's say that your API already takes care of limiting how many messages have come back, searching for the right messages, etc., and focus strictly on the UI. We can start with our Javascript view model of a chat message...
function IM(msg) {
var self = this;
self.username = ko.observable();
self.message = ko.observable();
self.timestamp = ko.observable();
}
This is taking a few liberties and assuming that you get back an IM object which has the name of the user sending the message, and the content, as well as a timestamp for the message. Probably not too far fetched to hope you have access to these data elements, right? Moving on to the large view model encapsulating your IMs...
function vm() {
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable(new IM());
self.setup = function () {
self.chatFetch();
self.message().username([user current username] || '');
};
self.chatFetch = function () {
$.getJSON("https://api.parse.com/1/classes/chats", function(results){
for(var key in data) {
// parse your incoming data to get whatever elements you
// can matching the IM view model here then assign it as
// per these examples as closely as possible
var im = new IM();
im.username(data[key][i].username || '');
im.message(data[key][i].message || '');
im.timestamp(data[key][i].message || '');
// the ([JSON data] || '') defaults the property to an
// empty strings so it fails gracefully when no data is
// available to assign to it
self.messages.push(im);
}
});
};
}
All right, so we have out Javascript models which will update the screen via bindings (more on that in a bit) and we're getting and populating data. But how do we update and send IMs? Well, remember that self.message object? We get to use it now.
function vm() {
// ... our setup and initial get code
self.chatSend = function () {
var data = {
'user': self.message().username(),
'text': self.message().message(),
'time': new Date()
};
$.post("https://api.parse.com/1/classes/chats", data, function(result) {
// do whatever you want with the results, if anything
});
// now we update our current messages and load new ones
self.chatFetch();
};
}
All right, so how do we keep track of all of this? Through the magic of bindings. Well, it's not magic, it's pretty intense Javascript inside Knockout.js that listens for changes and the updates the elements accordingly, but you don't have to worry about that. You can just worry about your HTML which should look like this...
<div id="chat">
<ul data-bind="foreach: messages">
<li>
<span data-bind="text: username"></span> :
<span data-bind="text: message"></span> [
<span data-bind="text: timestamp"></span> ]
</li>
</ul>
</div>
<div id="chatInput">
<input data-bind="value: message" type="text" placeholder="message..." />
<button data-bind="click: $root.chatSend()">Send</button>
<div>
Now for the final step to populate your bindings and keep them updated, is to call your view model and its methods...
$(document).ready(function () {
var imVM = new vm();
// perform your initial search and setup
imVM.setup();
// apply the bindings and hook it all together
ko.applyBindings(imVM.messages, $('#chat')[0]);
ko.applyBindings(imVM.message, $('#chatInput')[0]);
// and now update the form every three seconds
setInterval(function() { imVM.chatFetch(); }, 3000);
});
So this should give you a pretty decent start on a chat system in an HTML page. I'll leave the validation, styling, and prettifying as an exercise to the programmer...