AngularJS directive not loading - html

I have written a directive in AngularJS and trying to use the directive in a html page.
directive.js
.directive('formFieldNew', function($timeout, FieldTypes) {
return {
restrict : 'EA',
templateUrl : '/common-licensing/resources/partials/subscriber/template.html',
replace : true,
scope : {
record : '=',
field : '#',
live : '#',
required : '#'
},
link : function($scope, element, attr) {
$scope.$on('record:invalid', function() {
$scope[$scope.field].$setDirty();
});
$scope.types = FieldTypes;
$scope.remove = function(field) {
delete $scope.record[field];
$scope.blurUpdate();
};
$scope.blurUpdate = function() {
if ($scope.live !== 'false') {
$scope.record.$update(function(updatedRecord) {
$scope.record = updatedRecord;
});
}
};
var saveTimeout;
$scope.update = function() {
$timeout.cancel(saveTimeout);
saveTimeout = $timeout($scope.blurUpdate, 1000);
};
}
};
new.html
<form name='newContact' novalidate class='form-horizontal'>
<form-field-new ng-repeat='(k,v) in productTempDetailsLists' record='productTempDetailsLists' field='{{k}}'></form-field-new>
<button class='btn btn-primary' ng-click='save()' ng-disabled='newContact.$invalid'> Create Contact </button>
</form>
template.html
<div>
<input ng-model='record[field][0]' type='{{record[field][1]}}' class='form control'/>
</div>
When I run the file I am getting the below error in my console.
Error: [$compile:tplrt] http://errors.angularjs.org/1.3.15/$compile/tplrt?p0=formFieldNew&p1=%2Fcommon-licensing%2Fresources%2Fpartials%2Fsubscriber%2Ftemplate.html
at Error (native)
at http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:6:417
at http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:65:275
at http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:112:113
at n.$eval (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:126:15)
at n.$digest (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:123:106)
at n.$apply (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:126:293)
at l (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:81:240)
at M (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:85:342)
at XMLHttpRequest.F.onload (http://localhost:8080/common-licensing/resources/lib/angular/angular.min.js:86:367)
I tried adding the <div> tag for my new.html file as per the error log but it did not work. Please help me out.

I had similar issue, wherein templateURL doesn't work with absolute URL's. To see this check this out wherein you will find same error http://plnkr.co/edit/NoSZjfIiCiRrCaBbsHiE?p=preview
Use relative URL's and Try below
.directive('formFieldNew', function($timeout, FieldTypes) {
return {
restrict : 'E',
//Not sure if this is correct relative path. check
templateUrl : '../common-licensing/resources/partials/subscriber/template.html',
replace : true,
scope : {
record : '=',
field : '#',
live : '#',
required : '#'
},
link : function($scope, element, attr) {
$scope.$on('record:invalid', function() {
$scope[$scope.field].$setDirty();
});
$scope.types = FieldTypes;
$scope.remove = function(field) {
delete $scope.record[field];
$scope.blurUpdate();
};
$scope.blurUpdate = function() {
if ($scope.live !== 'false') {
$scope.record.$update(function(updatedRecord) {
$scope.record = updatedRecord;
});
}
};
var saveTimeout;
$scope.update = function() {
$timeout.cancel(saveTimeout);
saveTimeout = $timeout($scope.blurUpdate, 1000);
};
}
};

Related

Directive changes color and text on a fly

This is a directive that should change the color and text of the element depending on the incoming data
function colorStatus() {
return {
restrict: 'AE',
scope: {
status: '#'
},
link: function (scope, element) {
let status = +scope.status;
switch (status) {
case 0:
element.text(' ');
element.css('color', '#FFFFFF');
break;
case 1:
element.text('Correct!');
element.css('color', '#4CAF50');
break;
case 2:
element.text('Error!');
element.css('color', '#F44336');
break;
case 3:
element.text('Waiting...');
element.css('color', '#FF9800');
break;
}
}
};
}
Initially, it receives resolved data from the controller.
Here is HTML:
<color-status status="{{vm.status}}"></color-status>
<button ng-click="vm.changeStatus()"><button>
Here is function from controller:
vm.changeStatus = changeStatus;
vm.status = chosenTask.status; // It equals 0 in the received data
function changeStatus() {
vm.status = 1;
}
I expect that the text and color of the directive will change, but this does not happen. Where is my mistake?
Post link is only called once
The problem you're having is that you set your element's text and color in your link function. This means that when your directive instantiates and goes through initialisation, the link function will be executed, but it will get executed exactly once. When the value of status changes, you're not handling those changes to reflect the, on your element. Therefore you should add $onChanges() function to your directive and handle those changes.
function StatusController($element) {
this.$element = $element;
this.status = 0;
}
StatusController.mapper = [
{ text: ' ', color: '#FFFFFF' },
{ text: 'Correct!', color: '#4CAF50' },
{ text: 'Error!', color: '#F44336' },
{ text: 'Waiting...', color: '#FF9800' },
}];
StatusController.prototype.setStatus = function(status) {
var statusObj = StatusController.mapper[+status];
this.$element
.text(statusObj.text)
.css('color', statusObj.color);
}
StatusController.prototype.$onInit = function() {
// this.status is now populated by the supplied attribute value
this.setStatus(this.status);
}
StatusController.prototype.$onChanges = function(changes) {
if (changes.status && !changes.status.isFirstChange()) {
this.setStatus(this.status);
}
}
var StatusDirective = {
restrict: 'AE',
controller: StatusController,
scope: true,
bindToController: {
status: '#'
}
};
angular.module('someModule')
.directive('colorStatus', function() { return StatusDirective; });
But apart from this, I also suggest you set element's text by using ng-bind or {{...}} to put that value in. Directive could populate its public properties instead and use those in HTML along with CSS. It's always wiser to not manipulate DOM elements from within AngularJS code if possible.
function StatusController($element) {
this.$element = $element;
this.status = 0;
this.text = '';
this.name = '';
}
StatusController.mapper = [
{ text: ' ', name: '' },
{ text: 'Correct!', name: 'correct' },
{ text: 'Error!', name: '#error' },
{ text: 'Waiting...', name: 'pending' },
}];
StatusController.prototype.setStatus = function(status) {
var statusObj = StatusController.mapper[+status];
this.text = statusObj.text;
this.name = statusObj.name;
}
StatusController.prototype.$onInit = function() {
// this.status is now populated by the supplied attribute value
this.setStatus(this.status);
}
StatusController.prototype.$onChanges = function(changes) {
if (changes.status && !changes.status.isFirstChange()) {
this.setStatus(this.status);
}
}
var StatusDirective = {
restrict: 'AE',
controller: StatusController,
controllerAs: 'colorStatus',
scope: true,
bindToController: {
status: '#'
}
};
angular.module('someModule')
.directive('colorStatus', function() { return StatusDirective; });
And then in your template write use it this way:
<color-status status="{{vm.status}}" ng-class="colorStatus.name" ng-bind="colorStatus.text"></color-status>
This will give you a lot more flexibility in templates. Instead of setting text in the controller you could get away with just class name and use pseudo classes to add text to the element however you please to do, so each instance of your <color-status> directive could then display differently for the same status value.

Saving whole data in a list at once and then displaying one by one or changing the html content of single div

I am creating an app using "vue.js" in which there are few videos and there is a button, video title and video description for each video, so that when a user clicks on the button it will only display the related video, its title and the description but the video titles and descriptions of all other videos will be hidden. Now there are two ways to do this:
1. By creating the list of all the video titles and descriptions with default display property as none and then changing the display property to block of only one video title and description on the button click. For ex:
onBtnClick: function(event, index) {
var videoContainer = "#videoContainer" + index;
$(videoContainer).css("display", "block);
}
.container {
display: none;
}
<div v-for="(video, index) in json._videos" id="'videoContainer'+index" class="container">
<div v-if="video.title">{{ video.title }}</div>
<div v-if="video.decription">{{ video.decription }}</div>
</div>
<div id="buttonContainer">
<button v-for="(video, index) in json._videos" id="'button'+index" v-on:click="onBtnClick($event,index)">{{ video.buttonText }}</button>
</div>
By creating the single video container and then changing the html content of the video title and description using v-html directive. For ex:
onBtnClick: function(event, index) {
getVideoTitle(index);
getVideoDescription(index);
}
getVideoTitle: function(index) {
return json._videos[index].title;
}
getVideoDescription: function(index) {
return json._videos[index].description;
}
.container {
display: none;
}
<div id="'videoContainer'+index" class="container">
<div v-if="json._videos" v-html="getVideoTitle()"></div>
<div v-if="json._videos" v-html="getVideoDescription()"></div>
</div>
<div id="buttonContainer">
<button v-for="(video, index) in json._videos" id="'button'+index" v-on:click="onBtnClick($event,index)">{{ video.buttonText }}</button>
</div>
Guys please tell me which one is the better approach and why?
You can create two Vue components: VideoList and single Video.
In your Video component you should have attribute "isVisible" (or with other name, if you want), which will be set from VideoList component for the specifig Video component.
This approach grant you ability to separate your logic and gives full control for every single video and for all set of such videos.
Here is the full working example:
https://jsfiddle.net/yh1xrk27/22/
var VideoList = Vue.component('video-list', {
template: $('#video-list').html(),
data: function () {
return {
videos: [
]
};
},
mounted: function () {
this.videos = [
{
title: 'Video 1',
description: 'The first video',
isVisible: true
},
{
title: 'Video 2',
description: 'The second video',
isVisible: false
}
];
},
methods: {
show: function (index) {
var self = this;
for (var key in this.videos) {
this.videos[key].isVisible = (key == index);
}
}
}
});
var VideoItem = Vue.component('video-item', {
template: $('#video-item').html(),
props: [
'fields'
],
data: function () {
return {
title: '',
description: '',
isVisible: false
};
},
created: function () {
this.title = this.fields.title;
this.description = this.fields.description;
this.isVisible = this.fields.isVisible;
}
});
var demo = new Vue({
el: '#app'
});
Answer on author's comment:
You can get your date from any desired source, it's not the problem.
Also you do not have to define all fields for every single video. Every field in VideoItem have deafult value and when you assign attributes from property "fields", you should just check definition of every field. For example:
Replace this:
created: function () {
this.title = this.fields.title;
this.description = this.fields.description;
this.isVisible = this.fields.isVisible;
}
On this:
created: function () {
this.title = typeof this.fields.title !== 'undefined' ? this.fields.title : 'SOME_DEFAULT_VALUE';
this.description = typeof this.fields.title !== 'undefined' ? this.fields.description ? 'SOME_DEFAULT_VALUE';
this.isVisible = typeof this.fields.title ? this.fields.isVisible : false; // or true, depends on you
}
Or you can create separate method for setting data about videos and call this method in created hook of the component VideoList:
methods: {
...
setData: function(){
this.title = typeof this.fields.title !== 'undefined' ? this.fields.title : 'SOME_DEFAULT_VALUE';
this.description = typeof this.fields.title !== 'undefined' ? this.fields.description ? 'SOME_DEFAULT_VALUE';
this.isVisible = typeof this.fields.title ? this.fields.isVisible : false; // or true, depends on you
}
}
And in your VideoList component you can load information about videos from anywhere, from internal or external resource. For example:
created: function(){
var self = this;
$.ajax({
// load data from some internal resource or paste some external resource link
url: '/source/videos.json',
dataType: 'json',
success: function(jsonData){
// call setData method of this component
self.setData(jsonData);
},
});
}
So you don't have any limits about your JSON, do what you want with your raw JSON data and paste it in VideoList component, that's all.
I will suggest one more variable, use a data option of vue, and have a data variable for your videos. The benefit is that you can use the same variable at multiple places and it is reactive as well, i.e. change in that variable will be reflected in the view.
data:{
return videos: []
}
methods: {
onBtnClick: function(event, index) {
updateVideoTitle(index);
updateVideoDescription(index);
}
updateVideoTitle: function(index) {
this.videos[index] = Object.assign(this.videos[index], {title: json._videos[index].title});
}
updateVideoDescription: function(index) {
this.videos[index] = Object.assign(this.videos[index], {description: json._videos[index].description});
}
}
and in HTML:
<div v-for="(video, index) in videos" id="'videoContainer'+index" class="container">
<div v-if="video.title">{{ video.title }}</div>
<div v-if="video.decription">{{ video.decription }}</div>
</div>
<div id="buttonContainer">
<button v-for="(video, index) in videos" id="'button'+index" v-on:click="onBtnClick($event,index)">{{ video.buttonText }}</button>
</div>

typeahead / filter / JSON parse?

Trying to 'parse/read' an external .json file on my typeahead code, but the .json file (which I cannot modify) looks like:
{"**cms_countries**":
[{"**cms_country**":
[{"**countrydisplayname**":"Afghanistan"}
,{"countrydisplayname":"Albania"} ,{"countrydisplayname":"Algeria"}
... ... ... ,{"countrydisplayname":"Zimbabwe"} ] } ,{"TotalRecords":
[ {"TotalRecords":"246"} ] } ] }
So, I think my problem is to know how to parse/read/assimilate/integrate/adopt this .json file, having
cms_countries ,
cms_country ,
and then, my countrydisplayname field on it. (have you seen the tree here ?)
This is my code:
$(document).ready(function() {
var searchablePlaces = new Bloodhound({
datumTokenizer : Bloodhound.tokenizers.obj.whitespace("countrydisplayname"),
queryTokenizer : Bloodhound.tokenizers.whitespace,
prefetch : 'countries.json',
remote : {
url : 'countries/%QUERY.json',
wildcard : '%QUERY',
filter : function(response) { return response.cms_country; }
},
limit : 10
});
searchablePlaces.initialize();
$('#remote .typeahead').typeahead(
{
hint : true,
highlight : true,
minLength : 2
},
{
name : 'countrydisplayname',
displayKey : "countrydisplayname",
source : searchablePlaces.ttAdapter()
})
});
But of course, it is not working:
ANY hint on how to organize my filter... ? or how to do to overcome my nested .json wrappers....
OK, I've got my code working now:
$(window).load(function(){
var movies = new Bloodhound({
limit: 10,
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'countries.json',
filter: function (movies) {
return $.map(movies.cms_countries[0].cms_country, function (paises) {
return {
value: paises.countrydisplayname
};
});
}
}
});
// Initialize the Bloodhound suggestion engine
movies.initialize();
// Instantiate the Typeahead UI
$('.typeahead').typeahead(
{
hint: true,
highlight: true,
minLength: 1
},
{
//displayKey: 'value',
displayKey: function (toto) {
return toto.value;
},
source: movies.ttAdapter()
});
});

How do i test my custom angular schema form field

I've just started developing with Angular schema form and I'm struggling to write any tests for my custom field directive.
I've tried compiling the schema form html tag which runs through my directives config testing it's display conditions against the data in the schema. However it never seems to run my controller and I can't get a reference to the directives HTML elements. Can someone give me some guidance on how to get a reference to the directive? Below is what I have so far:
angular.module('schemaForm').config(['schemaFormProvider',
'schemaFormDecoratorsProvider', 'sfPathProvider',
function(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) {
var date = function (name, schema, options) {
if (schema.type === 'string' && schema.format == 'date') {
var f = schemaFormProvider.stdFormObj(name, schema, options);
f.key = options.path;
f.type = 'date';
options.lookup[sfPathProvider.stringify(options.path)] = f;
return f;
}
};
schemaFormProvider.defaults.string.unshift(date);
schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'date',
'app/modules/json_schema_form/schema_form_date_picker/schema_form_date_picker.html');
}]);
var dateControllerFunction = function($scope) {
$scope.isCalendarOpen = false;
$scope.showCalendar = function () {
$scope.isCalendarOpen = true;
};
$scope.calendarSave = function (date) {
var leaf_model = $scope.ngModel[$scope.ngModel.length - 1];
var formattedDate = $scope.filter('date')(date, 'yyyy-MM-dd');
leaf_model.$setViewValue(formattedDate);
$scope.isCalendarOpen = false;
};
};
angular.module('schemaForm').directive('schemaFormDatePickerDirective', ['$filter', function($filter) {
return {
require: ['ngModel'],
restrict: 'A',
scope: false,
controller : ['$scope', dateControllerFunction],
link: function(scope, iElement, iAttrs, ngModelCtrl) {
scope.ngModel = ngModelCtrl;
scope.filter = $filter
}
};
}]);
<div ng-class="{'has-error': hasError()}">
<div ng-model="$$value$$" schema-form-date-picker-directive>
<md-input-container>
<!-- showTitle function is implemented by ASF -->
<label ng-show="showTitle()">{{form.title}}</label>
<input name="dateTimePicker" ng-model="$$value$$" ng-focus="showCalendar()" ng-disabled="isCalendarOpen">
</md-input-container>
<time-date-picker ng-model="catalogue.effectiveFrom" ng-if="isCalendarOpen" on-save="calendarSave($value)" display-mode="date"></time-date-picker>
</div>
<!-- hasError() defined by ASF -->
<span class="help-block" sf-message="form.description"></span>
</div>
And the spec:
'use strict'
describe('SchemaFormDatePicker', function() {
var $compile = undefined;
var $rootScope = undefined;
var $scope = undefined
var scope = undefined
var $httpBackend = undefined;
var elem = undefined;
var html = '<form sf-schema="schema" sf-form="form" sf-model="schemaModel"></form>';
var $templateCache = undefined;
var directive = undefined;
beforeEach(function(){
module('app');
});
beforeEach(inject(function(_$compile_, _$rootScope_, _$templateCache_, _$httpBackend_) {
$compile = _$compile_
$rootScope = _$rootScope_
$httpBackend = _$httpBackend_
$templateCache = _$templateCache_
}));
beforeEach(function(){
//Absorb call for locale
$httpBackend.expectGET('assets/locale/en_gb.json').respond(200, {});
$templateCache.put('app/modules/json_schema_form/schema_form_date_picker/schema_form_date_picker.html', '');
$scope = $rootScope.$new()
$scope.schema = {
type: 'object',
properties: {
party: {
title: 'party',
type: 'string',
format: 'date'
}}};
$scope.form = [{key: 'party'}];
$scope.schemaModel = {};
});
describe("showCalendar", function () {
beforeEach(function(){
elem = $compile(html)($scope);
$scope.$digest();
$httpBackend.flush();
scope = elem.isolateScope();
});
it('should set isCalendarOpen to true', function(){
var result = elem.find('time-date-picker');
console.log("RESULT: "+result);
));
});
});
});
If you look at the below example taken from the project itself you can see that when it uses $compile it uses angular.element() first when setting tmpl.
Also, the supplied test module name is 'app' while the code sample has the module name 'schemaForm'. The examples in the 1.0.0 version of Angular Schema Form repo all use sinon and chai, I'm not sure what changes you would need to make if you do not use those.
Note: runSync(scope, tmpl); is a new addition for 1.0.0 given it is now run through async functions to process $ref includes.
/* eslint-disable quotes, no-var */
/* disabling quotes makes it easier to copy tests into the example app */
chai.should();
var runSync = function(scope, tmpl) {
var directiveScope = tmpl.isolateScope();
sinon.stub(directiveScope, 'resolveReferences', function(schema, form) {
directiveScope.render(schema, form);
});
scope.$apply();
};
describe('sf-array.directive.js', function() {
var exampleSchema;
var tmpl;
beforeEach(module('schemaForm'));
beforeEach(
module(function($sceProvider) {
$sceProvider.enabled(false);
exampleSchema = {
"type": "object",
"properties": {
"names": {
"type": "array",
"description": "foobar",
"items": {
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string",
"default": 6,
},
},
},
},
},
};
})
);
it('should not throw needless errors on validate [ノಠ益ಠ]ノ彡┻━┻', function(done) {
tmpl = angular.element(
'<form name="testform" sf-schema="schema" sf-form="form" sf-model="model" json="{{model | json}}"></form>'
);
inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
scope.model = {};
scope.schema = exampleSchema;
scope.form = [ "*" ];
$compile(tmpl)(scope);
runSync(scope, tmpl);
tmpl.find('div.help-block').text().should.equal('foobar');
var add = tmpl.find('button').eq(1);
add.click();
$rootScope.$apply();
setTimeout(function() {
var errors = tmpl.find('.help-block');
errors.text().should.equal('foobar');
done();
}, 0);
});
});
});

