I am using the Lodash and jQuery library inside my javascript and I am trying to figure out how to call a method that will allow me to truncate the results of a key value pair used to create a list inside my .html code. The html looks as follows:
<div class="slide-in-panel">
<ul class="list-unstyled slide-in-menu-navigation" data-bind="foreach: __plugins">
<li class="btn-block">
<div class="btn btn-default btn-block" data-bind="click: $parent.showPlugin, tooltip: 'Shoebox.Panel'">
<span data-bind="text: config.title"></span>
<em class="webgis-Icon webgis-Cross slide-in-menu-remove-shoebox-button"
data-bind="click: $parent.showRemoveConfirmBox, tooltip: 'Shoebox.RemoveShoebox'">
</em>
</div>
</li>
</ul>
</div>
The key component is the data-bind="text: config.title" part. This populates the list with name for that button. The config.title is created in the javascript file below. My goal is to apply a method such as .truncate() to the config.title part in the javascript to keep whatever name is being populated, from being to long. How would I do this?
return this.__backendShoeboxClient.createShoebox(this.__shoeboxName()).then((function(_this) {
return function(shoebox) {
return $when.join(shoebox.getName(), shoebox.getId(), shoebox.getUserName()).then(function(arg) {
var shoeboxId, shoeboxName, userName;
shoeboxName = arg[0], shoeboxId = arg[1], userName = arg[2];
return _this.__shoeboxContentFactory.create({
shoeboxId: shoeboxId,
shoeboxName: shoeboxName,
userName: userName
}).then(function(arg1) {
var activeShoeboxHandle, config, shoeboxContent;
shoeboxContent = arg1.shoeboxContent, activeShoeboxHandle = arg1.activeShoeboxHandle;
_this.__activeShoeboxHandleMain.loadModel(activeShoeboxHandle);
config = {
plugin: shoeboxContent,
title: shoeboxName,
userName: userName,
id: shoeboxId,
handle: activeShoeboxHandle,
icon: ""
};
_this.add(config, null, null);
activeShoeboxHandle.loadModel(shoebox);
_this.__shoeboxName.useDefaultValue();
return _this.__shoeboxName.clearError();
});
})["catch"](function(error) {
__logger__.error("Error while calling request " + error);
return $when.reject(new Error("Error while calling request. " + error));
});
};
})(this));
};
I am also trying to use the knockout style binding like this, but without any success:
<span data-bind="style: { textOverflow: ellipsis }, text: config.title"></span>
This should do it:
Use the truncate function like this: config.title = _.truncate(config.title, {'length': maxLength});
return this.__backendShoeboxClient.createShoebox(this.__shoeboxName()).then((function(_this) {
return function(shoebox) {
return $when.join(shoebox.getName(), shoebox.getId(), shoebox.getUserName()).then(function(arg) {
var shoeboxId, shoeboxName, userName;
shoeboxName = arg[0], shoeboxId = arg[1], userName = arg[2];
return _this.__shoeboxContentFactory.create({
shoeboxId: shoeboxId,
shoeboxName: shoeboxName,
userName: userName
}).then(function(arg1) {
var activeShoeboxHandle, config, shoeboxContent;
shoeboxContent = arg1.shoeboxContent, activeShoeboxHandle = arg1.activeShoeboxHandle;
_this.__activeShoeboxHandleMain.loadModel(activeShoeboxHandle);
config = {
plugin: shoeboxContent,
title: shoeboxName,
userName: userName,
id: shoeboxId,
handle: activeShoeboxHandle,
icon: ""
};
config.title = _.truncate(config.title, {'length': 15});
_this.add(config, null, null);
activeShoeboxHandle.loadModel(shoebox);
_this.__shoeboxName.useDefaultValue();
return _this.__shoeboxName.clearError();
});
})["catch"](function(error) {
__logger__.error("Error while calling request " + error);
return $when.reject(new Error("Error while calling request. " + error));
});
};
})(this));
};
So, for edification's sake, I was able to find a solution to this problem using the substring method inside a simple if statement. The issue seemed to be that I was putting this in the wrong part of my code so I want to clarify what worked for me for future readers. I was able to apply the following inside the key: value pair and it totally worked:
config =
plugin: shoeboxContent
title: if name.length > 24
"#{name.substring 0, 24}..."
else
name
userName: shoebox.getUserName()
id: shoebox.getId()
handle: activeShoeboxHandle
icon: ""
#add config, null, null
Related
I'm trying to make my website a little bit faster, and for that, I'm trying to make a button that on each click presents more images. For example: a user can see 5 images, and if the user wants to see 5 more he can, by clicking on the button.
So for now only got this, and i really think it's not the right way.
HTML ->
<ion-card *ngFor="let pic of photoList">
<h1>{{pic?.username}}</h1>
<h2>{{pic?.name}}</h2>
<img src={{pic?.picture}}>
</ion-card>
<button ion-button (click)="load()">Load More Images</button>
Js ->
load() {
firebase.database().ref('HomeList').limitToLast(5).on('value', snapshot => {
this.photoList = [];
snapshot.forEach(snap => {
this.photoList.push({
id: snap.key,
name: snap.val().name,
username: snap.val().username,
picture: snap.val().picture,
email: snap.val().email,
uid: snap.val().uid,
rating: snap.val().rating
});
console.log(this.photoList);
return false
});
return this.photoList.reverse();
});
}
so you need a pagination try to use .startAfter(number) and .limit(number); assuming this.current = 0; sets in constructor();
load() {
firebase.database().ref('HomeList').startAfter(this.current).limit(5).on('value', snapshot => {
this.photoList = [];
snapshot.forEach(snap => {
this.photoList.push({
id: snap.key,
name: snap.val().name,
username: snap.val().username,
picture: snap.val().picture,
email: snap.val().email,
uid: snap.val().uid,
rating: snap.val().rating
});
console.log(this.photoList);
this.current = this.current + photoList.length;
return false
});
return this.photoList.reverse();
});
}
I have no idea what's going wrong in my app. I'm trying to update a user profile. If a user has already a profile, it should display the current values of the profile. I have a SimpleSchema attached to the user collection.
<template name="updateCustomerProfile">
<div class="container">
<h1>Edit User</h1>
{{#if isReady 'updateCustomerProfile'}}
{{#autoForm collection="Users" doc=getUsers id="profileForm" type="update"}}
<fieldset>
{{> afQuickField name='username'}}
{{> afObjectField name='profile'}}
</fieldset>
<button type="submit" class="btn btn-primary">Update User</button>
<a class="btn btn-link" role="button" href="{{pathFor 'adminDocuments'}}">Back</a>
{{/autoForm}}
{{else}}
Nothing
{{/if}}
</div>
</template>
I have a template helper:
Template.updateCustomerProfile.events({
getUsers: function () {
//return Users.findOne();
return Meteor.user();
}
});
I have an Autoform hook
AutoForm.addHooks(['profileForm'], {
before: {
insert: function(error, result) {
if (error) {
console.log("Insert Error:", error);
AutoForm.debug();
} else {
console.log("Insert Result:", result);
AutoForm.debug();
}
},
update: function(error) {
if (error) {
console.log("Update Error:", error);
AutoForm.debug();
} else {
console.log("Updated!");
console.log('AutoForm.debug()');
}
}
}
});
Have the following route:
customerRoutes.route('/profile/edit', {
name: "updateCustomerProfile",
subscriptions: function (params, queryParams) {
this.register('updateCustomerProfile', Meteor.subscribe('usersAllforCustomer', Meteor.userId()));
},
action: function(params, queryParams) {
BlazeLayout.render('layout_frontend', {
top: 'menu',
main: 'updateCustomerProfile',
footer: 'footer'
});
}
});
and finally the following publication:
Meteor.publish('usersAllforCustomer', function (userId) {
check(userId, String);
var user = Users.findOne({_id: userId});
if (Roles.userIsInRole(this.userId, 'customer')) {
return Users.find({_id: userId});
}
});
And here is the collection:
Users = Meteor.users;
Schema = {};
Schema.UserProfile = new SimpleSchema({
firstName: {
type: String,
optional: true
},
lastName: {
type: String,
optional: true
},
gender: {
type: String,
allowedValues: ['Male', 'Female'],
optional: true
},
organization : {
type: String,
optional: true
}
});
Schema.User = new SimpleSchema({
username: {
type: String,
optional: true
},
emails: {
type: Array,
optional: true
},
"emails.$": {
type: Object
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date,
optional: true,
denyUpdate: true,
autoValue: function() {
if (this.isInsert) {
return new Date();
}
}
},
profile: {
type: Schema.UserProfile,
optional: true
},
services: {
type: Object,
optional: true,
blackbox: true
},
roles: {
type: [String],
optional: true
}
});
Meteor.users.attachSchema(Schema.User);
I'm sure the user object is passed in the publication. I can't update the profile: getting the following error (from Autoform debug):
Update Error: Object {$set: Object}
$set: Object
profile.firstName: "test_firstname"
profile.gender: "Female"
profile.lastName: "test_lastname"
profile.organization: "test_organisation
"username: "test_username"
How to go about updating a profile, staring blind....
You need to change your before AutoForm Hooks.
AutoForm.addHooks(['profileForm'], {
before: {
insert: function(doc) {
console.log('doc: ', doc);
return doc;
},
update: function(doc) {
console.log('doc: ', doc);
return doc;
},
},
});
While the after callback has the js standard (error, result) function signature, the before callback has just one parameter, the doc to insert/update. This is why you are always logging an 'error', it is just the doc you want to insert. Also you need to either return it, or pass it to this.result to actually insert/update the object in the db.
From the docs:
var hooksObject = {
before: {
// Replace `formType` with the form `type` attribute to which this hook applies
formType: function(doc) {
// Potentially alter the doc
doc.foo = 'bar';
// Then return it or pass it to this.result()
return doc; (synchronous)
//return false; (synchronous, cancel)
//this.result(doc); (asynchronous)
//this.result(false); (asynchronous, cancel)
}
},
There are a couple small issues so I'm not sure how to tackle your problem, but here's some things to address.
Publish Method
Local variable user is never used. Were you trying to use
it?
No need to include userId as a function parameter since you have
access to this.userId
The current user's profile is published by default so you don't need to use publish/subscribe unless you want to include/exclude fields, but then I would define a Meteor.publish(null, ...) so that it overrides the default current user publication
Note: If you remove publish usersAllforCustomer function, don't forget to remove it from route updateCustomerProfile
Use Global Helper currentUser
Here's how to update your template to use currentUser instead of getUsers
<template name="updateCustomerProfile">
<div class="container">
<h1>Edit User</h1>
{{#with currentUser}}
{{#autoForm collection="Users" doc=this id="profileForm" type="update"}}
<fieldset>
{{> afQuickField name='username'}}
{{> afObjectField name='profile'}}
</fieldset>
<button type="submit" class="btn btn-primary">Update User</button>
<a class="btn btn-link" role="button" href="{{pathFor 'adminDocuments'}}">Back</a>
{{/autoForm}}
{{else}}
Nothing
{{/with}}
</div>
</template>
Hope this helps.
The meteorpad indeed solved the issue. There was a mistake in the helper. In fact, the original code was:
Template.updateCustomerProfile.events({
getUsers: function () {
return Meteor.user();
}
});
So in above snippet I was using an 'events' instead of an 'helper'. Below is the correct code:
Template.updateCustomerProfile.helpers({
getUsers: function(){
return Meteor.user();
}
});
The non-GET instance action $save doesn't work in my example. I always get the Error, that $save is not a function. The problem is, I don't know where I have to define the $scope.example = new Resource();, because in my example I'm using 2 Controllers. One for the table list with objects and the other one for my modal window, where you can take CRUD operations. The CRUD operations are defined in an angular service.
The code is structured as follows:
Servie of Resource:
...
return {
name: $resource(baseUrl + '/api/name/:Id', {
Id: '#Id'
}, {
'update': {
method: 'PUT'
}
}),
...
Service of CRUD:
...
return {
create: function (newName) {
return newName.$save();
},
...
Ctrl of modal window:
$scope.selected = new resService.name();
$scope.createItem = function (newName) {
CrudService.create(newName).then(
function () {
$scope.dataSuccess = 'Person created.';
$scope.newName = null;
},
function (err) {
$scope.dataError = err.data.ModelState;
});
}
}
$scope.form = [{
label: 'Firstname',
fieldType: 'text',
name: 'Fname',
id: 'fname-id',
propertyName: 'fname',
disabled: false,
pattern: /^[a-zA-Z]{4}[a-zA-Z]*/,
required: true,
errRequired: 'Firstname is required.',
errPattern: 'Firstname has at least 4 letters.'
},
...];
The view with form:
<form class="form-horizontal" name="editForm" novalidate>
<div class="form-group-sm has-feedback" ng-repeat="elem in form" ng-class="{ 'has-error' : hasError(editForm, elem.name), 'has-success' : hasSuccess(editForm, elem.name) }">
<label class="control-label" for="{{elem.id}}">{{elem.label}}</label>
<input type="{{elem.fieldType}}"
class="form-control"
placeholder="{{elem.label}}"
name="{{elem.name}}"
id="{{elem.id}}"
ng-model="selected[elem.propertyName]"
ng-disabled="{{elem.disabled}}"
ng-pattern="elem.pattern"
ng-required="{{elem.required}}"
/>
<p class="help-block" ng-if="elem.errRequired" ng-show="editForm[elem.name].$error.required && editForm[elem.name].$touched">{{elem.errRequired}}</p>
<p class="help-block" ng-if="elem.errPattern" ng-show="editForm[elem.name].$error.pattern">{{elem.errPattern}}</p>
EDIT:
I'm getting a new Error. The console tells, that I have to use track by expression. But I was trying to use the form view without generating and then works. But I need the generated form view (the example view above).
Error Message:
Error: ngRepeat:dupes
Duplicate Key in Repeater
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys.
If you wan't to create a new object you need the choose the service between the Services choice (factory, service, providers).
The difference between a factory and a service, is about syntax. Just syntax.
.factory(function(){
//Private variables and functions
var x = "ez";
function getX(){
return x;
}
//Public functions (or variables)
return {
a : "test",
getA : function(){
return a;
}
}
})
//Service example
.service(function(){
//Handled by Angular:
//new() is used to create a new object
//Private functions and variables
var x = "test";
function getX(){
return x;
}
//Public funcitons (and variables)
this.a = function(){
"test";
};
this.getA = function(){
return a;
};
//Handeled by AngularJS
//return this;
});
Everything that is returned in the factory is available.
The service automaticaly creates a new object when calling it, which makes available the object ("this")
Calling a service or a factory remains the same:
var a = service.getA();
var a = factory.getA();
EDIT
Notice also that you can decide if your promise is going to the next error or success call.
Just as an exmaple:
xhr()
.then(success1, error1)
.then(success2, error2)
.then(success3, error3)
...
success and error are all callback functions.
By using $q you can go to the next success or error, wathever the callback.
QUESTION CODE
. factory ( 'YourFacotry' , [ '$resource' ,
function ( $resource ) {
return $resource ( '/api/note/:id' , { id : '#id' },
{
markAsDone :
{
url : '/api/note/:id/done' ,
method : 'POST' ,
isArray : true
}
});
}]);
Ctrl of modal window:
$scope.createItem = function () { //Forgot $scope here!
CrudService.query().then(
function () {
$scope.dataSuccess = 'Person created';
$scope.newName = null;
},
function (err) {
$scope.dataError = err.data.ModelState;
});
}
}
I'm a newbie to AngularJS with some fair knowledge with KnockoutJS also.
I'm trying to implement a search feature on 'products' in my ViewModel that is configurable by the end user by combining..
Search by 'name' of product
Search by 'tags' of product
in combination with search operations
CONTAINS
STARTS WITH
EQUALS
I believe you understood the functionality I am trying to build up.
The following is the ViewModel I'm using.
var InstantSearchController = function ($scope) {
var self = this;
$scope.filtersAvailable = [
{
displayText: 'Tag',
filterMethod: 'tagFilter',
description: 'Filter by Tags'
},
{
displayText: 'Description',
filterMethod: 'descriptionFilter',
description: 'Filter by description'
}
];
$scope.selectedFilter = $scope.filtersAvailable[1];
$scope.filterBehaviorsAvailable = [
{
displayText: 'CONTAINS',
regexPrefix: '',
regexPostfix: ''
},
{
displayText: 'STARTS WITH',
regexPrefix: '^',
regexPostfix: ''
},
{
displayText: 'EQUALS',
regexPrefix: '^',
regexPostfix: '$'
}
];
$scope.selectedFilterBehavior = $scope.filterBehaviorsAvailable[0];
$scope.products = [
{
name: 'Household Product',
description: 'Description household',
tags: ['personal', 'home']
},
{
name: 'Office product',
description: 'Business equipments',
tags: ['office', 'operations', 'business']
},
{
name: 'Misc products',
description: 'Uncategorized items',
tags: ['noclass']
}
];
}
Now, the following is my filters list.
var app = angular.module('InstantSearchModule', []);
//FILTERS BEGIN
app.filter('descriptionFilter', function () {
var filterFunction = function (data, filterBy) {
if (filterBy == null || filterBy === '')
return data;
var filtered = [];
var regExp = new RegExp(filterBy, 'gi');
angular.forEach(data, function (item) {
if (item.description.match(regExp))
filtered.push(item);
});
return filtered;
};
return filterFunction;
});
app.filter('tagFilter', function () {
var tagFilter = function (data, filterBy) {
if (filterBy == null || filterBy === '')
return data;
var filtered = [];
var regExp = new RegExp('^' + filterBy, 'gi');
debugger;
angular.forEach(data, function (item) {
var isMatching = false;
angular.forEach(item.tags, function (t) {
isMatching = isMatching || (t.match(regExp) != null);
});
if (isMatching)
filtered.push(item);
});
return filtered;
};
return tagFilter;
});
// FILTERS END
I have created a working part to configure search criteria including the 'filterString'(in a textbox), search operand[tags or description](with a select list) and a search mode[starts with / contains / equals](with another select list). Both of the filters are working fine if I specify the filter functions (tagFilter or descriptionFilter) directly in AngularJS directives as follows [JSFiddle Here].
<div data-ng-repeat="p in products|tagFilter:filterString|orderBy:'description.length'">
<h4 style="margin-bottom: 5px">{{$index+1}}. {{p.name}}</h4>
<div>
{{p.description}}
<button data-ng-repeat="t in p.tags|orderBy:'toString()'">{{t}}</button>
</div>
</div>
I was expecting the following to work for me as {{selectedFilter.filterMethod}} is rendering the value successfully, but is showing an error. Please see the HTML I tried to use for it.JSFiddle Here
<div data-ng-repeat="p in products|{{selectedFilter.filterMethod}}:filterString|orderBy:'description.length'">
<h4 style="margin-bottom: 5px">{{$index+1}}. {{p.name}}</h4>
<div>
{{p.description}}
<button data-ng-repeat="t in p.tags|orderBy:'toString()'">{{t}}</button>
</div>
</div>
I have attached the error I'm receiving in Google Chrome developer tools along with the resultant HTML to the subject. Please see below.
As you can see in the HTML, the filter method is not resolved and so, its not working for me. Do you guys have an advice what I am doing wrong?
If I understand it correctly all you need is a way to dynamically change filters. Everything else seems to be working.
I dont think you can use the syntax you are trying to use but you can make a third filter that injects the two others and chooses the right one depending on the parameters you send in.
New filter:
app.filter('multiFilter', function (descriptionFilterFilter, tagFilterFilter) {
var filterFunction = function (data, filterBy, filterRegExp, selectedFilter) {
if(selectedFilter.displayText === 'Description') {
return descriptionFilterFilter(data, filterBy, filterRegExp);
}
else {
return tagFilterFilter(data, filterBy, filterRegExp);
}
};
return filterFunction;
});
As you can see it also takes the filterRegExp and the selectedFilter as parameters. I also changed your old filters to take selectedFilter as a parameter.
Also notice that you have to append "Filter" to the filter name in order to inject it.
You call the new filter like this
multiFilter:filterString:filterRegExp:selectedFilter
So the div could loke something like this
<div data-ng-repeat="p in products|multiFilter:filterString:filterRegExp:selectedFilter|orderBy:'description.length'"
title="{{selectedFilter.filterMethod}}">
<h4 style="margin-bottom: 5px">{{$index+1}}. {{p.name}}</h4>
<div>
I made a working fork of your fiddle
Your fiddle is not working and has other error but, the reason filters are not loading is that you have used global controller function and not registered with your app module for the injection to work. Your filter belong to module InstantSearchModule but you controller does not.
Try the module registration syntax
app.controller('InstantSearchController',function($scope) {
});
see the Angular guide on controller https://code.angularjs.org/1.2.15/docs/guide/controller
Update: As it turns out the issue is not with dependency injection. It is because you cannot use expression to dynamically change filter. When i set to fixed filter it works fine
<div data-ng-repeat="p in products|descriptionFilter:filterString|orderBy:'description.length'"
title="{{selectedFilter.filterMethod}}">
You would have to either combine then or find a way to do select filtering.
See my fix here
http://jsfiddle.net/cmyworld/pW9EZ/1/
i am using knockoutjs v3.1.0. i am trying to build a master-detail like view. the problem i am having is that elements are not showing (though they are hiding). my mock code is at http://jsfiddle.net/jwayne2978/qC4RF/3/
this is my html code.
<div data-bind="foreach: users">
<div>Row</div>
<div data-bind="text: username"></div>
<div data-bind="visible: showDetails">
<div data-bind="text: address"></div>
</div>
<div>
<a href="#"
data-bind="click: $root.toggleDetails">
Toggle Div
</a>
</div>
this is my javascript code
var usersData = [
{ username: "test1", address: "123 Main Street" },
{ username: "test2", address: "234 South Street" }
];
var UsersModel = function (users) {
var self = this;
self.users = ko.observableArray(
ko.utils.arrayMap(users, function (user) {
return {
username: user.username,
address: user.address,
showDetails: false
};
}));
self.toggleDetails = function (user) {
user.showDetails = !user.showDetails;
console.log(user);
};
};
ko.applyBindings(new UsersModel(usersData));
what's supposed to happen is that when a user clicks on the link, the corresponding HTML div should show. the console clearly shows that the property is being changed on the user object, but the HTML element's visibility is not changing. i also explicitly made the showDetails property observable, but that did not help.
showDetails : ko.observable(false)
any help is appreciated.
var UsersModel = function (users) {
var self = this;
//var flag=ko.observable(true);
self.users = ko.observableArray(
ko.utils.arrayMap(users, function (user) {
return {
username: user.username,
address: user.address,
showDetails: ko.observable(false) //it should be observable
};
}));
self.toggleDetails = function (user) {
user.showDetails(!user.showDetails());
console.log(user);
};
};
Fiddle Demo