I have sub-components that are updated by injecting state from the parent component.
I need to populate the model using an asynchronous function when the value of the parent component changes.
And I want to draw a new subcomponent after the asynchronous operation is finished.
I checked the change of the parent component value in onbeforeupdate, executed the asynchronous function, and then executed the redraw function, but this gets stuck in an infinite loop.
...
async onbeforeupdate((vnode)) => {
if (this.prev !== vnode.attrs.after) {
// Update model data
await asyncRequest();
m.redraw();
}
}
view() {
return (...)
}
...
As far as I can tell onbeforeupdate does not seem to support async calling style, which makes sense because it would hold up rendering. The use-case for onbeforeupdate is when you have a table with 1000s of rows. In which case you'd want to perform that "diff" manually. Say by comparing items length to the last length or some other simple computation.
This sort of change detection should happen in your model when the parent model changes, trigger something that changes the child model. Then the child view will return a new subtree which will be rendered.
In this small example the list of items are passed directly from the parent to the child component. When the list of items increases, by pressing the load button, the updated list of items is passed to the child and the DOM is updated during the redraw. There is another button to toggle if the decision to take a diff in the view should be done manually or not.
You can see when the views are called in the console.
The second example is the more common/normal Mithril usage (with classes).
Manual Diff Decision Handling
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class AppModel {
/* Encapsulate our model. */
constructor() {
this.child = {
items: [],
manualDiff: true,
};
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.child.items[this.child.items.length] = values[i];
}
}
getChildItems() {
return this.child.items;
}
toggleManualDiff() {
this.child.manualDiff = !this.child.manualDiff;
}
getManualDiffFlag() {
return this.child.manualDiff;
}
}
function setupApp(model) {
/* Set our app up in a namespace. */
class App {
constructor(vnode) {
this.model = model;
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
manualDiff: this.model.getManualDiffFlag(),
items: this.model.getChildItems(),
}),
m('button[type=button]', {
onclick: (e) => {
this.model.toggleManualDiff();
}
}, 'Toggle Manual Diff Flag'),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.model.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
constructor(vnode) {
this.lastLength = vnode.attrs.items.length;
}
onbeforeupdate(vnode, old) {
if (vnode.attrs.manualDiff) {
// Only perform the diff if the list of items has grown.
// THIS ONLY WORKS FOR AN APPEND ONLY LIST AND SHOULD ONLY
// BE DONE WHEN DEALING WITH HUGE SUBTREES, LIKE 1000s OF
// TABLE ROWS. THIS IS NOT SMART ENOUGH TO TELL IF THE
// ITEM CONTENT HAS CHANGED.
let changed = vnode.attrs.items.length > this.lastLength;
if (changed) {
this.lastLength = vnode.attrs.items.length;
}
console.log("changed=" + changed + (changed ? ", Peforming diff..." : ", Skipping diff..."));
return changed;
} else {
// Always take diff, default behaviour.
return true;
}
}
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
}
// Inject our model.
setupApp(new AppModel());
</script>
</body>
</html>
Normal Usuage
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class App {
constructor(vnode) {
this.items = [];
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.items[this.items.length] = values[i];
}
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
items: this.items
}),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
</script>
</body>
</html>
It should work. Maybe something is wrong with updating this.prev
Related
There's a 3rd-party library that creates table rows and buttons inside those rows. Both <tr> and <button> are constructed with an "onClick" property, but the authors forgot to call stopPropagation() and so when clicking a button it also triggers the <tr> handler:
render: (_text: any, record: TableRecord) => {
return createElement(
"button",
{
className: button.actionButtonClass,
onClick: () => {
onClickHandler(
record,
button.actionButtonOnClickAction,
button.actionButtonOnClickMf,
button.actionButtonOnClickNf,
button.actionButtonOnClickForm,
button.actionButtonOnClickOpenPageAs
);
}
I can't alter the code above, but I tried to add another handler:
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
if (!btn._my_have_stopPropagation) {
btn._my_have_stopPropagation = true;
btn.addEventListener("click", function(ev) {
ev.stopPropagation();
});
}
});
};
However, I learned that PointerEvent is not handled directly. It first bubbles to the container, then in the container's click handler it's wrapped with SyntheticEvent and that SyntheticEvent is passed back to the button. So calling stopPropagation() on the native event will prevent what we defined in the "onClick" property.
How do I subscribe to SynteticEvent so I can call stopPropagation on that event?
Upd This is what I came up with. It's full of hacks
// https://github.com/mendixlabs/mendix-tree-table/issues/35 BEGIN
function installOnClickHook(props) {
if (!props.onClick._my_have_stopPropagation) {
var oldOnClick = props.onClick;
props.onClick = function(ev) {
oldOnClick.apply(this, arguments);
ev.stopPropagation();
};
props.onClick._my_have_stopPropagation = true;
}
}
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
var props = FindReactProps(btn);
installOnClickHook(props);
});
};
function FindReactProps(dom) {
const key = Object.keys(dom).find(key=>{
return key.startsWith("__reactProps$")
});
// Somebody assigns another __reactProps to <button> without componentDidUpdate() on parent component
dom._my_props = dom[key];
Object.defineProperty(dom, key, {configurable: true, get: function() {
return this._my_props;
}, set: function (x) {
this._my_props = x;
installOnClickHook(x);
} });
return dom[key];
}
// https://github.com/mendixlabs/mendix-tree-table/issues/35 END
In my angular app, I have 2 methods save() and saveTriggers(). saveTriggers() updates all records by calling a web service (C#). I want to make sure that a block of code is executed after all records are updated in saveTriggers() and control is returned to save(). I believe I need to pass something from the saveTriggers() to make finally block execute. I tried various things, nothing works. Using .then() also gives the same error. I am not that good at JS. Can you please guide me.
vm.updatedTriggers = []; // IDs are pushed in
vm.saveTriggers = function () {
if (vm.updatedTriggers.length === 0) {
vm.close();
} else {
vm.saving = true;
vm.save()
.finally(function () { // ERROR - Cannot read property 'finally' of undefined
console.log("Saved all. Closing..."); // Never REACHES here
vm.saving = false;
vm.updated = true;
$uibModalInstance.close(true);
});
}
};
vm.save = function () {
//vm.saving = true;
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
target: trigger.target,
targetInfo: vm.targetData,
event: trigger.event,
eventQuantity: trigger.eventQuantity,
eventQuantityExtra: trigger.eventQuantityExtra
};
rpmService.editDeviceTrigger(input);
/*.finally(function () {
console.log("Updated event"); // Reaches here
vm.updated = true;
return Promise.resolve(2);
});*/ // Commenting this also doesn't help
}
return Promise.resolve(2);
};
rpmService.editDeviceTrigger(input)
public async Task EditDeviceTrigger(EditDeviceTriggerInput input) {
// calls other methods with await
// Doesn't return anything
}
EDIT: Updated Code: I got rid of the error, but the output is not is expected series.
vm.saveTriggers = function () {
vm.saving = true;
vm.save().then
(function success() {
console.log("Returned Result ");
console.log("Saved all. Closing..."); // These lines are executed before the event is upated
vm.saving = false;
$uibModalInstance.close(true);
});
};
vm.save = function () {
var deferred = $q.defer();
for (var i = 0; i < vm.updatedTriggers.length; i++) {
var trigger = vm.triggers.find(t => t.id === vm.updatedTriggers[i]);
var input = {
id: trigger.id,
....
};
rpmService.editDeviceTrigger(input)
.finally(function () {
console.log("Updated event"); // Successfully updates all events
vm.updated = true;
});
}
deferred.resolve();
return deferred.promise;
};
OUTPUT:
Returned Result
Saved all. Closing...
Updated event
EXPECTED OUTPUT:
Updated event
Returned Result
Saved all. Closing...
Thanks.
Usually you dont need $q.defer-related things, but u can do same using it if u want.
Here I guess you just need to collect all your save promises and return new resulting one using $q.all:
vm.save = function () {
const myAwesomePromises = []
for (var i = 0; i < vm.updatedTriggers.length; i++) {
...
const savePromise = rpmService.editDeviceTrigger(input);
savePromise.finally(() => console.log('edit device finally'));// <-- not sure u need this
myAwesomePromises.push(savePromise);
}
return $q.all(myAwesomePromises).finally(() => console.log('All edit device finally'));
};
I have a code in AngularJS which looks like below :
$scope.startWatching = function () {
return $scope.$watch('form', function (n, o) {
var timeoutPromise;
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
So whenever form changes, either $scope.showCompleteButton2() is called or $scope.showCompleteButton() is called.
The problem is that the $watch() gets called many number if times, so I need to bring these two methods out of the $watch().
Watchers like event listeners should only be added once when the DOM is built. And removed when the DOM is torn down.
If the code needs to enable or disable the actions performed by the watcher, provide a state in the model to do so:
var enableWatch = false;
$scope.startWatching = function () {
enableWatch = true;
};
var timeoutPromise;
$scope.$watch('form', function (n, o) {
if (!enableWatch) return;
//ELSE
timeoutPromise && $timeout.cancel(timeoutPromise);
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
The watcher ignores changes when the enableWatch variable is false. Set the variable to true to enable the specified actions.
Hopefully this is an easy answer.
I have this directive that controls the background images on the site. It rotates between images, but before it starts setting the background images, it actually preloads them. This is my code for the directive:
// ---
// CONTROLLERS.
// ---
.controller('BackgroundSwitcherController', ['$rootScope', 'Options', 'BackgroundSwitcherService', function ($rootScope, options, service) {
var self = this;
// Declare our variables
self.model = {
staticImage: '',
loading: false,
rotate: false
};
// On state change
$rootScope.$on('$stateChangeStart', function (event, toState) {
// Get our data
var data = toState.data;
// Set our rotate flag
self.model.rotate = options.current.rotateBackground;
// If we are set to rotate
if (self.model.rotate) {
// Make a call to our service (this is called every state change, but only preloaded once)
service.get().then(function (response) {
// Return the images in the response
self.model.images = response;
});
} else {
// Otherwise, reset the images
self.model.images = null;
}
// If we have data
if (data) {
// Reset our variables
self.model.staticImage = data.backgroundImage;
//self.model.loading = true;
}
});
}])
// ---
// SERVICES.
// ---
.service('BackgroundSwitcherService', ['$q', 'PreloaderService', function ($q, preloader) {
this.loading = true;
this.successful = false;
this.percentLoaded = 0;
// Get our images
this.get = function () {
// Our images
var images = [
'assets/backgrounds/Avebury Steps.jpg',
'assets/backgrounds/Avebury Stripe.jpg',
'assets/backgrounds/Avebury.jpg',
'assets/backgrounds/Forest Hills.jpg',
'assets/backgrounds/Inteface.jpg',
'assets/backgrounds/New Oaklands.jpg',
'assets/backgrounds/Primo Delight.jpg',
'assets/backgrounds/Secure 3d.jpg',
'assets/backgrounds/Secure.jpg',
'assets/backgrounds/Sensation Heathers.jpg',
'assets/backgrounds/Sensation.jpg',
'assets/backgrounds/Woman.jpg'
];
// Create our promise
var deferred = $q.defer();
// Preload the images
preloader.preloadImages(images).then(
// Function to handle the changing of the flags when all images have loaded
function handleResolve(imageLocations) {
this.loading = false;
this.successful = true;
deferred.resolve(images);
},
// Function to handle any errors
function handleReject(imageLocation) {
this.loading = false;
this.successful = false;
deferred.reject();
},
// Function that notifies our percentage loaded flag
function handleNotify(event) {
this.percentLoaded = event.percent;
}
);
// Return our promise
return deferred.promise;
};
}])
// ---
// DIRECTIVES.
// ---
.directive('backgroundSwitcher', ['$interval', function ($interval) {
// Variables
var target = null,
timer = null,
images = null,
currentIndex = 0;
// Function to get a random value between two numbers
function getRandomInt(min, max) {
// Return our random number
return Math.floor(Math.random() * (max - min)) + min;
};
var getImageIndex = function (length) {
// Get our random index
var index = getRandomInt(0, images.length);
// If our index matches the current index
if (index == currentIndex) {
// Run again until we get a different index
return getImageIndex(length);
}
// Set our current index our the new index
currentIndex = index;
// Return our index
return index;
}
// Apply the image
var applyImage = function () {
// Get a random image
var image = images[getImageIndex(images.length)];
// Apply our image to our target
applyStaticImage(image);
};
// Apply a static image
var applyStaticImage = function (image) {
// Apply our image to our target
target.css('background-image', 'url(' + escape(image) + ')');
};
// Remove any images
var removeImage = function () {
// Remove our background image
target.css('background-image', 'none');
};
// Start timer function
var startTimer = function () {
// If our timer is not running
if (!timer || timer.$$state.status === 2) {
// Apply our first image
applyImage();
// Start our timer
timer = $interval(changeImage, 10000);
}
};
// Function to change the background image
var changeImage = function () {
// Apply our image
applyImage();
};
// Stop the timer
var stopTimer = function () {
// If we have started our timer
if (timer) {
// Stop it
$interval.cancel(timer);
}
};
return {
restrict: 'A',
controller: 'BackgroundSwitcherController',
link: function (scope, element, attr, controller) {
// Assign our element to our global variable
target = element;
// Watch our image
scope.$watch(function () {
// Return our image
return controller.model.staticImage;
}, function (staticImage) {
// If we have an image
if (staticImage) {
// Stop our timer
stopTimer();
// Apply our static image
applyStaticImage(staticImage);
} else {
// If we are not rotating
if (!controller.model.rotate) {
// Remove any images
removeImage();
}
}
})
// Watch our rotate
scope.$watch(function () {
// Return our rotate flag
return controller.model.images;
}, function (array) {
// Set our variable
images = array;
// If we have some images
if (images) {
// If we don't have a static image
if (!controller.model.staticImage) {
// Start rotating our images
startTimer();
}
} else {
// Remove any images
removeImage();
// Otherwise, stop our timer
stopTimer();
}
});
// Destroy function to cancel all timers
element.on('$destroy', function () {
// Stop our timer
stopTimer();
});
}
}
}]);
and for the actual preloader I have this:
// ---
// SERVICES.
// ---
.factory('PreloaderService', function ($q, $rootScope) {
function Preloader(imageLocations) {
this.imageLocations = imageLocations;
this.imageCount = imageLocations.length;
this.loadCount = 0;
this.errorCount = 0;
this.states = {
PENDING: 1,
LOADING: 2,
RESOLVED: 3,
REJECTED: 4
};
this.state = this.states.PENDING;
// When loading the images, a promise will be returned to indicate
// when the loading has completed (and / or progressed).
this.deferred = $q.defer();
this.promise = this.deferred.promise;
}
// I reload the given images [Array] and return a promise. The promise
// will be resolved with the array of image locations.
Preloader.preloadImages = function (imageLocations) {
var preloader = new Preloader(imageLocations);
return (preloader.load());
};
Preloader.prototype = {
// Best practice for 'instnceof' operator.
constructor: Preloader,
// ---
// PUBLIC METHODS.
// ---
// I determine if the preloader has started loading images yet.
isInitiated: function isInitiated() {
return (this.state !== this.states.PENDING);
},
// I determine if the preloader has failed to load all of the images.
isRejected: function isRejected() {
return (this.state === this.states.REJECTED);
},
// I determine if the preloader has successfully loaded all of the images.
isResolved: function isResolved() {
return (this.state === this.states.RESOLVED);
},
// I initiate the preload of the images. Returns a promise.
load: function load() {
// If the images are already loading, return the existing promise.
if (this.isInitiated()) {
return (this.promise);
}
this.state = this.states.LOADING;
for (var i = 0 ; i < this.imageCount ; i++) {
this.loadImageLocation(this.imageLocations[i]);
}
// Return the deferred promise for the load event.
return (this.promise);
},
// ---
// PRIVATE METHODS.
// ---
// I handle the load-failure of the given image location.
handleImageError: function handleImageError(imageLocation) {
this.errorCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
this.state = this.states.REJECTED;
this.deferred.reject(imageLocation);
},
// I handle the load-success of the given image location.
handleImageLoad: function handleImageLoad(imageLocation) {
this.loadCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
// Notify the progress of the overall deferred. This is different
// than Resolving the deferred - you can call notify many times
// before the ultimate resolution (or rejection) of the deferred.
this.deferred.notify({
percent: Math.ceil(this.loadCount / this.imageCount * 100),
imageLocation: imageLocation
});
// If all of the images have loaded, we can resolve the deferred
// value that we returned to the calling context.
if (this.loadCount === this.imageCount) {
this.state = this.states.RESOLVED;
this.deferred.resolve(this.imageLocations);
}
},
// I load the given image location and then wire the load / error
// events back into the preloader instance.
// --
// NOTE: The load/error events trigger a $digest.
loadImageLocation: function loadImageLocation(imageLocation) {
var preloader = this;
// When it comes to creating the image object, it is critical that
// we bind the event handlers BEFORE we actually set the image
// source. Failure to do so will prevent the events from proper
// triggering in some browsers.
var image = angular.element(new Image());
image.bind('load', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageLoad(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).bind('error', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageError(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).prop('src', imageLocation);
}
};
// Return the factory instance.
return (Preloader);
});
Now, when I use this directive all my other images / font awesome icons pause before they load. It causes an issue because the font awesome icons actually briefly show a rectangular box before fully loading and the profile image is blank until loaded.
If I remove my directive from my HTML everything loads fine.
Now I realise that this is happening because it has to load an array of images for the background switcher, so what I would like to do is to not start the preloading until everything else has loaded first.
Can someone tell me if there is a simple way to do this?
I'm trying to learn firebase/angularjs by extending an app to use firebase as the backend.
My forge looks like this
.
In my program I have binded firebaseio.com/projects to $scope.projects.
How do I access the children?
Why doesn't $scope.projects.getIndex() return the keys to the children?
I know the items are in $scope.projects because I can see them if I do console.log($scope.projects)
app.js
angular.module('todo', ['ionic', 'firebase'])
/**
* The Projects factory handles saving and loading projects
* from localStorage, and also lets us save and load the
* last active project index.
*/
.factory('Projects', function() {
return {
all: function () {
var projectString = window.localStorage['projects'];
if(projectString) {
return angular.fromJson(projectString);
}
return [];
},
// just saves all the projects everytime
save: function(projects) {
window.localStorage['projects'] = angular.toJson(projects);
},
newProject: function(projectTitle) {
// Add a new project
return {
title: projectTitle,
tasks: []
};
},
getLastActiveIndex: function () {
return parseInt(window.localStorage['lastActiveProject']) || 0;
},
setLastActiveIndex: function (index) {
window.localStorage['lastActiveProject'] = index;
}
}
})
.controller('TodoCtrl', function($scope, $timeout, $ionicModal, Projects, $firebase) {
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
var keys = $scope.projects.$getIndex();
console.log($scope.projects.$child('-JGTmBu4aeToOSGmgCo1'));
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("" + keys[0]);
});
// A utility function for creating a new project
// with the given projectTitle
var createProject = function(projectTitle) {
var newProject = Projects.newProject(projectTitle);
$scope.projects.$add(newProject);
Projects.save($scope.projects);
$scope.selectProject(newProject, $scope.projects.length-1);
};
// Called to create a new project
$scope.newProject = function() {
var projectTitle = prompt('Project name');
if(projectTitle) {
createProject(projectTitle);
}
};
// Called to select the given project
$scope.selectProject = function(project, index) {
$scope.activeProject = project;
Projects.setLastActiveIndex(index);
$scope.sideMenuController.close();
};
// Create our modal
$ionicModal.fromTemplateUrl('new-task.html', function(modal) {
$scope.taskModal = modal;
}, {
scope: $scope
});
$scope.createTask = function(task) {
if(!$scope.activeProject || !task) {
return;
}
console.log($scope.activeProject.task);
$scope.activeProject.task.$add({
title: task.title
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
task.title = "";
};
$scope.newTask = function() {
$scope.taskModal.show();
};
$scope.closeNewTask = function() {
$scope.taskModal.hide();
};
$scope.toggleProjects = function() {
$scope.sideMenuController.toggleLeft();
};
// Try to create the first project, make sure to defer
// this by using $timeout so everything is initialized
// properly
$timeout(function() {
if($scope.projects.length == 0) {
while(true) {
var projectTitle = prompt('Your first project title:');
if(projectTitle) {
createProject(projectTitle);
break;
}
}
}
});
});
I'm interested in the objects at the bottom
console.log($scope.projects)
Update
After digging around it seems I may be accessing the data incorrectly. https://www.firebase.com/docs/reading-data.html
Here's my new approach
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
console.log(snapshot.val()['-JGTdgGAfq7dqBpSk2ls']);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
I'm still not sure how to traverse the keys programmatically but I feel I'm getting close
It's an object containing more objects, loop it with for in:
for (var key in $scope.projects) {
if ($scope.projects.hasOwnProperty(key)) {
console.log("The key is: " + key);
console.log("The value is: " + $scope.projects[key]);
}
}
ok so val() returns an object. In order to traverse all the children of projects I do
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
var keys = Object.keys(snapshot.val());
console.log(snapshot.val()[keys[0]]);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
Note the var keys = Object.keys() gets all the keys at firebaseio.com/projects then you can get the first child by doing snapshot.val()[keys[0])