Refactor JS/jQuery code - javascript

Premise:
I am creating a page that contains many HTML options with relations like Product-> SubProduct-> Task. Task depends on SubProduct which in-turn depends on Product to Load.
I am repeating my code multiple times in my javascript.
Problem:
I have repeatedly tried to change this code, but I have a multitude of variables. I tried creating a function, but in vain since I am using $each
I accept any suggestions to refactor this code in any capacity.
Code:
$("#ProductId").change(function () {
$("#SubProductId").empty();
$("#TaskId").empty();
$("#SubProductId").append('<option value="0">[Select ...]</option>');
$.ajax({
type: 'POST',
url: urlGetSubProducts,
dataType: 'json',
data: { productId: $("#ProductId").val() },
success: function (data) {
$.each(data, function (i, data) {
$("#SubProductId").append('<option value="'
+ data.SubProductId + '">'
+ data.Name + '</option>');
});
ValidateFilledFields();
},
error: function (ex) {
alert('Fail to find subproduct.' + ex);
}
})
return false;
})
return false;
})
<div>
#Html.LabelFor(m => m.ProductId, "Product")
#Html.DropDownList("ProductId", null, htmlAttributes: new { #class = "form-control", required = "required" })
</div>
Notes:
ProductId is the property id from the HTML above

