I want to load css on demand but I don't get it.
This is the file structure
index.html
<script src="js/ui-router-styles.js"></script>
<script src="js/app.js"></script>
<script src="js/dao.js"></script>
<script src="js/controllers.js"></script>
....
</head>
<body ng-app="starter" ui-router-styles>
<div ui-view></div>
</body>
app.js
var my_app = angular.module('starter', ['starter.posicion_integrada', 'uiRouterStyles','ionic','pascalprecht.translate']);
config part
my_app.config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.backButton.previousTitleText(false);
$ionicConfigProvider.backButton.text('');
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'views/menu.html',
controller: 'appController'
})
.state('app.CarrouselSearcher', {
url: '/CarrouselSearcher/:entUsuario/:codUsuario',
views: {
'menuContent': {
templateUrl: 'views/carrouselSearcher.html',
controller: 'carrouselsearcherCtrl',
data: {
css: ['css/tabla.css', 'css/carousel.css']
}
}
}
})
I found a problem of your code in ui-router-styles.js
is
for(var state = toState; state && state.name !== ''; state=$$parentState(state)) {
if(state && state.data && state.data.css) {
if(!Array.isArray(state.data.css)) {
state.data.css = [state.data.css];
}
angular.forEach(state.data.css, function(css) {
// css = {name: 'layout', href: '/layout.css'}
if(typeof css === 'object' && css.name && css.href && !stylesObject[css.name]) {
stylesObject[css.name] = css.href;
}else if(typeof css === 'string') {
stylesObject[css] = css;
}
});
in your
.state('app.CarrouselSearcher',
your data.css is in another object
state.current.views.menuContent.data.css;
// this condition checks just state.data.css
if(state && state.data && state.data.css) {
your css are in views.menuContent then data.css
so this directive cant finds css from multiple views
Related
I am currently working on a .NET Core application based on a CMS Framework named PiranhaCMS. This framework allows the definition of configurable "Blocks", basically widgets, that can be added by the users on their pages. The configuration page of the blocks is realized as a Vue.js component, and code is then compiled via gulp in a standard JS format (from the .vue file to the Vue.component(...) syntax) for the Piranha framework to read and render. The author of Piranha confirmed that this is the only way to define new blocks.
In one of our custom blocks, we are trying to implement a DevExpress Web Dashboard. I have tried following the steps outlined at https://docs.devexpress.com/Dashboard/401150/web-dashboard/dashboard-component-for-vue, but to no avail, since the compiler throws an Exception stating that the top-level declaration should be an export default { ... }, and not an import statement.
I came up with a workaround in which I dynamically load the required scripts and styles on the created() method of the component, and then define the dashboard in the same way I would in a classic javascript case (https://docs.devexpress.com/Dashboard/119158/web-dashboard/dashboard-control-for-javascript-applications-jquery-knockout-etc/add-web-dashboard-to-a-javascript-application);;) however, I am sure there is a more elegant solution to this problem.
Below is the code relevant to the problem. Here is the custom block itools-dashboard.vue:
<template>
<div class="form-group block-body">
<div :id="'dashboard-designer-' + uid" class="dashboard-designer">
<div :id="'dashboard_' + uid" style="height: 100%;">
</div>
</div>
<div class="row">
<div class="col-sm-6" style="padding:10px; margin-top: 0px;vertical-align: top;">
<fieldset>
<legend>Dashboard</legend>
<div class="form-group">
<label>Dashboard name</label>
<select class="form-control small" :id="'dashboard-names-' + uid" v-model="model.dashboardName.value">
<option v-for="dash in dashboardNames">{{ dash }}</option>
</select>
</div>
<div class="form-group">
<label>Update time</label>
<input class="form-control small" type="number" v-model="model.updateTime.value">
</div>
<div class="form-group">
<label>Width</label>
<input class="form-control small" type="text" v-model="model.width.value">
</div>
<div class="form-group">
<label>Height</label>
<input class="form-control small" type="text" v-model="model.height.value">
</div>
</fieldset>
</div>
<div class="col-sm-6" style="padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;">
<itools-base :model="model"></itools-base>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["uid", "toolbar", "model"],
data: function () {
return {
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
// define all the css and js files paths
cssResources: [
"devextreme/dist/css/light.css",
"#devexpress/analytics-core/dist/css/dx-analytics.common.css",
"#devexpress/analytics-core/dist/css/dx-analytics.light.css",
"#devexpress/analytics-core/dist/css/dx-querybuilder.css",
"devexpress-dashboard/dist/css/dx-dashboard.light.min.css"
],
jsResources: [
"js/jquery/jquery-3.3.1.min.js",
"jquery-ui-dist/jquery-ui.js",
"knockout/build/output/knockout-latest.js",
"ace-builds/src-min-noconflict/ace.js",
"ace-builds/src-min-noconflict/ext-language_tools.js",
"ace-builds/src-min-noconflict/theme-dreamweaver.js",
"ace-builds/src-min-noconflict/theme-ambiance.js",
"devextreme/dist/js/dx.all.js",
"devextreme/dist/js/dx.aspnet.mvc.js",
"devextreme-aspnet-data/js/dx.aspnet.data.js",
"#devexpress/analytics-core/dist/js/dx-analytics-core.min.js",
"#devexpress/analytics-core/dist/js/dx-querybuilder.min.js",
"devexpress-dashboard/dist/js/dx-dashboard.min.js"
]
}
},
created: function () {
// dynamically add the required css
this.cssResources.forEach(x => {
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
});
// dynamically add the js files.
// It needs to be a synchronous ajax call so that the exports are visible in the code
// (eg the new DevExpress call)
this.jsResources.forEach(x => {
$.ajax({
async: false,
url: this.basePath + x,
dataType: "script"
})
});
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
},
mounted: function () {
var h = document.getElementById("dashboard-designer-" + this.uid).clientHeight;
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid), {
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value,
});
dashboardControl.render();
},
beforeCreate: function () {
fetch("/api/Dashboards/GetDashboardNames")
.then(response => response.json())
.then(data => {
this.dashboardNames = data;
});
},
}
</script>
which is then compiled via gulp task to
Vue.component("itools-dashboard", {
props: ["uid", "toolbar", "model"],
data: function () {
return {
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
cssResources: ["devextreme/dist/css/light.css", "#devexpress/analytics-core/dist/css/dx-analytics.common.css", "#devexpress/analytics-core/dist/css/dx-analytics.light.css", "#devexpress/analytics-core/dist/css/dx-querybuilder.css", "devexpress-dashboard/dist/css/dx-dashboard.light.min.css"],
jsResources: ["js/jquery/jquery-3.3.1.min.js", "jquery-ui-dist/jquery-ui.js", "knockout/build/output/knockout-latest.js", "ace-builds/src-min-noconflict/ace.js", "ace-builds/src-min-noconflict/ext-language_tools.js", "ace-builds/src-min-noconflict/theme-dreamweaver.js", "ace-builds/src-min-noconflict/theme-ambiance.js", "devextreme/dist/js/dx.all.js", "devextreme/dist/js/dx.aspnet.mvc.js", "devextreme-aspnet-data/js/dx.aspnet.data.js", "#devexpress/analytics-core/dist/js/dx-analytics-core.min.js", "#devexpress/analytics-core/dist/js/dx-querybuilder.min.js", "devexpress-dashboard/dist/js/dx-dashboard.min.js"]
};
},
created: function () {
this.cssResources.forEach(x => {
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
});
this.jsResources.forEach(x => {
$.ajax({
async: false,
url: this.basePath + x,
dataType: "script"
});
});
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
},
mounted: function () {
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid), {
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value
});
dashboardControl.render();
},
beforeCreate: function () {
fetch("/api/Dashboards/GetDashboardNames").then(response => response.json()).then(data => {
this.dashboardNames = data;
});
},
template: "\n<div class=\"form-group block-body\">\n <div :id=\"'dashboard-designer-' + uid\" class=\"dashboard-designer\">\n <div :id=\"'dashboard_' + uid\" style=\"height: 100%;\">\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px;vertical-align: top;\">\n <fieldset>\n <legend>Dashboard</legend>\n <div class=\"form-group\">\n <label>Dashboard name</label>\n <select class=\"form-control small\" :id=\"'dashboard-names-' + uid\" v-model=\"model.dashboardName.value\">\n <option v-for=\"dash in dashboardNames\">{{ dash }}</option>\n </select>\n </div>\n <div class=\"form-group\">\n <label>Update time</label>\n <input class=\"form-control small\" type=\"number\" v-model=\"model.updateTime.value\">\n </div>\n <div class=\"form-group\">\n <label>Width</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.width.value\">\n </div>\n <div class=\"form-group\">\n <label>Height</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.height.value\">\n </div>\n </fieldset>\n </div>\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;\">\n <itools-base :model=\"model\"></itools-base>\n </div>\n </div>\n</div>\n"
});
The gulp task responsible for the compilation, defined by Piranha, is:
var gulp = require('gulp'),
sass = require('gulp-sass'),
cssmin = require("gulp-cssmin"),
uglifyes = require('uglify-es'),
composer = require('gulp-uglify/composer'),
uglify = composer(uglifyes, console),
rename = require("gulp-rename"),
concat = require("gulp-concat");
var path = require('path'),
vueCompiler = require('vue-template-compiler'),
babel = require("#babel/core"),
babelTemplate = require("#babel/template").default,
codeFrameColumns = require('#babel/code-frame').codeFrameColumns,
babelTypes = require("#babel/types"),
through2 = require('through2');
function vueCompile() {
return through2.obj(function (file, _, callback) {
var relativeFile = path.relative(file.cwd, file.path);
console.log(relativeFile);
var ext = path.extname(file.path);
if (ext === '.vue') {
var getComponent;
getComponent = function (ast, sourceCode) {
const ta = ast.program.body[0]
if (!babelTypes.isExportDefaultDeclaration(ta)) {
var msg = 'Top level declaration in file ' + relativeFile + ' must be "export default {" \n' + codeFrameColumns(sourceCode, { start: ta.loc.start }, { highlightCode: true });
throw msg;
}
return ta.declaration;
}
var compile;
compile = function (componentName, content) {
var component = vueCompiler.parseComponent(content, []);
if (component.styles.length > 0) {
component.styles.forEach(s => {
const linesToStyle = content.substr(0, s.start).split(/\r?\n/).length;
var msg = 'WARNING: <style> tag in ' + relativeFile + ' is ignored\n' + codeFrameColumns(content, { start: { line: linesToStyle } }, { highlightCode: true });
console.warn(msg);
});
}
var ast = babel.parse(component.script.content, {
parserOpts: {
sourceFilename: file.path
}
});
var vueComponent = getComponent(ast, component.script.content);
vueComponent.properties.push(babelTypes.objectProperty(babelTypes.identifier('template'), babelTypes.stringLiteral(component.template.content)))
var wrapInComponent = babelTemplate("Vue.component(NAME, COMPONENT);");
var componentAst = wrapInComponent({
NAME: babelTypes.stringLiteral(componentName),
COMPONENT: vueComponent
})
ast.program.body = [componentAst]
babel.transformFromAst(ast, null, null, function (err, result) {
if (err) {
callback(err, null)
}
else {
file.contents = Buffer.from(result.code);
callback(null, file)
}
});
}
var componentName = path.basename(file.path, ext);
if (file.isBuffer()) {
compile(componentName, file.contents.toString());
}
else if (file.isStream()) {
var chunks = [];
file.contents.on('data', function (chunk) {
chunks.push(chunk);
});
file.contents.on('end', function () {
compile(componentName, Buffer.concat(chunks).toString());
});
}
} else {
callback(null, file);
}
});
}
var js = {
name: "itools-blocks.js",
path: "wwwroot/assets/js/blocks/*.vue"
}
//
// Compile & minimize js files
//
gulp.task("min:js", function (done) {
gulp.src(js.path, { base: "." })
.pipe(vueCompile())
.pipe(concat("wwwroot/assets/js/blocks/" + js.name))
.pipe(gulp.dest("."))
.pipe(uglify().on('error', function (e) {
console.log(e);
}))
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("."));
done();
});
any kind of help is well appreciated
The gulpfile with the method “vueCompile” that you’re referring to was specifically written to suit the needs of the internal components we provide in the framework, it’s by no means a silver bullet for all Vue component compilation. However I understand your problem, before writing this code we desperately searched for existing npm-packages that would give us the functionality we needed, but this wasn’t that easy to find as we only use a subset of the features available in Vue.js
We’d be more than happy to get feedback or more information on how this could be done, so we’ll be watching this thread 👍🏼
I have a very simple pug file:
for item in itemList
form(method='post', action='/change')
table
tr
td(width=100)
td(width=200)
| #{item.name}
input(type='hidden', name='field' value=item.name)
input(type='hidden', name='style' value='doublevalue')
td(width=100)
input(type='number', name='value' min=-20.0 max=80.00 step=0.01 value=+item.value)
td(width=100)
input(type='submit', value='Update')
p end
As you can see it produces a few trivial forms like this:
(Each form is one 'line' which is a simple table.)
(On the script side, it just reads each 'line' from a MySQL table, there are 10 or so of them.)
So on the www page, the user either
types in new number (say "8")
or clicks the small arrows (say Up, changing it to 7.2 in the example)
then the user must
click submit
and it sends the post.
Quite simply, I would like it to be that when the user
clicks a small arrows (say Up, changing it to 7.2 in the example)
it immediately sends a submit-post.
How do I achieve this?
(It would be fine if the send happens, any time the user types something in the field, and/or, when the user clicks the Small Up And Down Buttons. Either/both is fine.)
May be relevant:
My pug file (and all my pug files) have this sophisticated line of code as line 1:
include TOP.pug
And I have a marvellous file called TOP.pug:
html
head
style.
html {
font-family: sans-serif
}
td {
font-family: monospace
}
body
I have a solution with javascript.
// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
// add event for each of them
document.querySelectorAll('input[type="number"]').forEach(function(el) {
el.addEventListener('change', function (e) {
// on change submit the parent (closest) form
e.currentTarget.closest('form').submit()
});
});
}
Actually it is short but if you want to support Internet Explorer you have to add the polyfill script too. Internet Explorer does not support closest() with this snippet below we teach it.
// polyfills for matches() and closest()
if (!Element.prototype.matches)
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
Ajax form submit to node.js
If you are interested in an ajax solution I put some code below just to blow your mind ;-) It should work instantly, I use it on one of my sites. You could use jQuery and save lines of code but I like it pure. (The ajax function and polyfills are utils so paste it anywhere)
HTML (example)
<form>
<input type="hidden" name="field" value="field1">
<input type="hidden" name="style" value="style1">
<input type="number" name="value">
<input type="submit" value="update">
</form>
<form>
<input type="hidden" name="field" value="field2">
<input type="hidden" name="style" value="style2">
<input type="number" name="value">
<input type="submit" value="update">
</form>
Javascript: event listener and prepare ajax call (note the callbacks).
// check if there are forms to prevent errors
if (document.querySelector('form')) {
// add submit event for each form
document.querySelectorAll('form').forEach(function (el) {
el.addEventListener('submit', function (e) {
e.currentTarget.preventDefault();
submitData(e.currentTarget);
});
});
}
// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
// add change event for each of them
document.querySelectorAll('input[type="number"]').forEach(function (el) {
el.addEventListener('change', function (e) {
submitData(e.currentTarget.closest('form'));
});
});
}
// collect form data and send it
function submitData(form) {
// send data through (global) ajax function
ajax({
url: '/change',
method: 'POST',
data: {
field: form.querySelector('input[name="field"]').value,
style: form.querySelector('input[name="style"]').value,
value: form.querySelector('input[name="value"]').value,
},
// callback on success
success: function (response) {
// HERE COMES THE RESPONSE
console.log(response);
// error is defined in (node.js res.json({error: ...}))
if (response.error) {
// make something red
form.style.border = '1px solid red';
}
if (!response.error) {
// everything ok, make it green
form.style.border = '1px solid green';
}
// remove above styling
setTimeout(function () {
form.style.border = 'none';
}, 1000);
},
// callback on error
error: function (error) {
console.log('server error occurred: ' + error)
}
});
}
As told javascript utils (paste it anywhere like a library)
// reusable ajax function
function ajax(obj) {
let a = {};
a.url = '';
a.method = 'GET';
a.data = null;
a.dataString = '';
a.async = true;
a.postHeaders = [
['Content-type', 'application/x-www-form-urlencoded'],
['X-Requested-With', 'XMLHttpRequest']
];
a.getHeaders = [
['X-Requested-With', 'XMLHttpRequest']
];
a = Object.assign(a, obj);
a.method = a.method.toUpperCase();
if (typeof a.data === 'string')
a.dataString = encodeURIComponent(a.data);
else
for (let item in a.data) a.dataString += item + '=' + encodeURIComponent(a.data[item]) + '&';
let xhReq = new XMLHttpRequest();
if (window.ActiveXObject) xhReq = new ActiveXObject('Microsoft.XMLHTTP');
if (a.method == 'GET') {
if (typeof a.data !== 'undefined' && a.data !== null) a.url = a.url + '?' + a.dataString;
xhReq.open(a.method, a.url, a.async);
for (let x = 0; x < a.getHeaders.length; x++) xhReq.setRequestHeader(a.getHeaders[x][0], a.getHeaders[x][1]);
xhReq.send(null);
}
else {
xhReq.open(a.method, a.url, a.async);
for (let x = 0; x < a.postHeaders.length; x++) xhReq.setRequestHeader(a.postHeaders[x][0], a.postHeaders[x][1]);
xhReq.send(a.dataString);
}
xhReq.onreadystatechange = function () {
if (xhReq.readyState == 4) {
let response;
try {
response = JSON.parse(xhReq.responseText)
} catch (e) {
response = xhReq.responseText;
}
//console.log(response);
if (xhReq.status == 200) {
obj.success(response);
}
else {
obj.error(response);
}
}
}
}
// (one more) polyfill for Object.assign
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, 'assign', {
value: function assign(target, varArgs) {
// .length of function is 2
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
// polyfills for matches() and closest()
if (!Element.prototype.matches)
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
Element.prototype.closest = function (s) {
var el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
In node.js (e.g. express route)
// the route in node.js
app.post('/change', (req, res) => {
// your logic here
let field = req.body.field;
let style = req.body.style;
let value = req.body.value;
// ...
// response result
res.json({
databaseError: false, // or true
additionalStuff: 'message, markup and other things ...',
});
});
Since I have updated my wordpress installation to 4.2.2 jquery-ui-tabs is not working at theme options page I am loading it at line # 458 in s3-options.php I also have another file which contains jQuery which was perfectly working on previous version of wordpress I am pasting the code below also the file is host on github.
<script type="text/javascript">
jQuery(document).ready(function($) {
var sections = [];
<?php foreach ( $this->sections as $section_slug => $section ){
echo "sections['$section'] = '$section_slug';";
}?>
var wrapped = $(".wrap h3").wrap("<div class=\"ui-tabs-panel\">");
wrapped.each(function() {
$(this).parent().append($(this).parent().nextUntil("div.ui-tabs-panel"));
});
$(".ui-tabs-panel").each(function(index) {
$(this).attr("id", sections[$(this).children("h3").text()]);
if (index > 0)
$(this).addClass("ui-tabs-hide");
});
$(".ui-tabs").tabs({
fx: { opacity: "toggle", duration: "fast" }
});
$("input[type=text], textarea").each(function() {
if ($(this).val() == $(this).attr("placeholder") || $(this).val() == "")
$(this).css("color", "#999");
});
$("input[type=text], textarea").focus(function() {
if ($(this).val() == $(this).attr("placeholder") || $(this).val() == "") {
$(this).val("");
$(this).css("color", "#000");
}
}).blur(function() {
if ($(this).val() == "" || $(this).val() == $(this).attr("placeholder")) {
$(this).val($(this).attr("placeholder"));
$(this).css("color", "#999");
}
});
$(".wrap h3, .wrap table").show();
// This will make the "warning" checkbox class really stand out when checked.
// I use it here for the Reset checkbox.
$(".warning").change(function() {
if ($(this).is(":checked"))
$(this).parent().css("background", "#c00").css("color", "#fff").css("fontWeight", "bold");
else
$(this).parent().css("background", "none").css("color", "inherit").css("fontWeight", "normal");
});
// Browser compatibility
if ($.browser.mozilla)
$("form").attr("autocomplete", "off");
});
Please help me to figured out
In previous versions of angularFire, it was possible to secure selected routes by using "authRequired" and "pathTo" with Angular's $routeProvider. These no longer appear to work with AngularFire 0.6.0. What is the equivalent parameter/technique in Angular 0.6.0?
Routing was moved out of angularFire for the same reasons it was moved out of the core of Angular--to be less opinionated in how routing is conducted and which lib you should use.
You can still include routing by grabbing the module from angularFire-seed, which is plug-and-play ready.
The steps are:
add ngRoute and routeSecurity to your app dependencies
declare the loginRedirectPath constant
add authRequired where appropriate
Example:
// add routeSecurity to your dependency libs
angular.module('myApp', [..., 'ngRoute', 'firebase', 'routeSecurity']);
// declare the loginRedirectPath variable
angular.module('myApp').constant('loginRedirectPath', '/login')
// put authRequired in your routes
$routeProvider.when('/account', {
authRequired: true, // must authenticate before viewing this page
templateUrl: 'partials/account.html',
controller: 'AccountCtrl'
});
// live long and prosper
Here's a hard copy of the module as of 0.6.0 for compliance with SO policy; refer directly to the source for a current version:
(function(angular) {
angular.module('routeSecurity', [])
.run(['$injector', '$location', '$rootScope', 'loginRedirectPath', function($injector, $location, $rootScope, loginRedirectPath) {
if( $injector.has('$route') ) {
new RouteSecurityManager($location, $rootScope, $injector.get('$route'), loginRedirectPath);
}
}]);
function RouteSecurityManager($location, $rootScope, $route, path) {
this._route = $route;
this._location = $location;
this._rootScope = $rootScope;
this._loginPath = path;
this._redirectTo = null;
this._authenticated = !!($rootScope.auth && $rootScope.auth.user);
this._init();
}
RouteSecurityManager.prototype = {
_init: function() {
var self = this;
this._checkCurrent();
// Set up a handler for all future route changes, so we can check
// if authentication is required.
self._rootScope.$on("$routeChangeStart", function(e, next) {
self._authRequiredRedirect(next, self._loginPath);
});
self._rootScope.$on('$firebaseSimpleLogin:login', angular.bind(this, this._login));
self._rootScope.$on('$firebaseSimpleLogin:logout', angular.bind(this, this._logout));
self._rootScope.$on('$firebaseSimpleLogin:error', angular.bind(this, this._error));
},
_checkCurrent: function() {
// Check if the current page requires authentication.
if (this._route.current) {
this._authRequiredRedirect(this._route.current, this._loginPath);
}
},
_login: function() {
this._authenticated = true;
if( this._redirectTo ) {
this._redirect(this._redirectTo);
this._redirectTo = null;
}
else if( this._location.path() === this._loginPath ) {
this._location.replace();
this._location.path('/');
}
},
_logout: function() {
this._authenticated = false;
this._checkCurrent();
},
_error: function() {
if( !this._rootScope.auth || !this._rootScope.auth.user ) {
this._authenticated = false;
}
this._checkCurrent();
},
_redirect: function(path) {
this._location.replace();
this._location.path(path);
},
// A function to check whether the current path requires authentication,
// and if so, whether a redirect to a login page is needed.
_authRequiredRedirect: function(route, path) {
if (route.authRequired && !this._authenticated){
if (route.pathTo === undefined) {
this._redirectTo = this._location.path();
} else {
this._redirectTo = route.pathTo === path ? "/" : route.pathTo;
}
this._redirect(path);
}
else if( this._authenticated && this._location.path() === this._loginPath ) {
this._redirect('/');
}
}
};
})(angular);
I would like to bind to an absolutely positioned element's top style in a directive. Is this possible?
Here is what I would like to do in made up code:
angular.module('exampleModule').directive('resize', [function () {
return {
link: function(scope, iElement, iAttrs) {
var top = 14;
// There is no styleChange event
iElement.bind('styleChange', styleChangeHandler);
function styleChangeHandler(event) {
if(event.style == 'top' && event.value != top) {
scope.$apply(function(scope){
scope[iAttrs.topChanged](event.value);
});
}
}
}
}
}]);
There are no style change events. If you are in control of the style changing you can create your custom event and trigger this manually. Or you could create a watch function, something like this:
link: function(scope, iElement, iAttrs) {
//...
scope.$watch(function(){
return iElement.css('top');
},styleChangeFn,true);
function styleChangeFn(value,old){
if(value !== old)
scope[iAttrs.topChanged](value);
}
}
So here is what I came up with (greatly helped by joakimbl's answer). It will work for watching any style.
The directive:
angular.module('unwalked.directives').directive('watchStyle', [function () {
return {
link: function(scope, iElement, iAttrs) {
scope.$watch(function(){
return iElement.css(iAttrs['watchedStyle']);
},
styleChanged,
true);
function styleChanged(newValue, oldValue) {
if(newValue !== oldValue) {
scope[iAttrs['watchStyle']](newValue);
}
}
}
};
}]);
Usage (Note: no brackets on the callback - it's just the function name):
<div watch-style="functionOnController" watched-style="height" >