BackboneJS - fetching collections from model

I have a JSON file which basically looks like this:
[
{
"First" : [...]
},
{
"Second" : [...]
},
{
"Third" : [...]
},
]
In my router i have:
this.totalCollection = new TotalCollection();
this.totalView = new TotalView({el:'#subContent', collection:this.totalCollection});
this.totalCollection.fetch({success: function(collection) {
self.totalView.collection=collection;
self.totalView.render();
}});
Now i have my Backbone Model:
define([
"jquery",
"backbone"
],
function($, Backbone) {
var TotalModel = Backbone.Model.extend({
url: "/TotalCollection.json",
initialize: function( opts ){
this.first = new First();
this.second = new Second();
this.third = new Third();
this.on( "change", this.fetchCollections, this );
},
fetchCollections: function(){
this.first.reset( this.get( "First" ) );
this.second.reset( this.get( "Second" ) );
this.third.reset( this.get( "Third" ) );
}
});
return TotalModel;
});
and my in my Backbone View i try to render the collection(s):
render: function() {
$(this.el).html(this.template(this.collection.toJSON()));
return this;
}
But I get the Error "First is not defined" - whats the issue here?
Have you actually defined a variable 'First', 'Second' and 'Third'? Based on what you're showing here, there is nothing with that name. One would expect you to have a couple lines like..
var First = Backbone.Collection.extend({});
var Second = Backbone.Collection.extend({});
var Third = Backbone.Collection.extend({});
However you haven't provided anything like that, so my first assumption is that you just haven't defined it.
Per comments, this may be more what you need:
render: function() {
$(this.el).html(this.template({collection: this.collection.toJSON())});
return this;
}
Then..
{{#each collection}}
{{#each First}}
/*---*/
{{/each}}
{{/each}}