I want to add 'add to favorites/bookmark' feature in one of my projects, but am totally blank regarding the same.
Basically, I'm using bootstrap glyphicons for user to select it if he wants add/remove from favorites. I did some research and found html5 localStorage concept suitable but can't really figure out its exact working for my project. It would be a great help if anyone here can guide me on it.
here's html:
<h1>test</h1>
<table class="'basic">
<tr>
<td><div class="glyphicon glyphicon-star-empty icon bkmark_icon"> </div></td>
<td>A</td>
</tr>
<tr>
<td><div class="glyphicon glyphicon-star-empty icon bkmark_icon"> </div></td>
<td>B</td>
</tr>
<tr>
<td><div class="glyphicon glyphicon-star-empty icon bkmark_icon"> </div></td>
<td>C</td>
</tr>
</table>
<br><button class="btn">You have selected:</button>
since I don't really know the implementation of localStorage in jQuery, haven't added it, yet this is what js file has for now:
$(function() {
$(document).on('click', '.bkmark_icon', function(){
$(this).toggleClass('glyphicon-star-empty glyphicon-star');
// localStorage.setItem('display', $(this).is(':visible'));
});
$(document).on('click', '.btn', function(){
var bkmark_item = '<table><tr><td><div class="glyphicon glyphicon-star icon bkmark_icon"></div></td><td>*selected*</td></tr></table>';
$(bkmark_item).insertAfter('h1');
});
});
i did search over it and found this stack overflow answer appropriate.
what I need?
when user clicks on glyphicon-star-empty, it should toggle to glyphicon-star and the related item should be added to localStorage (i.e favorites).
when user clicks on 'You have selected:' button, it should remove current content which is in table and show only the favorited items(on the same page) with an option to un-favorite it.
here's fiddle the i'm working on.
Try creating a JavaScript object and serialize it to save it in localStorage. Use something like this -
var bookmarkedItems = [];
function ItemObject(name, content)
{
this.name = name;
this.content = content;
}
function addItem()
{
bookmarkedItems.push(new ItemObject('Item1', 'Content1'));
}
function saveToLocalStorage(item)
{
var ob = localStorage.get('KEY');
if(ob)
{
bookmarkedItems = JSON.parse(ob);
}
bookmarkedItems.push(item);
localStorage.set('KEY', JSON.stringify(bookmarkedItems);
}
OR
Refer below code -
var storageService = function () {
var STORAGE_KEY = "bookmarkitems";
var bookmarkitems = {};
var init = function () {
bookmarkitems = sessionStorage.getItem(STORAGE_KEY);
if (bookmarkitems) {
bookmarkitems = JSON.parse(bookmarkitems);
}
else {
bookmarkitems = {};
}
};
var set = function (key, value) {
bookmarkitems[key] = value;
updateStorage();
};
var get = function (key) {
return bookmarkitems[key];
};
var updateStorage = function () {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarkitems));
};
return {
init: init,
set: set,
get: get,
updateStorage: updateStorage
};
};
Here is a small example on how to use local storage:
// Store
localStorage.setItem("lastname", "Smith");
// Retrieve
document.getElementById("result").innerHTML = localStorage.getItem("lastname");
You can reference this page for more examples.
Using it is pretty simple it works like any JS Object.
EDIT:
There are more extended examples on the functionality of local storage here.
Related
Within an ng-repeat block I have textboxes. To detect when the content differs from the original, the original data is stored in a variable.
<tr data-ng-repeat="p in products">
<td>
<textarea data-elastic data-ng-model="p.comment" data-ng-change="hasChanged=checkChange(original, rnd.comment);" data-ng-init="original=rnd.comment; hasChanged=false"></textarea>
</td>
<td class="save" ng-show="hasChanged" ng-click="save(p, original)">Save</td>
A save button is shown only when the content has changed. After a successful save the original value should be updated to the new value.
I can do it like this with a function in the controller:
$scope.save = function (p, original) {
//...successful save
this.original = p.comment; //this works
original = p.comment; //this does not
}
Relying on some implicit scope in the form of 'this' doesn't seem sensible.
Why doesn't updating the variable (original = ...) work? What's a smarter way to do this?
Based on comments I've updated it as follows:
ng-click="save(p, this)"
$scope.save = function (p, scope) {
//...successful save
scope.original = p.comment; //this works
}
This seems failrly sensible now. Is passing scope around like this considered bad practice or acceptable?
Products is defined as follows:
productStatusApp.controller('productStatusCtrl', function ($scope, $http, cid) {
$http.get('api/company/products/' + cid).success(function (data) {
$scope.products = data.products;
});
I've found the best way to avoid this kind of problems, is to use services
https://docs.angularjs.org/guide/services
http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
some rough code(use it just for pointers, not tested at all)
<tr data-ng-repeat="p in ProductsService.products">
<td>
<textarea data-elastic data-ng-model="p.comment"></textarea>
</td>
<td class="save" ng-show="p.originalComment!==p.comment" ng-click="ProductsService.save(p)">Save</td>
</tr>
and
var module = angular.module('app', []);
module.service('ProductsService', function () {
var products = [postA,postB,...,PostC];
products = products.map(function(p){p.originalComment=p.comment});
var save = function(p){
p.originalComment=p.comment;
someAjaxRequest(function _callback(err,response){....})
}
return {products:products,save:save};
});
and
module.controller('ProductsController', function ($scope, ProductsService) {
$scope.ProductsService= ProductsService;
});
They also allow better readability , WIN WIN
I have built a view model representing time slices with the following structure:
function TimeslotViewModel() {
this.timeslots = ko.observableArray();
this.updateTimeslots = function(timeslots) {
this.timeslots.destroyAll();
}
this.clearTimeslots = function() {
this.timeslots.destroyAll();
}
this.addTimeslot = function(timeslot) {
this.timeslots.push(timeslot);
}
}
function Timeslot(time, available) {
this.time = time;
this.available = available;
}
I'm trying to render this in a tabular format like so:
<div class="container">
<table class="table">
<thead>
<tr><th>Time</th><th>Status</th>
</thead>
<tbody data-bind="foreach: timeslots">
<td data-bind="text: time"></td>
<td data-bind="text: available"</td>
</tbody>
</table>
</div>
I've bound on page load:
$(function() {
ko.applyBindings(new TimeslotViewModel());
});
I'm trying to populate this table based on the callback result from an ajax call, but it doesn't seem to be working as expected. Here is what I tried:
$.getJSON(
"/myAjaxCall",
function (jsonData) {
var timeslotViewModel = new TimeslotViewModel();
timeslotViewModel.clearTimeslots();
$.each(jsonData, function (i, ts) {
var tsData = JSON.parse(ts);
var timeslot = new Timeslot(tsData.time, tsData.booked);
timeslotViewModel.addTimeslot(timeslot);
});
});
Unfortunately, I'm not seeing my view model's array get populated at all from this code. What is the right way to populate a view model based on a callback function's response?
You are creating a new viewmodel instead of updating the current one.
Replace this line
var timeslotViewModel = new TimeslotViewModel();
Either create a global viewmodel:
var myVm = new TimeslotViewModel();
ko.applyBindings(myVm);
//...
var timeslotViewModel = myVm;
Or get the current one from a node:
var timeslotViewModel = ko.contextFor($('.container').get(0)).$root
I've got a view that needs to be populated conditionally.
Scenario:
A manager will select a users name on screen B, then will be navigated to the same form the user filled in EG. screen A, except that the said manager will have the option to approve or deny the request of the user.
I've seen that in my VM on screen A I can do the following.
var vm = {
activate: function (data) {
console.log(data);
var id = data.id || -1;
if (id !== -1) {
router.isNavigating(true);
http.json('/api/user/'+ id )
.done(function (response) {
ko.viewmodel.updateFromModel(vm.userInfo, response);
router.isNavigating(false);
});
}
}
};
And then B (view & view model)
view
<table>
<thead>
<tr>
<td>User</td>
<td>Date Requested</td>
<td>Action</td>
</tr>
</thead>
<tbody>
<tr>
<td>Mike</td>
<td>19 Jun 2013
</td>
<td>
<input type="button" value="Go" data-bind="click: function() { buttons.goTo(6) }" />
</td>
</tr>
</tbody>
</table>
viewmodel
define(['durandal/plugins/router'], function (router) {
var buttons = {
goTo: function (id) {
console.log('goTo clicked');
//this does work in conjunction with my code on B
router.navigateTo('#/userinfo?id=' + id);
}
};
var vm = {
buttons: buttons
};
return vm;
});
My issue is that I'm not sure what the best way/or how to for that matter to get Durandal to navigate to page A from B... Is what I'm doing right? As it feels a little bit "hacky"
The navigation, at least to me, is designed to mimic standard MVC web navigation. In this case, since you already know that you want to go to 6, why not use an anchor like so
<a href="#/userinfo?id=6"/>
A better way would be to register your route with an id splat like so your route would become
routes.map({route: 'userinfo/:id, ...
<a href="#/userinfo/6" />
This way you can access the splat on the activate method..there are several examples out there but I don't have links to them. Basically the activate method of your userinfo viewmodel will accept a parameter and from there you can load an entity or whatever you like. Hope this helps.
Brad
I am new to Knockout and I am building a Simple POC for using knockout to build SPA(Single Page Application).
What I want to do is to show "Business Units" when the app loads and on selection of a business unit show all "Front End Units" under that business unit and on selection of a front end unit, show all "Sales Segments" under that front end unit.
All this will happen in a single page using the same view and the viewmodel will bind the model based on selected business unit or front end unit.
The issue I am facing is that, I have 5 business units that get bound properly first on document ready, but on selection of business unit, the front end units get repeated 5 times each. In this case, I have 2 front end units and each is shown 5 times. Same issue on selection of front end unit.
You can see this issue mimicked in the following jsFiddle sample - jsFiddle Link
Let me know if you can't access the jsfiddle link. In this sample, I have used arrays, but in actual I will be getting the data through async call to the oData service.
This is the view HTML:
<div id="divbu">
<h4 data-bind="text: Heading"></h4>
<ul data-role="listview" data-inset="true" data-bind="foreach: Collection">
<li data-role="list-divider" data-bind="text: EntityName"></li>
<li>
<a href="#" data-bind="click: $root.fnNextLevel">
<table border="0">
<tr>
<td>
<label style="font-size: 12px;">Bus. Plan: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: BusinessPlan"></label>
</td>
<td>
<label style="font-size: 12px;">Forecast: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: Forecast"></label>
</td>
</tr>
<tr>
<td>
<label style="font-size: 12px;">Gross Sales: </label>
</td>
<td colspan="3">
<label style="font-size: 12px;" data-bind="text: GrossSales"></label>
</td>
</tr>
</table>
</a>
</li>
</ul>
</div>
This is the model and view model:
function CommonModel(model, viewType) {
var self = this;
if (viewType == 'BU') {
self.EntityName = model[0];
self.BusinessUnit = model[0];
self.BusinessPlan = model[1];
self.Forecast = model[2];
self.GrossSales = model[3];
} else if (viewType == 'FEU') {
self.EntityName = model[1];
self.BusinessUnit = model[0];
self.FrontEndUnit = model[1];
self.BusinessPlan = model[2];
self.Forecast = model[3];
self.GrossSales = model[4];
} else if (viewType == 'SS') {
self.EntityName = model[2];
self.BusinessPlan = model[3];
self.Forecast = model[4];
self.GrossSales = model[5];
}
}
function ShipmentReportsViewModel(results, viewType) {
var self = this;
self.Collection = ko.observableArray([]);
for (var i = 0; i < results.length; i++) {
self.Collection.push(new CommonModel(results[i], viewType));
}
if (viewType == 'BU') {
self.Heading = "Business Units";
self.fnNextLevel = function (businessUnit) {
FetchFrontEndUnits(businessUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'FEU') {
self.Heading = results[0][0];
self.fnNextLevel = function (frontEndUnit) {
FetchSalesSegments(frontEndUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'SS') {
self.fnNextLevel = function () {
alert('No activity zone');
};
self.Heading = results[0][0] + ' - ' + results[0][1];
self.Home = function () {
FetchBusinessUnits();
};
}
}
You can see the complete code in the jsFiddle link.
I have also tried this with multiple views and multiple view models, where I apply bindings by giving the element ID. In this case, one flow from business unit -> sales segment is fine, but when I click on home or back button and I do binding again to that element, I face the same issue. (home and back button features are not done in jsFiddle example).
Let me know if more details are required. I did look into lot of other links in stack overflow, but nothing addressing this particular problem.
Any help is deeply appreciated. Thanks in advance.
The problem here is that you call your ko.applybindings TWICE and there is a foreach binding that iterate within 5 items, therefore the data are duplicated five times.
you should not call a ko.applybindings more than once on the same model.
Your model is always the same even if it's parametrized.
I had the same problem here: Data coming from an ObservableArray are displayed twice in my table
the fact that you have you business logic inside your viewModel is something that could be discussed, and it makes it not easy to fix this.
Make 3 classes, put them in a common model without logic inside. Then once you have applyed the ko.applyBindings once, you just have to modify the array like this:
viewModel.myArray(newValues)
Here is the fiddle with the amended code: http://jsfiddle.net/MaurizioPiccini/5B9Fd/17/
it does not do exaclty what you need but if remove the multiple bindings by moving the Collection object scope outside of your model.
As you can see the problem IS that you are calling the ko.applybindings twice on the same model.
Finally, I got this working. Thanks to #MaurizioIndenmark.
Though I have removed multiple call for ko.applybindings, I was still calling the view model multiple times. This was causing the issue.
Now, I have cleaner view model and I have different function calls for different actions and modify all the data required to be modified within these functions(events). Now, everything is working as expected.
This is how the view model looks now -
function ShipmentReportsViewModel(results) {
var self = this;
self.Heading = ko.observable();
self.BusinessUnits = ko.observableArray();
self.FrontEndUnits = ko.observableArray();
self.SalesSegments = ko.observableArray();
self.Home = function () {
var bu = FetchBusinessUnits();
self.Heading("Business Units");
self.BusinessUnits(bu);
self.FrontEndUnits(null);
self.SalesSegments(null);
};
self.fnFeu = function (businessUnit) {
var feu = FetchFrontEndUnits(businessUnit);
self.Heading(feu[0].BusinessUnit);
self.FrontEndUnits(feu);
self.BusinessUnits(null);
self.SalesSegments(null);
};
self.fnSalesSeg = function (frontEndUnit) {
var ss = FetchSalesSegments(frontEndUnit);
self.Heading(ss[0].BusinessUnit + ' - ' + ss[0].FrontEndUnit);
self.SalesSegments(ss);
self.BusinessUnits(null);
self.FrontEndUnits(null);
};
self.Home();
}
To see the entire working solution, please refer this jsFiddle
Thanks for all the valuable suggestions in getting this work.
Not sure what's going wrong here, but KnockoutJS is having some issues finding my observable array that's inside my MasterViewModel. Using 2.2.1 with jQuery 1.8.x as well as not my first KJS app. Here it is:
Initialize
$(function() {
window.vm = new MasterViewModel();
ko.applyBindings(vm);
});
ViewModel
function MasterViewModel(data) {
var self = this;
self.currentAppView = ko.observable();
// Users
self.userList = ko.observableArray([]);
self.templateListGetter = ko.computed(function() {
$.getJSON("/user/list"), function(data) {
var mapped = $.map(data, function(item) { return new userModel(item) });
self.userList(mapped);
};
});
self.goToAppView = function(appView) {
location.hash = '!/' + appView;
};
Sammy(function() {
this.get('#!/:appView', function() {
self.currentAppView(this.params.appView);
$('.appview').hide();
ko.applyBindings(new window[this.params.appView+'VM']());
});
this.notFound = function(){
location.hash = "!/dashboard";
}
//this.raise_errors = true;
}).run();
}
The View
<table class="table table-bordered table-striped">
<tbody data-bind="foreach: userList">
<tr>
<td data-bind="text: guid"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
<td data-bind="text: email"></td>
<td data-bind="text: updated"></td>
<td data-bind="text: suspended"></td>
</tr>
</tbody>
</table>
I have a simple table that I am loading
Even after double-checking a couple things like adding defer="defer" to my JS tag and ensuring the userList exists, it simply cannot find the observableArray. It gives the error:
Message: ReferenceError: userList is not defined;
Bindings value: foreach: userList Error {}
Anyone have any idea what's going on?
Update
For those wondering what gets called every time the hash changes:
function usersVM() {
// Data
var self = this;
// Behaviours
$('#users').show();
}
It looks like you're initializing knockout with an undefined viewmodel?
ko.applyBindings(new window[this.params.appView+'VM']());, yet your actual viewmodel is window.vm. Case sensitivity ftw. Also, the viewmodel on window is already created / initialized. So you don't need the new operator.
So, change the applyBindings line to be
ko.applyBindings(window[this.params.appView+'vm']());
Updated Answer: By Poster
There was no necessity to keep running ko.applyBindings every time the route changed since it was already applying bindings on page load. So Sammy.js was changed to:
Sammy(function() {
this.get('#!/:appView', function() {
self.currentAppView(this.params.appView);
$('.appview').hide();
window[this.params.appView+'Route']();
});
this.notFound = function(){
location.hash = "!/dashboard";
}
//this.raise_errors = true;
}).run();
It does look like ko.computed or a regular function call to window.vm.getUserList() isn't running properly, but this will be saved for a different question.
function usersRoute() {
// Data
var self = this;
// Behaviours
$('#users').show();
window.vm.getUserList();
}