AngularJS Voting System - Preventing Multi-Votes - javascript

I'm trying to create a voting system using AngularJS with the Ionic Framework. I'm using ng-repeat to loop over a series of user posts from a JSON array. Each post has an upvote and a downvote button. After one is selected, I have the button on that post disabled. The problem I'm currently experiencing is keeping that button disabled regardless if the user reopens the app. My goal is to prevent multi-voting. Currently, I'm using this method to disable the button after it's been clicked:
<button class="button button-small button-light" ng-click="upThread({{thread.id}})" ng-disabled="upDisabled[thread.id]">
<i class="icon ion-thumbsup"></i>
</button>
<button class="button button-small button-light" ng-click="downThread({{thread.id}})" ng-disabled="downDisabled[thread.id]">
<i class="icon ion-thumbsdown"></i>
</button>
$scope.upDisabled = {};
$scope.downDisabled = {};
$scope.upThread = function(id) {
...
$scope.upDisabled[id] = true;
$scope.downDisabled[id] = false;
}
$scope.downThread = function(id) {
...
$scope.downDisabled[id] = true;
$scope.upDisabled[id] = false;
}`
And this works great for disabling the buttons. Now, the problem is, I require Cache for the controller to be false, otherwise my new posts do not update in the view when added. This means, when reloading the app or switching between routes/views, the buttons become enabled again, which is not what I want. Additionally, setting cache to true doesn't keep the buttons disabled after the app has been reopened either.
I have tried using localstorage to store the $scope.upDisabled and $scope.downDisabled, but it ruins the JSON formatting. I have tried remote storage and received the same result. For example, the $scope.upDisabled and $scope.downDisabled store data like so:
{"2":true, "3":false, "4":true}
and so on. But because storing in localstorage or a database requires me to use JSON.stringify, you can guess that those quotes cause trouble and I end up with nested JSON as well. So, the data from localstorage does not return the correct format.
My known problems/options are twofold.
Is there a better solution to persistently disabling those
buttons?
How would I correctly format the JSON coming out of
localstorage?
Something else to note: Since this is a mobile/hybrid app using Ionic, I am not using cookies, but I did create a token system to act similarly.
Edit: Thanks to those who replied and your suggestions. I am currently using a MySQL database to keep track of the votes (May switch to SQLite later) #aorfevre: I've taken a look at the SQLite plugin before and will probably use it down the road. I finally got everything working how I want it to.
$scope.upDisabled = {};
$scope.downDisabled = {};
$scope.upCheck = {};
$scope.downCheck = {};
...Votes Loading function up here
...Thread loading Function up here
.finally(function(){
angular.forEach($scope.threads,function(value,index){
angular.forEach($scope.upCheck,function(value2,index){
if(value.id == value2.threadID)
{
$scope.upDisabled[value.id] = true;
$scope.downDisabled[value.id] = false;
}
})
})
angular.forEach($scope.threads,function(value,index){
angular.forEach($scope.downCheck,function(value2,index){
if(value.id == value2.threadID)
{
$scope.downDisabled[value.id] = true;
$scope.upDisabled[value.id] = false;
}
})
})
}
$scope.loadVotes();
$scope.loadThreads();
As for the server side (PHP with Laravel Framework):
public function upvoteThread($id, $token)
{
$thread = Thread::find($id);
$score = $thread->score;
$score = $score + 1;
$thread->score = $score;
$thread->save();
$voted = ThreadVote::where('threadID','=',$id)->where('token','=',$token)->get();
if($voted->isEmpty())
{
$upVote = new ThreadVote;
$upVote->threadID = $id;
$upVote->token = $token;
$upVote->upVote = 'true';
$upVote->downVote = 'false';
$upVote->save();
}
else
{
DB::table('thread_votes')
->where('threadID', '=', $id)
->where('token','=',$token)
->update(array('upVote' => 'true', 'downVote' => 'false')
);
}
return Response::json(array('status'=>1))->setCallback(Input::get('callback'));
}
public function getUpVotes($token)
{
$votes = ThreadVote::where('token','=',$token)
->where('upVote','=','true')
->select('threadID','upVote')
->get();
return Response::json($votes)->setCallback(Input::get('callback'));
}
...Similar downVote Function...
So as it stands. When the button is pushed, it saves the user's token, threadID, and vote to the database. When the view is loaded, that information, based on the token and threadID is loaded as a JSON array, associated with the Thread array and if a threadID and thread.id matches, it's pushed into the up/downdisabled scope as "threadID":bool ("1":true for example).

I'll recommand you to implement a DB if you manage huge volume of post.
I personnaly use localStorage for several preferences not for storing a db.
Therefore, if you target only for ios / android, I recommand you https://github.com/litehelpers/Cordova-sqlite-storage
If you target windows phone 8.1, the implementation is not trivial and I'm facing some issues regarding the structure of WindowsPhone & C++ compilation librairy ( more details here : https://issues.apache.org/jira/browse/CB-8866 )

Related

How to dynamically show a button based on conditions where the value being used to determine what button to show isn't known until it's clicked?

If someone has a better title feel free to edit. I inherited a project from a developer who is leaving the company and I'm scratching my head trying to find a solution to a problem the existing code provides.
Code from the view:
<div>
<table class="table">
<tr>
<th class="border-bottom border-top-0">Action</th>
</tr>
#foreach (Step actionItem in Model.Steps)
{
#if (actionItem.HasRun == false)
{
<tr class="border-top-0">
<td>
#if (actionItem.ReturnsInfo == true)
{
<input type="button" value="Run Check" onclick="loadProcessingFeedbackPartial('#actionItem.StepID', '#Model.Client.DatabaseConnectionString' )" />
}
else
{
<input type="submit" value="Run Check" name="btnRunStoredProcedure" asp-action="CallStepStoredProcedure" asp-route-StepID="#actionItem.StepID" asp-route-StepCompleted="#actionItem.HasRun" />
}
</td>
</tr>
break;
}
}
</table>
</div>
Javascript being called from the button click:
<script type="text/javascript">
function loadProcessingFeedbackPartial(x, y) {
var url = '#Url.Action("ViewProcessingFeedBackPartial", "Client")';
var stepId = x;
var databaseConnectionString = y;
$("#processingFeedbackPartialDiv").load(url, { stepId, databaseConnectionString },
function () {
$("#confirmButton").removeAttr("style");
});
}
</script>
Controller action:
public IActionResult ViewProcessingFeedBackPartial(int StepId, string DatabaseConnectionString)
{
FeedbackDetails feedbackDetails = new FeedbackDetails();
feedbackDetails.Data = _clientProcessingService.GetProcessingFeedbackDetails(StepId, DatabaseConnectionString);
return PartialView("_ViewFeedback", feedbackDetails);
}
The button in the view has an Onclick event that goes to the Javascript function, which loads a partial view with the data from the controller calling a service method. Here's where the problem is. If no rows are returned, I want to bypass the partial being drawn entirely.
So I changed the controller action around a bit to include a condition where if the feedbackDetails.Data has 0 rows to just call a different method from the service, process as normal, but return the View instead of a partial.
public IActionResult ViewProcessingFeedBackPartial(int StepId, string DatabaseConnectionString, int ClientId)
{
FeedbackDetails feedbackDetails = new FeedbackDetails();
feedbackDetails.Data = _clientProcessingService.GetProcessingFeedbackDetails(StepId, DatabaseConnectionString);
if(feedbackDetails.Data.Rows.Count == 0)
{
_clientProcessingService.RunProcessStepConfirmation(DatabaseConnectionString, StepId, ClientId, "No information returned, automatically proceeding to next step.");
return RedirectToAction("Processing", new { Id = ClientId });
}
return PartialView("_ViewFeedback", feedbackDetails);
}
This "worked", except since in the view it's being called in a Javascript function that loads a partial regardless, the view is returned inside that partial instead of the view being returned.
But I'm unsure how to fix this because without first clicking the button and attempting to populate that collection with data, I don't know if it's empty (and skip the partial) or it has rows (and draw the partial).
I attempted creating an intermediary controller action that returns a boolean and attempted to use the result of that inside the javascript function to either draw the partial or skip it based on the bool, but I'm not really the greatest at Javascript so I wasn't able to get it to work.
I'm unsure if the way to solve this involves creating logic that displays multiple buttons that route to different controller actions or javascript functions or just handling it all via Javascript somehow.
What would be a good way to go about solving this?
#Mkalafut, your jQuery function is loading the controller result directly into "#processingFeedbackPartialDiv" regardless of the result received. Better to pull this initially into a variable, then add some simple logic to decide what to do next. Potentially the controller can help by returning a null result that is easy to identify.
e.g.
$.get("url", { stepId, databaseConnectionString }, function (data) {
var result = data;
// Some example conditional logic - adjust as required
if (result != null){
$("#processingFeedbackPartialDiv").html(result);
$("#confirmButton").removeAttr("style");
}
});
Remember, jQuery load & get are both just shorthand functions for ajax, so if needs be you can customise the code further to get the flexibility you need.
https://api.jquery.com/jQuery.get/
https://api.jquery.com/load/

Angular JS some result doesn't show in Google Chrome but show in FireFox

I have created a SelectedProduct factory to save data from ProductDetailCtrl and retrieve the data in ProductOrder Ctrl
The factory is as below
.factory('SelectedProduct',function(){
var products = {};
return{
addCategory: function(_category){
return products.category = {category: _category};
},
addSelectedProduct: function(_name,_image,_quantity,_price){
return products.item = {name: _name, image: _image,quantity: _quantity,price: _price};
},
getSelectedProduct: function(){
return products.item;
},
getCategory:function(){
return products.category
}
}
})
In my product_detail.html, I save the parameter by ng-click in :
<img ng-src="{{product.image}}" alt="{{product.product_name}}" ng-click="SelectImage((detail.image = product.image),(detail.name = product.product_name),(detail.quantity = product.quantity),(detail.price = product.price))" width="500" height="340">
In ProductDetailCtrl:
SelectedProduct.addCategory($stateParams.categoryName);
$scope.SelectImage = function () {
SelectedProduct.addSelectedProduct($scope.detail.name,$scope.detail.image,$scope.detail.quantity,$scope.detail.price);
};
Then I call the saved data in product_order.html:
<h3>RM {{selectedProduct.price}}</h3>
My ProductOrderCtrl:
$scope.selectedProduct = SelectedProduct.getSelectedProduct();
var categoryName = SelectedProduct.getCategory();
BUT the output only can't show in chrome browser, it works fine in my device, and other browsers. I just wondering why would it be like this. Any idea and reason about this problem?
Note:
Other data like name, image link works fine in chrome but just the price in 00.00 format can't show. the price retrieved from MySQL online and is stored in varchar.
Result from Chrome
Result from FireFox
There must be some caching in Google Chrome, try Empty Cache and Hard Reload option which can be performed in developer mode, by a simple right click on refresh button!

Pass a model from a java server to a webpage using mustache or handlebars

I am trying to pass a value from a server to a html page using mustache but I cannot figure out how to do that. I am at a loss here. I looked though some tutorials but I got even more lost with them.
So here is where I am trying to pass the user information from the server
#RequestMapping(value = HOME_URL_MAPPING)
public String inventory(final Model model) {
//pass in user stuff gus
String myRole = "HQ";
//USER_ROLE = userRole
model.addAttribute(USER_ROLE, myRole);
return controllerHelper.createUrl(INVENTORY, WebGlobals.HOME);
}
And this is the html that I am trying to send the data to.
{{>partials/header}}
<script>
var haveClicked = false;
var currentUserRole;
function checkUserRights()
{
currentUserRole = "HQ";
if(haveClicked == false)
{
switch (currentUserRole)
{
case "ADMIN":
alert("Opening ADMIN Rights");
$('Group1').appendTo($('body'));
$('Group2').appendTo($('body'));
haveClicked = true;
break;
case "HQ":
alert("Opening HQ Rights");
$('Group1').appendTo($('body'));
haveClicked = true;
break;
default:
alert("Opening A Rights");
$('Group2').appendTo($('body'));
haveClicked = true;
}
}
}
{{>partials/footer}}
I just don't know how to pass the information from the server to the html.
I tried stuff like this and some other options, but I think I am missing a important step
Here is one thing that I have tried that I think is on the right path;
<select class="user-roles ">
{{#userRoles}}
<option value={{ userRole }}>{{ "HQ" }}</option>
{{/userRoles}}

Conflicts when working with scopes and controllers in AngularJS

I have a simple website that uses AngularJS with a NodeJS backend.
It has multiple pages, like a homepage, a login/register page, etc.
I'd like to implement a "Chat" page where you could send messages to other clients using socket.io. I already got that part working, using a local controller (by local, I mean active on a single page - the Chat page).
The problem is, I would like the chat system to be global (i.e. client can receive messages while being on the homepage, but they'll still only be displayed when going back on the Chat page).
I'm having an issue when setting the Chat controller global (active on all pages).
Here's how I'm including it:
<body ng-controller="AppCtrl"> <!-- include main controller -->
<div ng-include="'header.tpl.html'"></div>
<div ng-controller="ChatCtrl" class="page"> <!-- include global Chat controller -->
<div ng-view class="container"></div>
</div>
<div ng-include="'footer.tpl.html'"></div>
<!-- ...etc. -->
</body>
This works pretty well, but it seems like I can't access a value from my Chat page, though. Functions declared from the Chat controller can still be called, but the "$scope.message" value (which contains the message that's being typed) is always empty.
Here's my Chat controller (which is actually called TravelCtrl)
angular.module('base').controller('TravelCtrl', //['$scope', 'security',
function($rootScope, $scope, security, NgMap, $geolocation, socket){
$scope.messages = [];
// Socket listeners
// ================
socket.on('init', function (data) {
$scope.name = data.name;
$scope.users = data.users;
});
socket.on('send:message', function (message) {
$scope.messages.push(message);
});
socket.on('change:name', function (data) {
changeName(data.oldName, data.newName);
});
socket.on('user:join', function (data) {
$scope.messages.push({
user: 'Server',
text: 'User ' + data.name + ' has joined.'
});
$scope.users.push(data.name);
});
// add a message to the conversation when a user disconnects or leaves the room
socket.on('user:left', function (data) {
$scope.messages.push({
user: 'chatroom',
text: 'User ' + data.name + ' has left.'
});
var i, user;
for (i = 0; i < $scope.users.length; i++) {
user = $scope.users[i];
if (user === data.name) {
$scope.users.splice(i, 1);
break;
}
}
});
// Private helpers
// ===============
var changeName = function (oldName, newName) {
// rename user in list of users
var i;
for (i = 0; i < $scope.users.length; i++) {
if ($scope.users[i] === oldName) {
$scope.users[i] = newName;
}
}
$scope.messages.push({
user: 'Server',
text: 'User ' + oldName + ' has been authenticated as ' + newName + '.'
});
}
// Methods published to the scope
// ==============================
$scope.changeName = function () {
socket.emit('change:name', {
name: $scope.newName
}, function (result) {
if (!result) {
alert('There was an error changing your name');
} else {
changeName($scope.name, $scope.newName);
$scope.name = $scope.newName;
$scope.newName = '';
}
});
};
$scope.sendMessage = function () {
socket.emit('send:message', {
message: $scope.message
});
// add the message to our model locally
$scope.messages.push({
user: $scope.name,
text: $scope.message
});
// clear message box
$scope.message = '';
};
// ================
var init = function () {
$scope.newName = security.currentUser.username;
$scope.changeName();
}
if ($rootScope.hasLoaded() && $scope.name != security.currentUser.username) {
init();
} else {
$rootScope.$on('info-loaded', init);
}
}
//]
);
As well as the Chat page itself. The strange thing is that connected users and messages display correctly, but the controller can't seem to retrieve the typed message.
<div class='col'>
<h3>Users</h3>
<div class='overflowable'>
<p ng-repeat='user in users'>{{user}}</p>
</div>
</div>
<div class='col'>
<h3>Messages</h3>
<div class='overflowable'>
<p ng-repeat='message in messages' ng-class='{alert: message.user == "chatroom"}'>{{message.user}}: {{message.text}}</p>
</div>
</div>
<div class='clr'>
<form ng-submit='sendMessage()'>
Message: {{message}}<br/>
<input size='60', ng-model='message'/>
<input type='submit', value='Send as {{name}}'/>
</form>
</div>
When pressing the "Send" button, AngularJS successfully calls the sendMessage function, but retrieves the "message" value as an empty string, leading it to send an empty socket.io message.
I'm quite new to AngularJS, so my approach might be totally ridiculous. I'm convinced I'm missing something obvious but after re-reading the docs again, I really can't seem to find what.
Is this a proper way to organise an AngularJS app?
Thanks in advance for your help.
Having recently built a large scale Angular/Socket.IO application, I strongly suggest that you put all of your Socket implementation into a Service. This service will maintain all of your socket state, and allow you to inject it into any required controllers. This will allow you to have a main page for Chat, however still be able to display notifications, chat user information, etc in other areas of your application.
It's not about your problem, but I saw something I suspect to be wrong.
When you use another library with angularjs, you should use a bridge to it (angular-socket-io for example).
When you do an $http call with angular, it updates $scope correctly in the callback and the changes are seen in the view.
In your code:
socket.on('send:message', function (message) {
$scope.messages.push(message);
});
There is a problem: "socket" isn't a library included in angularjs, so when the callback is called, your "$scope" modification isn't correctly noticed to angularjs.
You have to do use $scope.$apply(function() { code here which modifies $scope });
Example:
socket.on('send:message', function (message) {
$scope.$apply(function() {
$scope.messages.push(message);
});
});
EDIT:
I would like the chat system to be global (i.e. client can receive messages while being on the homepage, but they'll still only be displayed when going back on the Chat page).
Either store the datas in a global variable, or use $rootScope which is the parent scope of all the $scope you use in the application.
EDIT 2:
In fact it should solve your problem ;)
Another things:
1) use $rootScope instead of $scope for global variables (or a global variable). In any $scope you will access $rootScope variables ($scope is a copy of either $rooScope or a parent $scope).
2) register socket.io only once. Currently, if you change pages, you will register new callbacks at EACH page change.

Update list in Aurelia using EventAggregator

I am using app-contacts demo to learn Aurelia, yes I know, it's incomplete as mentioned by #Eisenberg, but then I thought to use EventAggregator to notify the app.js, when I save the contact or create a new contact. Till now everything works fine. I am able to receive the contact object in app.js, but now I would like to update the contact list, which is not working and when I save a contact and update the contacts list in app.js, it gets removed.
Code added in app.js and subscribe method is called in constructor.
subscribe(){
this.ea.subscribe('contact_event', payload => {
console.log(payload);
var instance = JSON.parse(JSON.stringify(payload));
let found = this.contacts.filter(x => x.id == payload.id)[0];
if(found){
let index = this.contacts.indexOf(found);
this.contacts[index] = instance;
}else{
instance.id = this.contacts.length + 1;
this.contacts.push(instance);
}
});
}
No changes made to app.html
<li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
<a href="#" click.delegate="$parent.select(contact)">
<h4 class="list-group-item-heading">${contact.firstName} ${contact.lastName}</h4>
<p class="list-group-item-text">${contact.email}</p>
</a>
</li>
How to update the list?
Update
This worked for me, but not sure what is the right approach
subscribe(){
this.ea.subscribe('contact_event', payload => {
return this.api.getContactList().then(contacts => {
this.contacts = contacts;
});
});
}
Eric L. Anderson does a great job of explaining how this works in his blog post:
Aurelia's Event Aggregator. It's event the same type of code you're trying to do!
Note: it's probably far too late to answer Kishore's question, but I'm putting the link in for others who end up here from a search.

Categories

Resources