Proposal:
You can easily design your logic to follow a structured approach like so:
const getCountries = _ => new Promise((res,rej) => $.ajax({
...yourNormalConfigData,
success: res,
fail: rej,
})).then(d => d.countries) // Get contries from response
const getStates = country => new Promise((res,rej) => $.ajax({
url: `/foo/bar/get/States?country=${country}`,
...yourNormalConfigData,
success: res,
fail: rej,
}).then(d => d.states) // Get states from response
const getCities = state => new Promise((res,rej) => $.ajax({
url: `/foo/bar/get/Cities?state=${state}`,
...yourNormalConfigData,
success: res,
fail: rej,
}).then(d => d.cities) // Get cities from response
const injectOptions = elem => arr => {
const html = arr.map(c => `<option>${c}</option>`).join('')
elem.html(html)
}
const el = {
country: $('#Countries'),
state: $('#States'),
city: $('#Cities'),
}
getCountries().then(injectOptions(el.country))
el.country.change(e => {
getStates(el.country.val()).then(injectOptions(el.state))
})
el.state.change(e => {
getCities(el.state.val()).then(injectOptions(el.city))
})
<select id='Countries'>
</select>
<select id='States'>
</select>
<select id='Cities'>
</select>
Notes:
This is leveraging promises, and a ton of code re-use so that you can use the same components to load and configure your data dynamically. I tried not to deviate significantly from your code-base. It also features curried functions for a neater promise logic flow!

The only way to improve that you can make to your code as you have it, is to use string interpolation
$("#VersionId").append(`<option value="
${data.VersionId}">
${data.VersionName}</option>`);
Note: that is omitting the county->state -> city paragraph since is not related with the ajax
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

Related

How to record a user has been registered to an event - SQLite

I am trying to update an event within my calendar using https://fullcalendar.io/ to show if the current user is registered or not for that event to show that the event color will turn from green to red.
The below methods are within my service method.
public List<TimeTableEvent> GetAllEvents()
{
return db.TimeTableEvents.ToList();
}
public List<EventAttendance> GetAllMembershipEvents(int membershipId)
{
var events = GetAllEvents();
var attendances = db.Attendances.Where(m => m.MembershipId == membershipId);
var membershipEvents = events.Select(e => new EventAttendance
{
Event = e,
Attendance = attendances.FirstOrDefault(a => a.TimeTableEventId == e.Id)
}).ToList();
return membershipEvents;
}
public Attendance AddMembershipToEvent(int membershipId, int eventId)
{
var attendance = new Attendance { MembershipId = membershipId, TimeTableEventId = eventId,
AttendanceStatus = AttendanceStatus.Pending };
db.Attendances.Add(attendance);
db.SaveChanges();
return attendance;
}
I am then using this method within the controller
public JsonResult GetMembershipEvents(int membershipId)
{
var events = service.GetAllMembershipEvents(membershipId);
return Json(events);
}
And then I have a javascript method within my View. That is shown below.
function FetchEventsAndRenderCalendar() {
var userId = 1;//User.Claims.FirstOrDefault(c => c.Type == "Id");
events = []; // clear existing events
$.ajax({
type: "GET",
url: "/Timetable/GetMembershipEvents/" + userId,
success: function (data) {
$.each(data, function (i, json) {
events.push(
{
id: json.event.id,
title: json.event.title,
description: json.event.description,
start: moment(json.event.start),
end: moment(json.event.end),
color: json.isRegistered?"Red":"Green",
allDay: json.event.allDay
}
);
})
I am adding a user to an event so one of the events should be red when it complies. However, it is still showing as green.
Any help would be greatly appreciated.
You can try changing your function from this:
function FetchEventsAndRenderCalendar() {
var userId = 1;//User.Claims.FirstOrDefault(c => c.Type == "Id");
events = []; // clear existing events
$.ajax({
type: "GET",
url: "/Timetable/GetMembershipEvents/" + userId,
success: function (data) {
$.each(data, function (i, json) {
events.push(
{
id: json.event.id,
title: json.event.title,
description: json.event.description,
start: moment(json.event.start),
end: moment(json.event.end),
color: json.isRegistered?"Red":"Green",
allDay: json.event.allDay
}
);
})
to
// Function in which you use await should haveasync delaration
async function FetchEventsAndRenderCalendar() {
var userId = 1;//User.Claims.FirstOrDefault(c => c.Type == "Id");
events = []; // clear existing events
// await means wait for the call to finish and then proceed
const response = await fetch('http://www.example.com/Timetable/GetMembershipEvents/' + userId, {
method: 'GET',
headers: {"Content-Type": "application/json"}// Set required headers. Check Postman call you made.
});
console.log(response);
//Continue doing stuff with response
}
Lot of nodejs projects do this. Try this. If this does not work, try adding async: false in your ajax call.

How to unit test nested subscribe methods in Angular?

MethodToBeTested() {
this.serviceA.methodA1().subscribe((response) => {
if (response.Success) {
this.serviceA.methodA2().subscribe((res) => {
this.serviceB.methodB1();
})
}
});
}
Here is the scenario.
Things to test:
serviceA.methodA1(). was called.
if response.Success then check if serviceA.methodA2() was called
check if serviceB.methodB1() was called when serviceA.methodA2() received value.
first, one is easy to test.
let spy = spyOn(serviceA, 'methodA1');
expect(spy).toHaveBeenCalled();
But does one test 2 and 3?
let spy= spyOn(serviceA, 'methodA1').and.returnValue({subscribe: () => {success:true}});
subject.MethodToBeTested();
something like that?
Alright, so I figured out what I am looking for is callFake
it('should test inside of subscribe', () => {
let spy = spyOn(serviceA, 'methodA1').and.callFake(() => {
return of({ success: true });
});
let spy2 = spyOn(serviceA, 'methodA2').and.callFake(() => {
return of({ success: true });
});
let spy3 = spyOn(serviceB, 'methodB1').and.returnValue(of({ success: true }));
subject.MethodToBeTested();
expect(spy3).toHaveBeenCalled();
});
I learned that returnValue won't actually execute the inside of the subscribe while callFake will with the data you provide inside it.
It would be better to not use a nested subscribe.
Something like this could be a sollution:
let $obs1 = this.serviceA.methodA1().pipe(share());
let $obs2 = $obs1.pipe(switchMap(x => this.serviceA.methodA2()));
$obs1.subsribe(logic1 here...);
$obs2.subsribe(logic2 here...);

Concurrent requests with axios

In a React app I need to post data from a form. The post creates a new dashboard object. Once that's done, I need to immediately update a select dropdown in the component to include the newly added dashboard name. The axios documentation says it should be done like so:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
So this is what I've done:
class DashboardForm extends Component {
saveDashboard() {
var siteId = this.state.siteId;
var self= this;
return axios.post('/path/to/save/dashboard' + siteId + '/dashboards', {
slug: this.refs.dashboardUrl.value,
name: this.refs.dashboardName.value,
}).then(function (response) {
self.setState({
dashboardId: response.data.dashboardId,
dashboardName: response.data.dashboardName,
submitMessage: (<p>Successfully Created</p>)
});
self.setUrl(siteId, response.data.dashboardId);
}).catch(function (error) {
console.log(error);
self.setState({
submitMessage: (<p>Failed</p>)
});
});
}
getAllDashboards(){
var self = this;
self.setState({siteId: this.props.selectedSiteID});
var getDashboardsPath = "path/to/get/dashboards/" + self.props.selectedSiteID + "/dashboards";
axios(getDashboardsPath, {
credentials: 'include',
method: 'GET',
cache: 'no-cache'
}).then(function (response) {
return response.data.dashboards;
}).then(function (arrDashboards) { //populate options for the select
var options = [];
for (var i = 0; i < arrDashboards.length; i++) {
var option = arrDashboards[i];
options.push(
(<option className="dashboardOptions" key={option.dashboardId} value={option.slug}>{option.name}</option>)
);
}
self.setState({
options: options
});
});
}
saveAndRepopulate() {
axios.all([saveDashboard(), getAllDashboards()])
.then(axios.spread(function (savedDashboard, listOfDashboards) {
// Both requests are now complete
}));
}
}
The saveAndRepopulate is called when the form submits.
The problem is that I get the following errors:
error 'saveDashboard' is not defined no-undef
error 'getAllDashboards' is not defined no-undef
I've tried doing
function saveDashboard() {
but then I get
Syntax error: Unexpected token, expected ( (175:13)
|
| function saveDashboard() {
| ^
How do I call these two functions? Also, am I going to need to change the promise (.then) from the individual calls to the saveAndPopulate?
Many thanks for any guidance.
First, as #Jaromanda X pointed out, you should call your inside components functions with this, and you need to bind these functions to this. There are multiple ways to do that, one of then is to bind it inside the component constructor like:
this.saveDashboard = this.saveDashboard.bind(this)
Other good thing to do is to return the axios call inside the saveDashboard() and getAllDashboards()

How to sort array of data objects in React.js

I'm trying to implement a simple way to sort an array of data pulled from the server in React. The implementation I'm using shown below is probably not the best way to go about it, but I feel it should be working, but is not. It seems the sorting function is never being called, and the data is being displayed in the default order that it comes in from the server.
I'm trying to make a simple orderByRecent function reverse the order of the data, which is returned in chronological order. I know there are ways to accomplish this server-side but this is just an experiment for more complex client-side sorting.
sorting function:
orderByDate: (threads) => {
return threads.sort((a, b) => {
return Date.parse(a.date) > Date.parse(b.date)
})
},
Feed.js:
class ThreadList extends Component {
render() {
var threadNodes, sortedThreadNodes
if (this.props.data) {
var sorted = this.props.sortFunc(this.props.data)
var threadNodes = sorted.map(function (thread) {
return (
<Thread victim={ thread.victim }
date={ thread.date }
author={ thread.author }
ct={ thread.included.length }
likes={ thread.likes }
dislikes={ thread.dislikes }
id={ thread._id}
key={ thread._id }>
{ thread.text }
</Thread>
)
})
}
return (
<div className="threadList">
{ threadNodes }
</div>
)
}
}
var ThreadsBox = React.createClass({
handleThreadSubmit: function (thread) {
var threads = this.props.feed
var newThreads = threads.concat([thread])
this.setState({feed: newThreads})
$.ajax({
url: config.apiUrl + 'threads',
dataType: 'json',
type: 'POST',
data: thread,
xhrFields: {withCredentials: true},
success: function (data) {
// this.setState({feed: feed})
}.bind(this),
error: function (xhr, status, err) {
this.setState({data: threads})
console.log(this.url, status, err.toString())
}.bind(this)
})
},
// Feed nav buttons default to order by date
getInitialState: function () {
return {feed: [], sortFunc: helpers.orderByDate}
},
changeState: function (state, value) {
this.setState({[state]: value})
},
render: function () {
return (
<div className="threadsBox">
<ThreadForm friends={this.props.friends}
onThreadSubmit={ this.handleThreadSubmit }/>
<div className="feedNav">
<NavButtonList divId={"feedNav"}
eventFunc={this.changeState}
state={"sortFunc"}
buttons={buttonObs.mainNavButtons} />
</div>
<ThreadList data={ this.props.feed }
sortFunc={ this.state.sortFunc } />
</div>
)
}
})
module.exports = ThreadsBox
Here's a different approach.
Keep your threads in an object that maps threadId to the actual Thread data.
Keep a separate array that has the threadIds in the order that you want to sort them. When you sort your threads, you only change the ordering of the elements of the array.
When you want to sort the data differently, dispatch an action that will sort based on whatever constraints you have. Rendering the threads is as simple as performing a map over the array and getting the proper thread.

CanJS add custom MODEL method

I want to add another function to get result from a CanJs Model
i Have something like this:
VideomakerModel = can.Model({
id:'ID',
findAll: 'GET /videomakers/',
findNear: function( params ){
return $.post("/get_near_videomakers/",
{address:params.address},
undefined ,"json")
}
},{});
VideomakerModel.findNear({address : "Milan"}, function(videomakers) {
var myList = new VideomakerControl($('#accordionVm'), {
videomakers : videomakers,
view: 'videomakersList'
});
});
If I name the method findAll it works correctly,
otherwise naming it findNear it never reach the callback
should I extend MODEL is some way?? is there a way of add a function like FindAll?
thank you very much
CanJS only adds the conversion into a Model instance for the standard findOne, findAll etc. Model methods. You will have to do that yourself in your additional implementation by running the result through VideoMaker.model (for a single item) or VideoMaker.models (for multiple items):
VideomakerModel = can.Model({
id:'ID',
findAll: 'GET /videomakers/',
findNear: function( params ) {
var self = this;
return $.post("/get_near_videomakers/", {
address:params.address
}, undefined ,"json").then(function(data) {
return self.model(data);
});
}
},{});
If I understand the question, it is necessary to do so:
VideomakerModel = can.Model({
id:'ID',
findAll: 'GET /videomakers/'
},
{
findNear: function(options, success){
can.ajax({
url: "/get_near_videomakers/",
type: 'POST',
data: options,
success: success
})
}
})
var myList = new VideomakerControl({});
myList.findNear({address:params.address}, function(resulrRequest){
//success
} )

Categories