Commit 2f171c7f by Rashid Khan

Merge branch 'master' into scratchy

parents aec9a42e d0f90b00
...@@ -11,7 +11,8 @@ define([ ...@@ -11,7 +11,8 @@ define([
'angular-sanitize', 'angular-sanitize',
'angular-strap', 'angular-strap',
'angular-dragdrop', 'angular-dragdrop',
'extend-jquery' 'extend-jquery',
'bindonce'
], ],
function (angular, $, _, appLevelRequire) { function (angular, $, _, appLevelRequire) {
"use strict"; "use strict";
...@@ -84,7 +85,8 @@ function (angular, $, _, appLevelRequire) { ...@@ -84,7 +85,8 @@ function (angular, $, _, appLevelRequire) {
'$strap.directives', '$strap.directives',
'ngSanitize', 'ngSanitize',
'ngDragDrop', 'ngDragDrop',
'kibana' 'kibana',
'pasvaz.bindonce'
]; ];
_.each('controllers directives factories services filters'.split(' '), _.each('controllers directives factories services filters'.split(' '),
......
...@@ -479,6 +479,26 @@ function($, _, moment) { ...@@ -479,6 +479,26 @@ function($, _, moment) {
}); });
}; };
// Find the smallest missing number in an array
kbn.smallestMissing = function(arr,start,end) {
start = start || 0;
end = end || arr.length-1;
if(start > end) {
return end + 1;
}
if(start !== arr[start]) {
return start;
}
var middle = Math.floor((start + end) / 2);
if (arr[middle] > middle) {
return kbn.smallestMissing(arr, start, middle);
} else {
return kbn.smallestMissing(arr, middle + 1, end);
}
};
kbn.byteFormat = function(size, decimals) { kbn.byteFormat = function(size, decimals) {
var ext, steps = 0; var ext, steps = 0;
decimals = decimals || 2; decimals = decimals || 2;
......
...@@ -20,6 +20,7 @@ require.config({ ...@@ -20,6 +20,7 @@ require.config({
'angular-sanitize': '../vendor/angular/angular-sanitize', 'angular-sanitize': '../vendor/angular/angular-sanitize',
timepicker: '../vendor/angular/timepicker', timepicker: '../vendor/angular/timepicker',
datepicker: '../vendor/angular/datepicker', datepicker: '../vendor/angular/datepicker',
bindonce: '../vendor/angular/bindonce',
underscore: 'components/underscore.extended', underscore: 'components/underscore.extended',
'underscore-src': '../vendor/underscore', 'underscore-src': '../vendor/underscore',
...@@ -84,7 +85,7 @@ require.config({ ...@@ -84,7 +85,7 @@ require.config({
'angular-resource': ['angular'], 'angular-resource': ['angular'],
'angular-route': ['angular'], 'angular-route': ['angular'],
'angular-touch': ['angular'], 'angular-touch': ['angular'],
'bindonce': ['angular'],
'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'], 'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'],
timepicker: ['jquery', 'bootstrap'], timepicker: ['jquery', 'bootstrap'],
......
...@@ -2,12 +2,6 @@ ...@@ -2,12 +2,6 @@
"title": "New Dashboard", "title": "New Dashboard",
"services": { "services": {
"query": { "query": {
"idQueue": [
1,
2,
3,
4
],
"list": { "list": {
"0": { "0": {
"query": "*", "query": "*",
...@@ -21,11 +15,6 @@ ...@@ -21,11 +15,6 @@
] ]
}, },
"filter": { "filter": {
"idQueue": [
0,
1,
2
],
"list": {}, "list": {},
"ids": [] "ids": []
} }
......
...@@ -2,12 +2,6 @@ ...@@ -2,12 +2,6 @@
"title": "Introduction", "title": "Introduction",
"services": { "services": {
"query": { "query": {
"idQueue": [
1,
2,
3,
4
],
"list": { "list": {
"0": { "0": {
"query": "*", "query": "*",
...@@ -23,11 +17,6 @@ ...@@ -23,11 +17,6 @@
] ]
}, },
"filter": { "filter": {
"idQueue": [
0,
1,
2
],
"list": {}, "list": {},
"ids": [] "ids": []
} }
......
...@@ -2,12 +2,6 @@ ...@@ -2,12 +2,6 @@
"title": "Your Basic Dashboard", "title": "Your Basic Dashboard",
"services": { "services": {
"query": { "query": {
"idQueue": [
1,
2,
3,
4
],
"list": { "list": {
"0": { "0": {
"query": "*", "query": "*",
...@@ -23,11 +17,6 @@ ...@@ -23,11 +17,6 @@
] ]
}, },
"filter": { "filter": {
"idQueue": [
0,
1,
2
],
"list": {}, "list": {},
"ids": [] "ids": []
} }
......
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
"title": "Logstash Search", "title": "Logstash Search",
"services": { "services": {
"query": { "query": {
"idQueue": [
1
],
"list": { "list": {
"0": { "0": {
"query": "{{ARGS.query || '*'}}", "query": "{{ARGS.query || '*'}}",
...@@ -20,9 +17,6 @@ ...@@ -20,9 +17,6 @@
] ]
}, },
"filter": { "filter": {
"idQueue": [
1
],
"list": { "list": {
"0": { "0": {
"type": "time", "type": "time",
......
...@@ -2,12 +2,6 @@ ...@@ -2,12 +2,6 @@
"title": "A few notes", "title": "A few notes",
"services": { "services": {
"query": { "query": {
"idQueue": [
1,
2,
3,
4
],
"list": { "list": {
"0": { "0": {
"query": "*", "query": "*",
...@@ -23,11 +17,6 @@ ...@@ -23,11 +17,6 @@
] ]
}, },
"filter": { "filter": {
"idQueue": [
0,
1,
2
],
"list": {}, "list": {},
"ids": [] "ids": []
} }
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
<select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span> <select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
</div> </div>
<div class="span2"> <div class="span2">
<h6>Trim Factor <tip>Trim fields to this long divided by # of rows</tip></h6> <h6>Trim Factor <tip>Trim fields to this long divided by # of rows. Requires data refresh.</tip></h6>
<input type="number" class="input-small" ng-model="panel.trimFactor"> <input type="number" class="input-small" ng-model="panel.trimFactor" ng-change="set_refresh(true)">
</div> </div>
</div> </div>
\ No newline at end of file
...@@ -8,24 +8,29 @@ ...@@ -8,24 +8,29 @@
</style> </style>
<div class="row-fluid"> <div class="row-fluid">
<div ng-class="{'span3':panel.field_list}" ng-show="panel.field_list"> <div bindonce ng-class="{'span3':panel.field_list}" ng-if="panel.field_list">
<div class="sidebar-nav"> <div class="sidebar-nav">
<strong>Fields <i class=" icon-chevron-sign-left pointer " ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Hide field list'" ng-show="panel.field_list"></i></strong><p> <strong>Fields <i class=" icon-chevron-sign-left pointer " ng-click="panel.field_list = !panel.field_list" bs-tooltip="'Hide field list'" ng-show="panel.field_list"></i></strong><p>
<div class="small"> <div class="small">
<span class="link" ng-click="panel.all_fields = true;" ng-class="{strong:panel.all_fields}">All</span> / <span class="link" ng-click="panel.all_fields = true;" ng-class="{strong:panel.all_fields}">
<span class="link" ng-click="panel.all_fields = false;" ng-class="{strong:!panel.all_fields}">Current</span> All ({{fields.list.length}})</span><br>
<span class="link" ng-click="panel.all_fields = false;" ng-class="{strong:!panel.all_fields}">
Current ({{current_fields.length || 0}})</span>
</div> </div>
<div><input type="text" class="input-medium" placeholder="Type to filter..." ng-model="fieldFilter"></div> <div><input type="text" class="input-medium" placeholder="Type to filter..." ng-model="fieldFilter"></div>
<ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;" ng-if="panel.all_fields">
<ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;"> <li ng-style="panel.style" ng-repeat="field in fields.list|filter:fieldFilter|orderBy:identity">
<li ng-style="panel.style" ng-repeat="field in fields.list|filter:fieldFilter|orderBy:identity" ng-show="panel.all_fields"> <i class="pointer" ng-class="{'icon-check': columns[field],'icon-check-empty': _.isUndefined(columns[field])}" ng-click="toggle_field(field)"></i>
<i class="pointer" ng-class="{'icon-check': _.contains(panel.fields,field),'icon-check-empty': !_.contains(panel.fields,field)}" ng-click="toggle_field(field)"></i> <a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: columns[field]}" bo-text="field"></a>
<a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: _.contains(panel.fields,field)}">{{field}}</a>
</li> </li>
<li ng-style="panel.style" ng-repeat="field in current_fields|filter:fieldFilter|orderBy:identity" ng-hide="panel.all_fields"> </ul>
<i class="pointer" ng-class="{'icon-check': _.contains(panel.fields,field),'icon-check-empty': !_.contains(panel.fields,field)}" ng-click="toggle_field(field)"></i>
<a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: _.contains(panel.fields,field)}">{{field}}</a> <ul class="unstyled" style="{{panel.overflow}}:{{panel.height || row.height}};overflow-y:auto;overflow-x:hidden;" ng-if="!panel.all_fields">
<li ng-style="panel.style" ng-repeat="field in current_fields|filter:fieldFilter|orderBy:identity">
<i class="pointer" ng-class="{'icon-check': columns[field],'icon-check-empty': _.isUndefined(columns[field])}" ng-click="toggle_field(field)"></i>
<a class="pointer" data-unique="1" bs-popover="'app/panels/table/micropanel.html'" data-placement="rightTop" ng-click="toggle_micropanel(field,true)" ng-class="{label: columns[field]}" bo-text="field"></a>
</li> </li>
</ul> </ul>
...@@ -61,12 +66,12 @@ ...@@ -61,12 +66,12 @@
</th> </th>
</thead> </thead>
<tbody ng-repeat="event in data| slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'"> <tbody bindonce ng-repeat="event in data| slice:panel.offset:panel.offset+panel.size" ng-class-odd="'odd'">
<tr ng-click="toggle_details(event)" class="pointer"> <tr ng-click="toggle_details(event)" class="pointer">
<td ng-show="panel.fields.length<1">{{event._source|stringify|tableTruncate:panel.trimFactor:1}}</td> <td ng-if="panel.fields.length<1" bo-text="event._source|stringify|tableTruncate:panel.trimFactor:1"></td>
<td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields" ng-bind-html-unsafe="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></td> <td ng-show="panel.fields.length>0" ng-repeat="field in panel.fields" bo-html="(event.kibana.highlight[field]||event.kibana._source[field]) |tableHighlight | tableTruncate:panel.trimFactor:panel.fields.length"></td>
</tr> </tr>
<tr ng-show="event.kibana.details"> <tr ng-if="event.kibana.details">
<td colspan={{panel.fields.length}} ng-switch="event.kibana.view"> <td colspan={{panel.fields.length}} ng-switch="event.kibana.view">
<span> <span>
View: View:
...@@ -82,18 +87,18 @@ ...@@ -82,18 +87,18 @@
<th>Value</th> <th>Value</th>
</thead> </thead>
<tr ng-repeat="(key,value) in event.kibana._source track by $index" ng-class-odd="'odd'"> <tr ng-repeat="(key,value) in event.kibana._source track by $index" ng-class-odd="'odd'">
<td>{{key}}</td> <td bo-text="key"></td>
<td style="white-space:nowrap"> <td style="white-space:nowrap">
<i class='icon-search pointer' ng-click="build_search(key,value)" bs-tooltip="'Add filter to match this value'"></i> <i class='icon-search pointer' ng-click="build_search(key,value)" bs-tooltip="'Add filter to match this value'"></i>
<i class='icon-ban-circle pointer' ng-click="build_search(key,value,true)" bs-tooltip="'Add filter to NOT match this value'"></i> <i class='icon-ban-circle pointer' ng-click="build_search(key,value,true)" bs-tooltip="'Add filter to NOT match this value'"></i>
<i class="pointer icon-th" ng-click="toggle_field(key)" bs-tooltip="'Toggle table column'"></i> <i class="pointer icon-th" ng-click="toggle_field(key)" bs-tooltip="'Toggle table column'"></i>
</td> </td>
<!-- At some point we need to create a more efficient way of applying the filter pipeline --> <!-- At some point we need to create a more efficient way of applying the filter pipeline -->
<td style="white-space:pre-wrap" ng-bind-html-unsafe="value|noXml|urlLink|stringify"></td> <td style="white-space:pre-wrap" bo-html="value|noXml|urlLink|stringify"></td>
</tr> </tr>
</table> </table>
<pre style="white-space:pre-wrap" ng-bind-html="without_kibana(event)|tableJson:2" ng-switch-when="json"></pre> <pre style="white-space:pre-wrap" bo-html="without_kibana(event)|tableJson:2" ng-switch-when="json"></pre>
<pre ng-bind-html="without_kibana(event)|tableJson:1" ng-switch-when="raw"></pre> <pre bo-html="without_kibana(event)|tableJson:1" ng-switch-when="raw"></pre>
</td> </td>
</tr> </tr>
</tbody> </tbody>
......
...@@ -129,6 +129,11 @@ function (angular, app, _, kbn, moment) { ...@@ -129,6 +129,11 @@ function (angular, app, _, kbn, moment) {
_.defaults($scope.panel,_d); _.defaults($scope.panel,_d);
$scope.init = function () { $scope.init = function () {
$scope.columns = {};
_.each($scope.panel.fields,function(field) {
$scope.columns[field] = true;
});
$scope.Math = Math; $scope.Math = Math;
$scope.identity = angular.identity; $scope.identity = angular.identity;
$scope.$on('refresh',function(){$scope.get_data();}); $scope.$on('refresh',function(){$scope.get_data();});
...@@ -203,8 +208,10 @@ function (angular, app, _, kbn, moment) { ...@@ -203,8 +208,10 @@ function (angular, app, _, kbn, moment) {
$scope.toggle_field = function(field) { $scope.toggle_field = function(field) {
if (_.indexOf($scope.panel.fields,field) > -1) { if (_.indexOf($scope.panel.fields,field) > -1) {
$scope.panel.fields = _.without($scope.panel.fields,field); $scope.panel.fields = _.without($scope.panel.fields,field);
delete $scope.columns[field];
} else { } else {
$scope.panel.fields.push(field); $scope.panel.fields.push(field);
$scope.columns[field] = true;
} }
}; };
......
...@@ -184,17 +184,19 @@ function (angular, $, kbn, _, config, moment, Modernizr) { ...@@ -184,17 +184,19 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
// Set the current dashboard // Set the current dashboard
self.current = _.clone(dashboard); self.current = _.clone(dashboard);
// Ok, now that we've setup the current dashboard, we can inject our services // Delay this until we're sure that querySrv and filterSrv are ready
querySrv = $injector.get('querySrv'); $timeout(function() {
filterSrv = $injector.get('filterSrv'); // Ok, now that we've setup the current dashboard, we can inject our services
querySrv = $injector.get('querySrv');
// Make sure these re-init filterSrv = $injector.get('filterSrv');
querySrv.init();
filterSrv.init(); // Make sure these re-init
querySrv.init();
// If there's an interval set, the indices have not been calculated yet, filterSrv.init();
// so there is no data. Call refresh to calculate the indices and notify the panels. },0).then(function() {
self.refresh(); // Call refresh to calculate the indices and notify the panels that we're ready to roll
self.refresh();
});
if(dashboard.refresh) { if(dashboard.refresh) {
self.set_interval(dashboard.refresh); self.set_interval(dashboard.refresh);
......
...@@ -14,20 +14,22 @@ function (angular, _, config) { ...@@ -14,20 +14,22 @@ function (angular, _, config) {
this.list = ['_type']; this.list = ['_type'];
this.mapping = {}; this.mapping = {};
this.fullMapping = {};
$rootScope.$watch(function(){return dashboard.indices;},function(n) { $rootScope.$watch(function(){return dashboard.indices;},function(n) {
if(!_.isUndefined(n) && n.length) { if(!_.isUndefined(n) && n.length) {
// Only get the mapping for indices we don't know it for // Only get the mapping for indices we don't know it for
var indices = _.difference(n,_.keys(self.mapping)); var indices = _.difference(n,_.keys(self.fullMapping));
// Only get the mapping if there are new indices // Only get the mapping if there are new indices
if(indices.length > 0) { if(indices.length > 0) {
self.map(indices).then(function(result) { self.map(indices).then(function(result) {
self.mapping = _.extend(self.mapping,result); self.fullMapping = _.extend(self.fullMapping,result);
self.list = mapFields(self.mapping); self.list = mapFields(self.fullMapping);
}); });
// Otherwise just use the cached mapping // Otherwise just use the cached mapping
} else { } else {
self.list = mapFields(_.pick(self.mapping,n)); // This is inefficient, should not need to reprocess?
self.list = mapFields(_.pick(self.fullMapping,n));
} }
} }
}); });
...@@ -57,12 +59,13 @@ function (angular, _, config) { ...@@ -57,12 +59,13 @@ function (angular, _, config) {
} }
}); });
// Flatten the mapping of each index into dot notated keys.
return request.then(function(p) { return request.then(function(p) {
var mapping = {}; var mapping = {};
_.each(p.data, function(v,k) { _.each(p.data, function(type,index) {
mapping[k] = {}; mapping[index] = {};
_.each(v, function (v,f) { _.each(type, function (fields,typename) {
mapping[k][f] = flatten(v); mapping[index][typename] = flatten(fields);
}); });
}); });
return mapping; return mapping;
......
...@@ -14,14 +14,12 @@ define([ ...@@ -14,14 +14,12 @@ define([
// Defaults for it // Defaults for it
var _d = { var _d = {
idQueue : [],
list : {}, list : {},
ids : [] ids : []
}; };
// For convenience // For convenience
var ejs = ejsResource(config.elasticsearch); var ejs = ejsResource(config.elasticsearch);
var _f = dashboard.current.services.filter;
// Save a reference to this // Save a reference to this
var self = this; var self = this;
...@@ -34,7 +32,6 @@ define([ ...@@ -34,7 +32,6 @@ define([
// Accessors // Accessors
self.list = dashboard.current.services.filter.list; self.list = dashboard.current.services.filter.list;
self.ids = dashboard.current.services.filter.ids; self.ids = dashboard.current.services.filter.ids;
_f = dashboard.current.services.filter;
_.each(self.list,function(f) { _.each(self.list,function(f) {
self.set(f,f.id,true); self.set(f,f.id,true);
...@@ -97,8 +94,6 @@ define([ ...@@ -97,8 +94,6 @@ define([
delete self.list[id]; delete self.list[id];
// This must happen on the full path also since _.without returns a copy // This must happen on the full path also since _.without returns a copy
self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id); self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id);
_f.idQueue.unshift(id);
_f.idQueue.sort(function(v,k){return v-k;});
_r = true; _r = true;
} else { } else {
_r = false; _r = false;
...@@ -230,10 +225,14 @@ define([ ...@@ -230,10 +225,14 @@ define([
}; };
var nextId = function() { var nextId = function() {
if(_f.idQueue.length > 0) { var idCount = dashboard.current.services.filter.ids.length;
return _f.idQueue.shift(); if(idCount > 0) {
// Make a sorted copy of the ids array
var ids = _.clone(dashboard.current.services.filter.ids).sort();
return kbn.smallestMissing(ids);
} else { } else {
return self.ids.length; // No ids currently in list
return 0;
} }
}; };
......
...@@ -14,7 +14,6 @@ function (angular, _, config, kbn) { ...@@ -14,7 +14,6 @@ function (angular, _, config, kbn) {
// Create an object to hold our service state on the dashboard // Create an object to hold our service state on the dashboard
dashboard.current.services.query = dashboard.current.services.query || {}; dashboard.current.services.query = dashboard.current.services.query || {};
_.defaults(dashboard.current.services.query,{ _.defaults(dashboard.current.services.query,{
idQueue : [],
list : {}, list : {},
ids : [], ids : [],
}); });
...@@ -31,7 +30,6 @@ function (angular, _, config, kbn) { ...@@ -31,7 +30,6 @@ function (angular, _, config, kbn) {
// For convenience // For convenience
var ejs = ejsResource(config.elasticsearch); var ejs = ejsResource(config.elasticsearch);
var _q = dashboard.current.services.query;
// Holds all actual queries, including all resolved abstract queries // Holds all actual queries, including all resolved abstract queries
var resolvedQueries = []; var resolvedQueries = [];
...@@ -176,10 +174,6 @@ function (angular, _, config, kbn) { ...@@ -176,10 +174,6 @@ function (angular, _, config, kbn) {
delete self.list[id]; delete self.list[id];
// This must happen on the full path also since _.without returns a copy // This must happen on the full path also since _.without returns a copy
self.ids = dashboard.current.services.query.ids = _.without(self.ids,id); self.ids = dashboard.current.services.query.ids = _.without(self.ids,id);
_q.idQueue.unshift(id);
_q.idQueue.sort(function(v,k){
return v-k;
});
return true; return true;
} else { } else {
return false; return false;
...@@ -246,10 +240,14 @@ function (angular, _, config, kbn) { ...@@ -246,10 +240,14 @@ function (angular, _, config, kbn) {
}; };
var nextId = function() { var nextId = function() {
if(_q.idQueue.length > 0) { var idCount = dashboard.current.services.query.ids.length;
return _q.idQueue.shift(); if(idCount > 0) {
// Make a sorted copy of the ids array
var ids = _.clone(dashboard.current.services.query.ids).sort();
return kbn.smallestMissing(ids);
} else { } else {
return self.ids.length; // No ids currently in list
return 0;
} }
}; };
......
...@@ -712,11 +712,15 @@ angular.module('$strap.directives').directive('bsTabs', [ ...@@ -712,11 +712,15 @@ angular.module('$strap.directives').directive('bsTabs', [
scope.$watch(iAttrs.ngModel, function (newValue, oldValue) { scope.$watch(iAttrs.ngModel, function (newValue, oldValue) {
if (angular.isUndefined(newValue)) if (angular.isUndefined(newValue))
return; return;
console.log(oldValue +" -> "+ newValue);
activeTab = newValue; activeTab = newValue;
setTimeout(function () { setTimeout(function () {
var $next = $($tabs[0].querySelectorAll('li')[newValue * 1]); // Check if we're still on the same tab before making the switch
if (!$next.hasClass('active')) { if(activeTab === newValue) {
$next.children('a').tab('show'); var $next = $($tabs[0].querySelectorAll('li')[newValue * 1]);
if (!$next.hasClass('active')) {
$next.children('a').tab('show');
}
} }
}); });
}); });
......
'use strict';
/**
* Bindonce - Zero watches binding for AngularJs
* @version v0.2.1 - 2013-05-07
* @link https://github.com/Pasvaz/bindonce
* @author Pasquale Vazzana <pasqualevazzana@gmail.com>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
angular.module('pasvaz.bindonce', [])
.directive('bindonce', function()
{
var toBoolean = function(value)
{
if (value && value.length !== 0)
{
var v = angular.lowercase("" + value);
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
}
else
{
value = false;
}
return value;
}
var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
if (isNaN(msie))
{
msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
}
var bindonceDirective =
{
restrict: "AM",
controller: ['$scope', '$element', '$attrs', '$interpolate', function($scope, $element, $attrs, $interpolate)
{
var showHideBinder = function(elm, attr, value)
{
var show = (attr == 'show') ? '' : 'none';
var hide = (attr == 'hide') ? '' : 'none';
elm.css('display', toBoolean(value) ? show : hide);
}
var classBinder = function(elm, value)
{
if (angular.isObject(value) && !angular.isArray(value))
{
var results = [];
angular.forEach(value, function(value, index)
{
if (value) results.push(index);
});
value = results;
}
if (value)
{
elm.addClass(angular.isArray(value) ? value.join(' ') : value);
}
}
var ctrl =
{
watcherRemover : undefined,
binders : [],
group : $attrs.boName,
element : $element,
ran : false,
addBinder : function(binder)
{
this.binders.push(binder);
// In case of late binding (when using the directive bo-name/bo-parent)
// it happens only when you use nested bindonce, if the bo-children
// are not dom children the linking can follow another order
if (this.ran)
{
this.runBinders();
}
},
setupWatcher : function(bindonceValue)
{
var that = this;
this.watcherRemover = $scope.$watch(bindonceValue, function(newValue)
{
if (newValue == undefined) return;
that.removeWatcher();
that.runBinders();
}, true);
},
removeWatcher : function()
{
if (this.watcherRemover != undefined)
{
this.watcherRemover();
this.watcherRemover = undefined;
}
},
runBinders : function()
{
var i, max;
for (i = 0, max = this.binders.length; i < max; i ++)
{
var binder = this.binders[i];
if (this.group && this.group != binder.group ) continue;
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
switch(binder.attr)
{
case 'if':
if (toBoolean(value))
{
binder.transclude(binder.scope.$new(), function (clone)
{
var parent = binder.element.parent();
var afterNode = binder.element && binder.element[binder.element.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
angular.forEach(clone, function(node)
{
parentNode.insertBefore(node, afterNextSibling);
});
});
}
break;
case 'hide':
case 'show':
showHideBinder(binder.element, binder.attr, value);
break;
case 'class':
classBinder(binder.element, value);
break;
case 'text':
binder.element.text(value);
break;
case 'html':
binder.element.html(value);
break;
case 'style':
binder.element.css(value);
break;
case 'src':
binder.element.attr(binder.attr, value);
if (msie) binder.element.prop('src', value);
case 'attr':
angular.forEach(binder.attrs, function(attrValue, attrKey)
{
var newAttr, newValue;
if (attrKey.match(/^boAttr./) && binder.attrs[attrKey])
{
newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
newValue = binder.scope.$eval(binder.attrs[attrKey]);
binder.element.attr(newAttr, newValue);
}
});
break;
case 'href':
case 'alt':
case 'title':
case 'id':
case 'value':
binder.element.attr(binder.attr, value);
break;
}
}
this.ran = true;
this.binders = [];
}
}
return ctrl;
}],
link: function(scope, elm, attrs, bindonceController)
{
var value = (attrs.bindonce) ? scope.$eval(attrs.bindonce) : true;
if (value != undefined)
{
bindonceController.runBinders();
}
else
{
bindonceController.setupWatcher(attrs.bindonce);
elm.bind("$destroy", bindonceController.removeWatcher);
}
}
};
return bindonceDirective;
});
angular.forEach(
[
{directiveName:'boShow', attribute: 'show'},
{directiveName:'boIf', attribute: 'if', transclude: 'element', terminal: true, priority:1000},
{directiveName:'boHide', attribute:'hide'},
{directiveName:'boClass', attribute:'class'},
{directiveName:'boText', attribute:'text'},
{directiveName:'boHtml', attribute:'html'},
{directiveName:'boSrcI', attribute:'src', interpolate:true},
{directiveName:'boSrc', attribute:'src'},
{directiveName:'boHrefI', attribute:'href', interpolate:true},
{directiveName:'boHref', attribute:'href'},
{directiveName:'boAlt', attribute:'alt'},
{directiveName:'boTitle', attribute:'title'},
{directiveName:'boId', attribute:'id'},
{directiveName:'boStyle', attribute:'style'},
{directiveName:'boValue', attribute:'value'},
{directiveName:'boAttr', attribute:'attr'}
],
function(boDirective)
{
var childPriority = 200;
return angular.module('pasvaz.bindonce').directive(boDirective.directiveName, function()
{
var bindonceDirective =
{
priority: boDirective.priority || childPriority,
transclude: boDirective.transclude || false,
terminal: boDirective.terminal || false,
require: '^bindonce',
compile: function (tElement, tAttrs, transclude)
{
return function(scope, elm, attrs, bindonceController)
{
var name = attrs.boParent;
if (name && bindonceController.group != name)
{
var element = bindonceController.element.parent();
bindonceController = undefined;
var parentValue;
while (element[0].nodeType != 9 && element.length)
{
if ((parentValue = element.data('$bindonceController'))
&& parentValue.group == name)
{
bindonceController = parentValue
break;
}
element = element.parent();
}
if (!bindonceController)
{
throw Error("No bindonce controller: " + name);
}
}
bindonceController.addBinder(
{
element : elm,
attr : boDirective.attribute,
attrs : attrs,
value : attrs[boDirective.directiveName],
interpolate : boDirective.interpolate,
group : name,
transclude : transclude,
scope : scope
});
}
}
}
return bindonceDirective;
});
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment