I have an application having a lot of things to save in cascade, imaging a normal master - detail view.
In this view I have a "Save All" Button that save each row in an iteration, triggering jQuery custom events, to serialize the saving operations and prevent the generation an uncontrolled queue of requests.
Each time a row is saved, the program decrement the counter and launch the save of the new row.
Everything ends when there where no rows to save (counter = 0).
This is a code snippet doing this:
var save_counter = -1;
// Creates a counter and save content header when finished to save rows.
var updCounter = function(evt){
// Update Counter
save_counter--;
// Register updates When there are not rows to skip
if ((save_counter===0)
|| (save_counter===0 && edit_status == "modified") ){
console.log('Persist Master');
$(document).trigger('save_ok');
}
};
saveRows = $(form_sel);
// Reset Save Counter
save_counter = saveRows.length;
// Iterate through lines
saveRows.each(function(idx){
var form = $(this);
// Execute Uptade Counter once
form.one(update_counter, updCounter);
// Per each performed save, decrese save counter
form.trigger('submit');
});
Now I'm migrating some critical application modules, using angular but I have no idea to do that.
There is a best practice to perform a batch request call?
Is it a good idea to use $scope variables and $watch, using something like this?
var RowController = angular.controller('RowController', function($scope, $http){
$scope.rows = [
{id : 1, title : 'lorem ipsum'}
, {id : 2, title : 'dolor sit amet'}
, {id : 3, title : 'consectetuer adipiscing elit'}
];
// Counter Index
$scope.save_counter = -1;
// "Trigger" the row saving, changing the counter value
$scope.saveAll = function () {
$scope.save_counter = 0;
};
// Watch the counter and perform the saving
$scope.$watch('save_counter', function(
// Save the current index row
if ($scope.save_counter >= 0
&& $scope.save_counter < $scope.rows.length) {
$http({
url : '/row/' + $scope.rows[$scope.save_counter].id,
data: $scope.rows[$scope.save_counter]
}).success(function(data){
// Update the counter ...
$scope.save_counter ++;
}).error(function(err){
// ... even on error
$scope.save_counter ++;
});
};
));
});
The best approach is to use a service with promises ($q).
Here's an example:
app.factory('RowService', function($http, $q) {
return {
saveRow: function(row) {
return $http({
url: '/row/' + row.id,
data: row
});
},
saveAll: function(rows) {
var deferred = $q.defer();
var firstRow = rows.shift();
var self = this;
// prepare all the saveRow() calls
var calls = [];
angular.forEach(rows, function(row) {
calls.push(function() {
return self.saveRow(row);
});
});
// setup the saveRow() calls sequence
var result = this.saveRow(firstRow);
angular.forEach(calls, function(call) {
result = result.then(call);
});
// when everything has finished
result.then(function() {
deferred.resolve();
}, function() {
deferred.reject();
})
return deferred.promise;
}
}
});
And on your controller:
app.controller('RowController', function($scope, RowService) {
...
$scope.saveAll = function() {
// $scope.rows.slice(0) is to make a copy of the array
RowService.saveAll($scope.rows.slice(0)).then(
function() {
// success
},
function() {
// error
})
};
});
Check this plunker for an example.
Related
I've been working on this for days and I can't seem to find a solution.
I want this script to wait until the user presses the enter key after the first value has been inputted into the field. I want the script to keep doing this every time a value is added, but I can't quite seem to find out how to do this.
$(document).ready(function() {
console.log("script loaded");
var apiKey = "";
var itemImage = $(".title-wrap img");
var itemList = [];
var i = 0;
var addPage = false;
// Run through all images and grab all item ID's.
function scrapeItems() {
itemImage.each(function() {
var grabItemID = $(this).attr("src").match(/\d+/)[0];
var disabled = $(this).closest("li.clearfix").hasClass("disabled");
// Add item number as class for easy reference later.
$(this).addClass("item-" + grabItemID);
// If the item's row has "disabled" class, skip this item.
if (disabled) {
return true;
scrapeItems();
}
// Add item to array.
itemList.push(grabItemID);
});
}
scrapeItems();
// Call the API request function and start gathering all bazaar prices.
function getPricing() {
console.log("script started");
$.each(itemList, function(key, value) {
// Set three second timer per API request.
setTimeout(function() {
// Actual API request.
return $.ajax({
dataType: "json",
url: "https://api.torn.com/market/" + value,
data: {
selections: "bazaar",
key: apiKey
},
// When data is received, run this.
success: function(data) {
console.log(value + " request was successful");
var cheapest = null;
// Run through all results and return the cheapest.
$.each(data["bazaar"], function(key, val) {
var cost = val["cost"];
if (cheapest == null || cost < cheapest) {
cheapest = cost;
}
});
var inputMoney = $(".item-" + value).closest("li.clearfix").find(".input-money:text");
inputMoney.val(cheapest - 1).focus();
// I WANT THE FUNCTION TO WAIT HERE UNTIL THE USER PRESSES ENTER
},
// When data is not received, run this.
error: function() {
console.log(value + " request was NOT successful");
}
});
}, key * 3000);
});
}
function checkPage() {
var i = 0;
var url = window.location.href;
i++
setTimeout(function() {
if (url.indexOf("bazaar.php#/p=add") > 0) {
addPage = true;
addButton();
} else {
checkPage();
}
}, i * 1000);
}
checkPage();
function addButton() {
$("#inventory-container").prepend('<button id="start-button" style="margin-bottom:10px;margin-right:10px;">Run Auto-pricing script</button><p id="s-desc" style="display:inline-block;font-weight:bold;text-transform:uppercase;">Press the enter key after the price has shown up!</p>');
}
$(document).on("click", "#start-button", function() {
getPricing();
});
});
I'm at a complete loss on this one guys, so all help is appreciated!
I think you should break down your code a bit more, and move the "on enter" part of the code into a separate function instead of waiting for user input within that success callback.
e.g in pseudo code, different stages of the scraping
let priceData;
const preProcessPriceData = (data) => {
// do some pre-processing, validate, change data formats etc
// return processed data
};
const processPriceData = (data) => {
// called when price data is ready and user pressed enter
// in other words - script continues here
console.log(priceData, 'or', data);
};
scrapeItems();
// in get prices function - remove event handler
$("#some-input-user-is-pressing-enter-in").offOnEnter(processPriceData);
priceData = null;
getPrices().then((data) => {
priceData = data;
let processedData = preProcessPriceData(data);
// add listener to wait for user input
$("#some-input-user-is-pressing-enter-in").onEnter(() => {
// script continues after user presses enter
processPriceData(processedData);
});
});
I need to delete a row from my table but I don't want to reload or refresh all my table in order to see the updated rows.
var demoApp = angular.module("demoApp", ["ngResource"]);
// Controller
demoApp.controller("categoryController", function($scope, deleteCategorieService, categoriesService){
$scope.categories = categoriesService.query();
$scope.deleteCategory = function(id){
deleteCategoryService.deleteCategory({id: id});
// I want to avoid this method to refresh my table.
// $scope.categories = categoriesService.query();
};
});
// Factories
demoApp.factory("deleteCategorieService", function($resource){
return $resource("/demopro/deleteCategory/:id", {}, {
deleteCategory: {
method: "DELETE",
params: {id: "#id"}
}
});
});
demoApp.factory("categoriesService", function($resource){
return $resource("/demopro/categories", {}, {
listAllCategories : {
method: "GET",
isArray: true
}
});
});
How can I do that?
You still have to make the server call to delete the item but, to simply remove it from the view without reloading the whole list from the server, loop through your $scope.categories looking for the id of the item you are deleting, when found, remove from the array.
var i = $scope.categories.length;
while (i > 0) {
if ($scope.categories[i].id === id) {
$scope.categories.splice(i, 1);
break;
}
i--;
}
You can also do a positive loop which I normally do but I recently was told this back-to-front loop is supposed to be much faster. YMMV.
for (var i = 0; i < $scope.categories.length; i++) {
if ($scope.categories[i].id === id) {
$scope.categories.splice(i, 1);
break;
}
}
If you are using 2-way binding in your view, the HTML should update without the item you just deleted without having to requery the entire collection.
If the problem is that you want to avoid the flickering that happens when refreshing the list, just update the list in the success callback. Something like:
$scope.deleteCategory = function(id){
deleteCategoryService.deleteCategory({id: id},
function(success) {
$scope.categories = categoriesService.query();
});
};
I'm newbie to js and this is my first question in stackoverflow as well. So any comment or act of downgrading is understandable.
This is the angular-js-flowchart project on github.
This is another stackoverflow topic that teachs how to use factory as a data getter involving $http.
My need is to generate data for the chart by using an Angular factory that returns a $http function. The $http talks to a php service that retrieve data from database. I have tested the service using jsonlint and its working fine. The directory of service is checked, relatively to the html file.
I copied the "factory" code from another stackoverflow question and applied to app.js in the angularjs-flowchart Github project.
The problem is that the Chrome console keeps throwing an error that I can not understand. Data is not retrieved. The error on console is "TypeError: Cannot read property 'getData' of undefined"
This is the modified-by-me app.js:
//
// Define the 'app' module.
//
angular.module('app', ['flowChart', ])
//
// Simple service to create a prompt.
//
.factory('prompt', function () {
/* Uncomment the following to test that the prompt service is working as expected.
return function () {
return "Test!";
}
*/
// Return the browsers prompt function.
return prompt;
})
//
// Application controller.
//
.controller('AppCtrl', ['$scope', 'prompt', function AppCtrl ($scope, prompt, dataFactory) {
//
// Code for the delete key.
//
var deleteKeyCode = 46;
//
// Code for control key.
//
var ctrlKeyCode = 65;
//
// Set to true when the ctrl key is down.
//
var ctrlDown = false;
//
// Code for A key.
//
var aKeyCode = 17;
//
// Code for esc key.
//
var escKeyCode = 27;
//
// Selects the next node id.
//
var nextNodeID = 10;
//
// Event handler for key-down on the flowchart.
//
$scope.keyDown = function (evt) {
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = true;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Event handler for key-up on the flowchart.
//
$scope.keyUp = function (evt) {
if (evt.keyCode === deleteKeyCode) {
//
// Delete key.
//
$scope.chartViewModel.deleteSelected();
}
if (evt.keyCode == aKeyCode && ctrlDown) {
//
// Ctrl + A
//
$scope.chartViewModel.selectAll();
}
if (evt.keyCode == escKeyCode) {
// Escape.
$scope.chartViewModel.deselectAll();
}
if (evt.keyCode === ctrlKeyCode) {
ctrlDown = false;
evt.stopPropagation();
evt.preventDefault();
}
};
//
// Add a new node to the chart.
//
$scope.addNewNode = function () {
var nodeName = prompt("Enter a task name:", "New Task");
if (!nodeName) {
return;
}
//
// Template for a new node.
//
var newNodeDataModel = {
name: nodeName,
id: nextNodeID++,
x: 0,
y: 0,
inputConnectors: [
{
name: "Pre"
}
],
outputConnectors: [
{
name: "Sub"
}
],
};
$scope.chartViewModel.addNode(newNodeDataModel);
};
//
// Add an input connector to selected nodes.
//
$scope.addNewInputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addInputConnector({
name: connectorName,
});
}
};
//
// Add an output connector to selected nodes.
//
$scope.addNewOutputConnector = function () {
var connectorName = prompt("Enter a connector name:", "New connector");
if (!connectorName) {
return;
}
var selectedNodes = $scope.chartViewModel.getSelectedNodes();
for (var i = 0; i < selectedNodes.length; ++i) {
var node = selectedNodes[i];
node.addOutputConnector({
name: connectorName,
});
}
};
//
// Delete selected nodes and connections.
//
$scope.deleteSelected = function () {
$scope.chartViewModel.deleteSelected();
};
//
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
//
// Create the view-model for the chart and attach to the scope.
//
$scope.chartViewModel = new flowchart.ChartViewModel(chartDataModel);
}])
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Basically, what i added but doesn't work is
// Setup the data-model for the chart.
//
var chartDataModel = {};
var handleSuccess = function(data, status){
chartDataModel = data;
console.log(chartDataModel);
};
dataFactory.getData().success(handleSuccess);
and
.factory('dataFactory', function($http){
return {
getData : function(){
return $http.post("chart-data-retrieve.php");
}
};
});
Please help, thanks.
I tried to set the chartViewModel of the $scope directly inside the service call, so the variable chartDataModel becomes redundant. And it works.
// Create the view-model for the chart and attach to the scope.
//
myService.then(function(data) {
$scope.chartViewModel = new flowchart.ChartViewModel(data);
});
I tried to return a promise, not a $http from the factory. It works now. The controller can now use the service to retrieve data. However I still could not set the controller's variable to the data retrieved.
The following is the code:
.factory('myService', function($http, $q) {
//this runs the first time the service is injected
//this creates the service
var deferred = $q.defer();
$http.get('chart-data-retrieve.php').then(function(resp) {
deferred.resolve(resp.data);
});
return deferred.promise;
})
And the code inside controller:
var chartDataModel = {};
//get data from myService factory
myService.then(function(data) {
alert(data);
chartDataModel = data;
});
Currently, the alert() show me the data already. However, the variable chartDataModel is still unset.
I have view page where am trying to display all organizations,which is
obtained by a server call..In order to feel the application responsive
, in between the server response I want to load all local store
items.. But server call is always executing first.. The code I
mentioned bellow..
initialize: function()
{
var me = this,
st = Ext.create("Ext.data.Store", {
fields : [ {......................}]});
me.callParent(arguments);
me.setStore(st);
me.on({
show : me.onShow,
scope: me
});
},
onShow:function()
{
var me = this;
Ext.create('Ext.util.DelayedTask',
//call back function ,purpose : delayed exicution
function () {
me.DelShow(function(){
_syncMgr.getOrgGroup(-1,0,5); // servercall
});
}).delay(500);
},
DelShow: function(callback)
{
orgStore = Ext.getStore('Organizations'),
orgStore.load(function(records)
{
var i=0,len = records.length,
for(;i<len;i++)
{
organization = records[i];
regId = organization.get('rg_id');
resStr = organization.Resources();
resStr.load({callback:function(resorces)
{
var i = 0,rlen =resorces.length,
obj = {},
obj.rg_id = str.boundTo.get('rg_id');
}
orgViStr.add([obj]);
});
}
});
me.lodorg(callback);
},
lodorg:function(callback)
{
callback();
console.log("I don't know why this call back works first....");
console.log("plz help me to work last....");
}
Call You callback method after You load data from local store .
What you want to do is add the content from your local store to the list that is currently set in the view then refresh the list.
var localStore = something.getStore(),
entries = [];
localStore.each(function(entry){
entries.push(entry.copy);
});
//me is the list that is set in the view
me.add(entries);
me.deselectAll();
me.refresh();
Hope that helps :)
What I have is simple CRUD operation. Items are listed on page, when user clicks button add, modal pops up, user enters data, and data is saved and should automatically (without refresh)be added to the list on page.
Service:
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).fail(getFailed);
},
addExerciseAndCategories: function(data, initialValues) {
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function(item) {
manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
});
saveChanges().fail(addFailed);
function addFailed() {
removeItem(items, item);
}
},
Controller:
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.queryItems = adminCrudService.querySucceeded(data);
var exerciseIds = _($scope.queryItems).pluck('ExerciseId').uniq().valueOf();
$scope.exerciseAndCategories = [];
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise : exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where($scope.queryItems, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
$scope.items.push(newItem);
});
$scope.$apply();
}
Here is how I add new data from controller:
adminCrudService.addExerciseAndCategories($scope.selectedCategories, { Name: $scope.NewName, Description: $scope.NewDesc });
So my question is, why list isn't updated in real time (when I hit save I must refresh page).
EDIT
Here is my querySuceeded
querySucceeded: function (data) {
items = [];
data.results.forEach(function(item) {
items.push(item);
});
return items;
}
EDIT 2
I believe I've narrowed my problem !
So PW Kad lost two hours with me trying to help me to fix this thing (ad I thank him very very very much for that), but unfortunately with no success. We mostly tried to fix my service, so when I returned to my PC, I've again tried to fix it. I believe my service is fine. (I've made some changes as Kad suggested in his answer).
I believe problem is in controller, I've logged $scope.items, and when I add new item they don't change, after that I've logged $scope.queryItems, and I've noticed that they change after adding new item (without refresh ofc.). So probably problem will be solved by somehow $watching $scope.queryItems after loading initial data, but at the moment I'm not quite sure how to do this.
Alright, I am going to post an answer that should guide you on how to tackle your issue. The issue does not appear to be with Breeze, nor with Angular, but the manner in which you have married the two up. I say this because it is important to understand what you are doing in order to understand the debug process.
Creating an entity adds it to the cache with an entityState of isAdded - that is a true statement, don't think otherwise.
Now for your code...
You don't have to chain your query execution with a promise, but in your case you are returning the data to your controller, and then passing it right back into some function in your service, which wasn't listed in your question. I added a function to replicate what yours probably looks like.
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).then(querySucceeded).fail(getFailed);
function querySucceeded(data) {
return data.results;
}
},
Now in your controller simply handle the results -
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
// Set your object directly to the data.results, because that is what we are returning from the service
$scope.queryItems = data;
$scope.exerciseAndCategories = [];
Last, let's add the properties we create the entity and see if that gives Angular a chance to bind up properly -
_.forEach(data, function(item) {
var e = manager.createEntity("ExerciseAndCategory");
e.Exercise = addedExercise; e.Category: item.Category;
});
So I've managed to solve my problem ! Not sure if this is right solution but it works now.
I've moved everything to my service, which now looks like this:
function addCategoriesToExercise(tempdata) {
var dataToReturn = [];
var exerciseIds = _(tempdata).pluck('ExerciseId').uniq().valueOf();
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise: exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where(tempdata, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
dataToReturn.push(newItem);
});
return dataToReturn;
}
addExerciseAndCategories: function (data, initialValues) {
newItems = [];
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function (item) {
var entity = manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
items.push(entity);
newItems.push(entity);
});
saveChanges().fail(addFailed);
var itemsToAdd = addCategoriesToExercise(newItems);
_.forEach(itemsToAdd, function (item) {
exerciseAndCategories.push(item);
});
function addFailed() {
removeItem(items, item);
}
}
getAllExercisesAndCategories: function () {
var query = breeze.EntityQuery.from("ExercisesAndCategories").expand("Exercise,ExerciseCategory");
return manager.executeQuery(query).then(getSuceeded).fail(getFailed);
},
function getSuceeded(data) {
items = [];
data.results.forEach(function (item) {
items.push(item);
});
exerciseAndCategories = addCategoriesToExercise(items);
return exerciseAndCategories;
}
And in controller I have only this:
$scope.getAllExercisesAndCategories = function () {
adminExerciseService.getAllExercisesAndCategories()
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.items = data;
$scope.$apply();
}