Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for the Blaze rendering engine. Also added a more flexible... #26

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ngMeteor
- [Creating and inserting template views](https://github.com/loneleeandroo/ngMeteor#creating-and-inserting-template-views)
- [Routing](https://github.com/loneleeandroo/ngMeteor#routing)
- [Module Injection](https://github.com/loneleeandroo/ngMeteor#module-injection)
- [Flexible module bootstrapping](https://github.com/loneleeandroo/ngMeteor#flexistrap)

### New Data-Binding to avoid conflict
To prevent conflicts with Handlebars, ngMeteor has changed the default AngularJS data bindings from <code>{{foo}}</code> to <code>[[foo]]</code>. For example:
Expand Down Expand Up @@ -165,14 +166,12 @@ You can render this template using handlebars as you would for any other Meteor

{{> foo}}

Templates will also be added to the $templateCache of the ngMeteor angular module. To invoke the template in AngularJS you could use ng-view and specify the template in the $templateCache when defining your routes using the $routeProvider or your could use the ng-template directive to render your template like this:
Meteor templates can also be rendered in your Angular views using the ng-template directive like this:

<ANY ng-template="foo"></ANY>

<!--Add the ng-controller attribute if you want to load a controller at the same time.-->
<ANY ng-template="foo" ng-controller="fooCtrl"></ANY>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer need this since users only will render templates they identify with the ng-template directive. Templates are no longer pre-rendered and cached. This provides for rendering templates via meteor spacebars first and then rendering that output using angular's compile/link functionality.

Pre-rendering with render() and then caching the result was not very flexible in any case if the template was not 'static'. If you needed to generate the template contents, for example, using meteor's rendering engine, and then feed that result into the angular compile/link processor, then using the pre-rendered HTML from the templateCache would not have been helpful.


Templates with names starting with an underscore, for example "_foo", will not be put into the $templateCache, so you will not be able to access those templates using ng-template, ng-include or ng-view.

### Routing
The [ngRoute](http://docs.angularjs.org/api/ngRoute) module developed by the AngularJS team is included in ngMeteor, which will satisfy those with simple routing needs. For example, if you want to call a template called 'foo' and a controller called 'TodoCtrl' when someone lands on your home page you would define your route like this:
Expand Down Expand Up @@ -212,10 +211,38 @@ Using this method, additional functionality has been provided to ngMeteor in the

Feel free to make ngMeteor module smart packages, and please contact [loneleeandroo](https://github.com/loneleeandroo) if you would like your package to be listed here as well.

### Flexible module bootstrapping (flexistrap)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added doc for the fleximodel way of bootstraping angular modules into the dom. This allows multiple modules to be bootstrapped on the same page.


By default, the ngMeteor angular module is bootstrapped into the top level document on all routes. Since modules cannot be nested in angular, should you want to have parts of your DOM controlled by different modules you can do so using a flexible bootstrapping mechanism called flexistrap which is built in to the ngMeteor package. To use this mechanism you call ngMeteor.addFlexistrap() and provide a jQuery-like selector and an angular module name (or array of module names) as parameters. Fleximodel will then instruct angular to bootstrap the modules with the name (or names, if you provided an array of names) into the DOM elements matching the provided selector.

Optionally, you can provide a path string (or array of path strings) as the 3rd parameter to addFlexistrap. This will apply your modules to the selected DOM elements only when the path(s) identified by the provided strings are being rendered. You can provide a value of '*' to apply your flexistrap configuration to all paths (this is the default behavior if this parameter is not provided).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed typeo ... 'infront' became 'in front' in the third line.


Optionally, you can provide a boolean as the 4th parameter. If this value is true, the configuration you provide will replace any existing flexistrap configuration for the given path(s) and selector. If False, it will ONLY update EXISTING configurations. If not supplied or null, it will update existing configurations or add new ones if they don't already exist.

Example:

Define two new modules: 'myModule' and 'yourModule':

myCoolModule = angular.module('myModule',[]);
yourCoolModule = angular.module('yourModule',[]);


Remove existing (default) bootstrapping of ngMeteor on the toplevel document:

ngMeteor.removeFlexistrap(); // removes all existing flexistrap configurations for all paths and selectors.

Bootstrap div with id 'xxx' with new module named 'myModule'

ngMeteor.addFlexistrap('#xxx', 'myModule')

Bootstrap div with id 'yyy' with both the new module 'yourModule' and the original 'ngMeteor' module:

ngMeteor.addFlexistrap('#yyy', ['yourModule', 'ngMeteor'])


<!---
### Dynamic routing
Routes will automaticlly be created based on a template's name, however, you can override the dynamic routes by manually assigning a route using $routeProvider. The route will load that template and conditionally load a controller with the same name if it exists. You can prevent a template from creating a route by adding a "_" infront of the template name. Based on the URL, this is how you should name your templates:
Routes will automaticlly be created based on a template's name, however, you can override the dynamic routes by manually assigning a route using $routeProvider. The route will load that template and conditionally load a controller with the same name if it exists. You can prevent a template from creating a route by adding a "_" in front of the template name. Based on the URL, this is how you should name your templates:

| URL | Template / Controller name | $routeParams |
| :-------------------------------- | :----------------------------- | :----------- |
Expand Down
321 changes: 321 additions & 0 deletions lib/bindonce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
(function () {
"use strict";
/**
* Bindonce - Zero watches binding for AngularJs
* @version v0.3.1
* @link https://github.com/Pasvaz/bindonce
* @author Pasquale Vazzana <[email protected]>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/

var bindonceModule = angular.module('pasvaz.bindonce', []);

bindonceModule.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 transclude = function (transcluder, scope)
{
transcluder.transclude(scope, function (clone)
{
var parent = transcluder.element.parent();
var afterNode = transcluder.element && transcluder.element[transcluder.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);
});
});
};

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.checkBindonce(newValue);
}, true);
},

checkBindonce: function (value)
{
var that = this, promise = (value.$promise) ? value.$promise.then : value.then;
// since Angular 1.2 promises are no longer
// undefined until they don't get resolved
if (typeof promise === 'function')
{
promise(function ()
{
that.runBinders();
});
}
else
{
that.runBinders();
}
},

removeWatcher: function ()
{
if (this.watcherRemover !== undefined)
{
this.watcherRemover();
this.watcherRemover = undefined;
}
},

runBinders: function ()
{
while (this.binders.length > 0)
{
var binder = this.binders.shift();
if (this.group && this.group != binder.group) continue;
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
switch (binder.attr)
{
case 'boIf':
if (toBoolean(value))
{
transclude(binder, binder.scope.$new());
}
break;
case 'boSwitch':
var selectedTranscludes, switchCtrl = binder.controller[0];
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?']))
{
binder.scope.$eval(binder.attrs.change);
angular.forEach(selectedTranscludes, function (selectedTransclude)
{
transclude(selectedTransclude, binder.scope.$new());
});
}
break;
case 'boSwitchWhen':
var ctrl = binder.controller[0];
ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []);
ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element });
break;
case 'boSwitchDefault':
var ctrl = binder.controller[0];
ctrl.cases['?'] = (ctrl.cases['?'] || []);
ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element });
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);
break;
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;
}
};

return ctrl;
}],

link: function (scope, elm, attrs, bindonceController)
{
var value = attrs.bindonce && scope.$eval(attrs.bindonce);
if (value !== undefined)
{
bindonceController.checkBindonce(value);
}
else
{
bindonceController.setupWatcher(attrs.bindonce);
elm.bind("$destroy", bindonceController.removeWatcher);
}
}
};

return bindonceDirective;
});

angular.forEach(
[
{ directiveName: 'boShow', attribute: 'show' },
{ directiveName: 'boHide', attribute: 'hide' },
{ directiveName: 'boClass', attribute: 'class' },
{ directiveName: 'boText', attribute: 'text' },
{ directiveName: 'boBind', 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' },

{ directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 },
{ directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } },
{ directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' },
{ directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' }
],
function (boDirective)
{
var childPriority = 200;
return bindonceModule.directive(boDirective.directiveName, function ()
{
var bindonceDirective =
{
priority: boDirective.priority || childPriority,
transclude: boDirective.transclude || false,
terminal: boDirective.terminal || false,
require: ['^bindonce'].concat(boDirective.require || []),
controller: boDirective.controller,
compile: function (tElement, tAttrs, transclude)
{
return function (scope, elm, attrs, controllers)
{
var bindonceController = controllers[0];
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 new Error("No bindonce controller: " + name);
}
}

bindonceController.addBinder(
{
element: elm,
attr: boDirective.attribute || boDirective.directiveName,
attrs: attrs,
value: attrs[boDirective.directiveName],
interpolate: boDirective.interpolate,
group: name,
transclude: transclude,
controller: controllers.slice(1),
scope: scope
});
};
}
};

return bindonceDirective;
});
})
})();
Loading