JS FRAMEWORK/ LIBS:
I'm using Angular 1.5.x and lodash 4.x.
QUESTION:
I'm new to JS and struggling with how to inject a new object instance into a abstracted function (ideally factory). Per my service class FormService I have a function creates the FormModel(object) and hydrates its constructor - hydrateModel = function(arr). Is there anyway to abstract that to a generic function or factory class so that I can object a new generic object instance.
I can do this with backend languages but I'm struggling to make that type of syntax work with JS, which has me replicating the hydrateModel function for each data model instance (uGly). Said differently how can I make a OR ideally a . Appreciate any guidance. Ideally this makes sense and apologies if there is a basic answer.
EDIT - IDEAL OUTCOME
Would be to do something like this, which obviously doesn't work.
hydrateModel = function(arr, varObjectInstance) {
var newArr = [];
_.each(arr, function (obj,k) {
// is it possible in JS to inject a new object and reference it dynamically with a different set of dynamic arguments
newArr.push( [varObjectInstance](obj, fillable));
});
return newArr;
},
hydrateModel(arr, new ObjectInstance())
Example, in PHP you could say new $var($arguments)
REFERENCE CODE CONTEXT
// note I've removed the __constructor for brevity sake but it is a fairly basic object extend class
FormModel.$inject = ['__constructor', 'object'];
function FormModel(__constructor, object) {
function Form(data, keys) {
__constructor.call(this, data, keys);
}
Form.prototype = Object.create(__constructor.prototype);
Form.prototype.constructor = Form;
return Form;
}
FormService.$inject = ['FormModel', 'FormDataService'];
function FormService(FormModel,FormDataService) {
var service = this,
forms = {},
fillable = ['app_id','name','class','label','type','maxlength','minlength','placeholder','required','autocomplete','index','helpTitle','helpDescription','messages'],
hydrateModel = function(arr) {
var formEloquent = [];
_.each(arr, function (obj,k) {
formEloquent.push( new FormModel(obj, fillable));
});
return formEloquent;
};
// INIT function: 1. get form field (email, name, password,etc) data for 3 forms along with help messages etc.
service.initForms = function () {
var self = this;
_.each({
register:FormDataService.getRegisterData(), // not including FormDataService for brevity sake but it is http api service to backend
contact:FormDataService.getContactData(),
subscribe:FormDataService.getSubscribeData()
}, function (obj, key) {
forms[key] = {
model:{},
current:1,
// below is my question - how could create a function / factory hydrateModel(obj, newGenericObjectInstance) that would be generic so that I can call new [newGenericObjectInstance](arguments) here or in the hydrateModel
data:hydrateModel(obj),
view:{},
state:{},
help:{}
}
});
};
return {
initForms: function () {
return service.initForms();
}
}
}
DATA EXAMPLE
Example of a form field data row returned from FormDataService (basic)
var formFieldRowExample = {
id: '1010',
name: 'email',
class: 'form--input',
label: 'Enter your email',
type: 'email',
maxlength: 50,
minlength: 4,
placeholder: 'Example: person#example.com',
required: true,
autocomplete: 'on',
validation: [
{
type: 'email',
message: 'Email must be a valid email address'
},
{
type: 'minlength',
message: 'Your email address is too short'
},
{
type: 'maxlength',
message: 'Your email address is too long'
},
{
type: 'required',
message: 'Your email address is required'
}
]
};
In the example above varObjectInstance is an object, not constructor function. It cannot be called or newed.
The workable code may look like this
hydrateModel = function(arr, Cls) {
var newArr = [];
...
newArr.push(new Cls(obj, fillable));
...
}
hydrateModel(arr, ObjectInstance)
Related
I've been trying to create a custom widget for Netlify CMS to allow for key-value pairs to be inserted. However there are a few things going wrong, I'm thinking they might be related so I'm making a single question about them.
This is my first custom widget, and I’m mostly basing this on the official tutorial: https://www.netlifycms.org/docs/custom-widgets/
I’m using a Map as the value, but when I add a new element to the map, and then call the onChange(value) callback, nothing seems to happen. However, if I change it to onChange(new Map(value)) it does update. It seems that the onChange callback requires a new object?
Secondly, the value doesn’t seem to be actually saved. When I fill in other widgets and refresh the page, it then asks to restore the previous values. However it doesn’t restore the map, while restores the other values just fine.
And lastly, I get uncaught exception: Object like a second after I change anything to the map. My guess is that Netlify CMS is trying to save the map (debouncing it for a second so it doesn’t save every letter I type), but fails and throws that exception. That would explain the previous problem (the non-saving one).
My complete code for the custom widget currently is:
var IngredientsControl = createClass({
getDefaultProps: function () {
return {
value: new Map()
};
},
addElement: function (e) {
var value = this.props.value;
value.set("id", "Description");
//is.props.onChange(value);
this.props.onChange(new Map(value));
},
handleIdChange: function (oldId, newId) {
console.log(oldId, newId);
var value = this.props.value;
var description = value.get(oldId);
value.delete(oldId);
value.set(newId, description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
handleDescriptionChange: function (id, description) {
console.log(id, description);
var value = this.props.value;
value.set(id.toLowerCase(), description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
render: function () {
var value = this.props.value;
var handleIdChange = this.handleIdChange;
var handleDescriptionChange = this.handleDescriptionChange;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('input', { type: 'text', value: id, onChange: function (e) { handleIdChange(id, e.target.value); } }),
h('input', { type: 'text', value: description, onChange: function (e) { handleDescriptionChange(id, e.target.value); } })
);
items.push(li);
}
return h('div', { className: this.props.classNameWrapper },
h('input', {
type: 'button',
value: "Add element",
onClick: this.addElement
}),
h('ul', {}, items)
)
}
});
var IngredientsPreview = createClass({
render: function () {
var value = this.props.value;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('span', {}, id),
h('span', {}, ": "),
h('span', {}, description)
);
items.push(li);
}
return h('ul', {}, items);
}
});
CMS.registerWidget('ingredients', IngredientsControl, IngredientsPreview);
What am I doing wrong?
Thanks!
I solved this by using immutable-js's map: https://github.com/immutable-js/immutable-js
How to remove the repetitive code by providing method as parameter in javascript? Below is the code.
var toastrService = function (toastr) {
var _toastrService = this;
_toastrService.success =function(message,title) {
toastr.success(message,title);
}
_toastrService.info =function(message,title) {
toastr.info(message,title);
}
_toastrService.error =function(message,title) {
toastr.error(message,title);
}
_toastrService.warning =function(message,title) {
toastr.warning(message,title);
}
_toastrService.success =function(message,title) {
toastr.success(message,title);
}
}
Just iterate over an array of property strings:
['success', 'info', 'error', 'warning', 'success'].forEach((prop) => {
_toastrService[prop] = function(message, title) {
toastr[prop](message, title);
};
});
If you don't need to strip excess function arguments, you can trim it down to:
['success', 'info', 'error', 'warning', 'success'].forEach((prop) => {
_toastrService[prop] = toastr[prop].bind(toastr);
});
If the functions don't need a this of toastr, then you can leave out the .bind and just assign the plain function:
_toastrService[prop] = toastr[prop]
To make a global property injectable, simply declare it as an AngularJS value:
angular.module("app",[]).value("toastrService", toastr);
Then inject it where needed:
app.controller("ctrl", function (toastrService) {
toastrService.info("Title","Message");
});
For more information, see
AngularJS module type Reference - value
I have a model "User" that has a Many-to-One relationship with a "Subject".
User.js
attributes: {
subject: { model: 'subject' },
}
Subject.js
attributes: {
name: { type: 'string', unique: true, required: true },
}
When I call the blueprint create function for a User "/user" and pass in the data:
{
"name":"Test",
"subject":{"name":"Do Not Allow"}
}
It creates the user and also creates the Subject. However I do not want to allow the subject to be created, I only want to be able to attach an existing one. For example I would like it to reject the subject being created using the above data but allow the subject to be attached by using the below data.
{
"name":"Test",
"subject":1
}
I tried adding a policy (shown below) but this only stops the subject from being created using the URL "/subject" and not the nested create shown above.
'SubjectController':{
'create':false
}
Edit
To help understand what is going on here this is the lifecycle process it is going through:
Before Validation of Subject
After Validation of Subject
Before Creating Subject
After Creating Subject
Before Validation of User
After Validation of User
Before Creating User
Before Validation of User
After Validation of User
After Creating User
As you can see it is validating and creating the subject before it even gets to validating or creating the user.
You want to avoid the creation of an associated object when calling the blueprint creation route.
Create a policy (I've named it checkSubjectAndHydrate) and add it into the policies.js file:
// checkSubjectAndHydrate.js
module.exports = function (req, res, next) {
// We can create a user without a subject
if (_.isUndefined(req.body.subject)) {
return next();
}
// Check that the subject exists
Subject
.findOne(req.body.subject)
.exec(function (err, subject) {
if (err) return next(err);
// The subject does not exist, send an error message
if (!subject) return res.forbidden('You are not allowed to do that');
// The subject does exist, replace the body param with its id
req.body.subject = subject.id;
return next();
});
};
// policies.js
module.exports.policies = {
UserController: {
create: 'checkSubjectAndHydrate',
update: 'checkSubjectAndHydrate',
}
};
You should be passing the subject id (e.g. 1) instead of an object (e.g. { name: 'Hello, World!' }) containing the name of the subject as it's not necessarily unique.
If it is unique, you should replace the object by its id inside a beforeValidate for example.
// User.js
module.exports = {
...
beforeValidate: function (users, callback) {
// users = [{
// "name":"Test",
// "subject":{"name":"Do Not Allow"}
// }]
async.each(users, function replaceSubject(user, next) {
var where = {};
if (_.isObject(user.subject) && _.isString(user.subject.name)) {
where.name = user.subject.name;
} else if(_.isInteger(user.subject)) {
where.id = user.subject;
} else {
return next();
}
// Check the existence of the subject
Subject
.findOne(where)
.exec(function (err, subject) {
if (err) return next(err);
// Create a user without a subject if it does not exist
user.subject = subject? subject.id : null;
next();
});
}, callback);
// users = [{
// "name":"Test",
// "subject":1
// }]
}
};
You can create custom type for subject, and add your logic inside model. I'm not 100% sure I understood the attach sometimes part but maybe this could help:
models/User.js
module.exports = {
schema: true,
attributes: {
name: {
type: 'string'
},
subject: {
type: 'json',
myValidation: true
}
},
types: {
myValidation: function(value) {
// add here any kind of logic...
// for example... reject if someone passed name key
return !value.name;
}
}
};
You can find more info here http://sailsjs.org/documentation/concepts/models-and-orm/validations at the bottom of the page.
If I totally missed the point... The second option would be to add beforeCreate and beforeUpdate lifecycle callback to your model like this:
models/User.js
module.exports = {
schema: true,
attributes: {
name: {
type: 'string'
},
subject: {
type: 'json'
}
},
beforeCreate: function (values, cb) {
// for example... reject creating of subject if anything else then value of 1
if (values.subject && values.subject !== 1) return cb('make error obj...');
cb();
},
beforeUpdate: function (values, cb) {
// here you can add any kind of logic to check existing user or current update values that are going to be updated
// and allow it or not
return cb();
}
};
By using this you can use one logic for creating and another one for updating... etc...
You can find more info here: http://sailsjs.org/documentation/concepts/models-and-orm/lifecycle-callbacks
EDIT
Realized you have trouble with relation, and in above examples I thought you are handling type json...
module.exports = {
schema: true,
attributes: {
name: {
type: 'string'
},
subject: {
model: 'subject'
}
},
beforeValidate: function (values, cb) {
// subject is not sent at all, so we just go to next lifecycle
if (!values.subject) return cb();
// before we update or create... we will check if subject by id exists...
Subject.findOne(values.subject).exec(function (err, subject) {
// subject is not existing, return an error
if (err || !subject) return cb(err || 'no subject');
//
// you can also remove subject key instead of sending error like this:
// delete values.subject;
//
// subject is existing... continue with update
cb();
});
}
};
I fetch data from web api and push the data into observable array. I would like to make the observable array item to be observable. However, it seems that i could not access the object if i make it observable.
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function() {
$.ajax({
type: 'GET',
success: function() {
$.each(data.ProfileList, function (index, value) {
self.ProfileList.push(value);
alert(self.ProfileList()[index].Name) // success
}
}
});
}
self.GetProfile();
}
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function() {
$.ajax({
type: 'GET',
success: function() {
$.each(data.ProfileList, function (index, value) {
self.ProfileList.push(ko.observable(value));
alert(self.ProfileList()[index].Name) // fail. Object does not support property or method 'Name'
}
}
});
}
self.GetProfile();
}
you are directly pushing object (by making it observable) into observableArray does it sound right ? Nah (you may want to make Name as observable i believe) . Tough you can get the output by doing something like this self.ProfileList()[index]().Name check here
Preferred way :
viewModel:
function convert(data) {
this.Name = ko.observable(data.Name)
this.place = ko.observable(data.place)
this.age = ko.observable(data.age)
}
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function () {
var data = [{
'Name': 'Super',
'place': 'Ind',
'age': 25
}, {
'Name': 'Cool',
'place': 'Aus',
'age': 15
}]
//Manual way with function defined
//self.ProfileList(ko.utils.arrayMap(data, function (value) {
// return new convert(value)
//}))
//Using Mapping Plugin
ko.mapping.fromJS(data,{},self.ProfileList)
}
self.GetProfile();
}
ko.applyBindings(new KnockoutViewModel());
working sample here
Try to use the mapping module:
self.ProfileList.push(ko.mapping.fromJS(value));
This will automatically wrap value's properties in knockout observables.
I am working on a CMS that I originally was using Knockout but I decided to try Angular because it like more of its functionality. In the CMS, one of the sections will be 'Users'. It has a table that allows the headers to be clicked to sort the data. The controller is below:
userControllers.controller('UserListControl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.columns = [
{ 'field': 'last', 'label': 'Last Name' },
{ 'field': 'first', 'label': 'First Name' },
{ 'field': 'username', 'label': 'Username' },
{ 'field': 'email', 'label': 'Email' },
];
$scope.orderProp = 'last';
$scope.orderDirection = false;
$scope.tableSort = function(field) {
if ($scope.orderProp === field) {
$scope.orderDirection = !$scope.orderDirection;
}
$scope.orderProp = field;
};
$scope.tableSortClass = function(field) {
if ($scope.orderProp === field) {
if ($scope.orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
};
}]);
It is part of my adminApp that I created. Since there will be other sections that will also use the table sort properties (orderProp, orderDirection) and methods (tableSort, tableSortClass), is there a place I can put these methods so my eventual recordsController will also have access to them?
OK, so I am trying to create it using a service and factory function. This is all new to me so I am not completely sure what I am doing but here is what I have:
adminServices.factory('TableSort', [
function() {
var orderProp = 'id';
var orderDirection = false;
function sort(field) {
alert('test');
if (orderProp === field) {
orderDirection = !orderDirection;
}
orderProp = field;
}
function sortClass(field) {
if (orderProp === field) {
if (orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
}
}]);
I was hoping to access them in my html using something like ng-click="TableSort.sort(field)" but it doesn't work as it is right now.
As stated above in the other posts, you can create a service that you can inject into various controllers to "share" the code.
Below is a full example:
myApp.service('myService', function myService() {
return {
someVar: "Value",
augmentName: function(name){
return "Sir " + name;
}
}
});
This first piece is the service. I've defined a "myService" and given it one function "augmentName" Now you can inject the myService and access the augment name function.
myApp.controller('testCtrl', function ($scope, myService) {
$scope.testFunction = function(name){
console.log(myFunction.someVar); //This will access the service created variable
return myService.augmentName(name);
}
}
The controller injects the service and then calls it within one of its functions.
Now your HTML code should have access to the controller if you have defined an ng-controller with "testCtrl" or if you have put testCtrl as the controller in your router.
You can now call ng-model="testFunction(someName)" and it will resolve as expected.
Hope that helps. Let me know if you want me to go into greater depth
If you are still trying to figure out how everything in angular works, the angular phone cat tutorial helped me allot when I started out. I'd recommend donating an hour or so into playing with it.
Also, I highly recommend experimenting as early as possible with yeoman/angular generator, this will force you to use angular "the angular way" and can really help you with getting your project set up correctly.
You can use a Service or a Factory to hold these common methods. Additionally, you could use the $rootScope.
You can create a service and put all those properties and method in it. Here is an example for the same:
userControllers.service('UserListControl', function() {
var orderProp = 'last';
var orderDirection = false;
return {
tableSort : function(field) {
if (orderProp === field) {
orderDirection = !orderDirection;
}
orderProp = field;
};
tableSortClass: function(field) {
if (orderProp === field) {
if (orderDirection) {
return 'sortDesc';
}
return 'sortAsc';
}
};
}
});